在了解进程概念前我们还得了解下冯诺依曼体系结构和操作系统的概念与定位。
1 冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系:
- 输入单元:包括网卡,键盘, 鼠标,扫描仪, 写板,话筒等 ;
- 中央处理器(CPU):含有运算器和控制器等 ;
- 输出单元:网卡,显示器,打印机等;
关于冯诺依曼,必须强调几点:
- 这里的存储器指的是内存 ;
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备) ;
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取;
- 一句话,所有设备都只能直接和内存打交道;
对冯诺依曼的理解,不能只停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。
在不考虑网络层情况下,小明用qq向小红发送了一条消息,小明的电脑从键盘上读取信息然后加载到内存,再从内存将数据通过一系列操作发送到输出设备上(网卡),然后通过一系列的网络操作将数据发送到小红的输入设备上(网卡),小红的电脑再从输入设备中将数据读到内存,然后通过输出设备(显示器)将信息刷新到小红的电脑上,这里数据刷新是两个方面的,再成功发送后小明的电脑也会显示出已经成功发送后的信息。
2 操作系统(Operator System)
概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
- 内核(进程管理,内存管理,文件管理,驱动管理)
- 其他程序(例如函数库,shell程序等等)
设计OS的目的
- 与硬件交互,管理所有的软硬件资源
- 为用户程序(应用程序)提供一个良好的执行环境
定位
在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件。
总结
计算机管理硬件
- 先描述起来,用struct结构体
- 再组织起来,用链表或其他高效的数据结构
系统调用和库函数概念
在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
3 进程
有了上面对冯诺依曼体系结构和操作系统的理解,我们自然可以想到进程也是先描述,再组织。
3.1 基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
有些教材书上甚至是给出这样的定义的:进程就是程序加载到内存中。但是我觉得这种描述是狭隘的不够具体的,具体的我们下面会给出解释.
3.2 描述进程-PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
task_struct是PCB的一种:
- 在Linux中描述进程的结构体叫做task_struct。
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息
我们将所有进程的属性用一个队列来维护,当我们想要加载程序时就将它的PCB链接到该运行队列中,这样就很好的维护了进程。
那现在我们再来回答什么是进程?
进程=当前程序的代码和数据+内核关于进程的相关数据结构
task_ struct内容分类:
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
3.2 组织进程
- 可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。
3.3 查看进程
我们在Linux环境中创建了一个profile.cpp的Cpp文件,然后编译运行生成了一个叫做profile的可执行文件,我们可以通过一下命令来查找进程:
ps ajx | head -1 && ps ajx | grep "查找进程的名字"
当我们运行profile后来查看:
不难发现我们查询到了profile进程的一些基本信息,如果我们想不加上下面那一行的信息可以将命令后面多加一些内容:
ps ajx | head -1 && ps ajx | grep "查找进程的名字" | grep -v grep
当然,文件名可加可不接双引号。
我们还可以在./proc中查询:
ls ./proc
3.4 通过系统调用获取进程标示符
- 进程id(PID)
- 父进程id(PPID)
我们向profile.cpp中写入以下代码:
1 #include<iostream> 2 #include<sys/types.h> 3 #include<unistd.h> 4 using namespace std; 5 6 7 int main() 8 { 9 while(1) 10 { 11 pid_t ret=getpid(); 12 cout<<"hello"<<ret<<" "<<endl; 13 pid_t t=fork(); 14 if(t==0) 15 { 16 while(1) 17 { 18 cout<<"我是一个子进程"<<" pid:"<<getpid()<<" ppid:"<<getppid()<<endl; 19 sleep(1); 20 } 21 } 22 else if(t>0) 23 { 24 while(1) 25 { 26 cout<<"我是一个父进程"<<" pid:"<<getpid()<<" ppid:"<<getppid()<<endl; 27 sleep(1); 28 } 29 } 30 } 31 return 0; 32 }
当我们查看进程时:
fork()后执行流会变成两个,是先执行父进程还是子进程是由调度器决定的,fork()后的代码共享,我们通常是用if else 来进行分流的。
- 运行 man fork 认识fork
RETURN VALUE On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
当我们只读数据不写数据时,父子进程是共享代码的,而当有其中一个执行流尝试修改数据时OS就会在当前进程触发写时拷贝另外生成一份。
如何理解有两个返回值呢?
创建子进程本质上就是OS提供的一个函数,当函数内部进行return 时我们主体功能已经完成了。