前言:
我们在前面的对操作系统的学习,目的是为了让我们加深操作系统对“管理”的描述。我们在上一节了解到操作系统存在许多管理,今天我们就来初次了解一下——进程管理。
进程概念:
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体
一个任务就是一个进程,而一个程序可能含有多个进程。
Windows的任务管理器就监控着你电脑正在运行的各个进程以及他们所占用资源的百分比。通过上图不难看出,Windows操作系统含有非常多的进程,那么既然存在那么多的进程,那就需要对进程进行管理。
OS该如何管理进程?
我在我的任务管理器里发现我正在执行Chrome的进程,Chrome的可执行程序在不运行的时候是存放在硬盘中的,那么当运行Chrome时,OS就会将Chrome可执行程序 + 代码存放至内存中。
但是这也并不是管理呀,之前在讲解冯诺依曼体系的时候就了解过,所谓的将数据和代码存放至内存,不过是拷贝而已,但是没错仅仅是拷贝而已,实在是不知道怎么构成管理。
现在让我们来回顾一下,我们在上一篇文章还谈及过管理二字,提及管理永远是六个字——“先描述,再组织”。想想上一篇文章举的例子,转换成链表的增删查改。
其实在计算机还未开机的时候,操作系统的相关熟悉还没有运转起来,还是以二进制的方式存入在磁盘之中,而开机之后,操作系统则会加载至内存之中,这时候如果有一个进程需要执行,此时操作系统内部就会创建一个结构体,这个结构体就叫做——“进程控制块”,我们简称PCB(process control block)。task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。而不同的操作系统有不同的PCB名称,Windows叫作EPROCESS,而Linux的则叫task_struck,是一个结构体类型。
先描述:task_struct存有当前进程的所以属性,并且存在内存指针,方便查找该内存的代码 + 数据。
再描述:task_struct存有next指针,可以指向下一个task_struct形成链表,所以OS对于进程的管理,可以简化为对链表的增删查改。
进程 = 自己的PCB + 自己的代码和数据
如何理解进程动态运行?
对于我们吃力多个task_struct我们进行调度运行进程,本质就是让进程控制块task_struct进行排队。只要我们进程的task_struct,将来在不同队列中,进程就可以访问不同的资源。
进程的task_struct本身属性都有哪些?
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
1、以前我们使用./来运行可执行程序,本质就是让系统创建进程并运行。我们自己写的代码 == 系统命令 == 可执行文件。在Linux中运行的大部分执行操作,本质都是运行进程。
2、给个进程都有自己的标识符,叫“进程pid”,具体方法我们可以通过指令查看当前运行的进程,输入指令:ps ajx
3、一个进程若是想要知道自己的pid,我们就可以输入指令:ps ajx | head -1 && ps ajx | grep process 这样通过管道和grep的方式直接定位你要查询的进程的信息。
下面我先写一个简单的C语言程序。
现在编译运行可执行程序然后通过ps ajx | head -1 && ps ajx | grep process 查询当前进程的pid:
当然你也可以在写入新的程序中使用getpid(),这是我们第一次使用系统调用!!!
而getpid()函数需要:头文件<unistd.h>,返回值pid_t getpid();
可以看到系统调用出来的该进程的pid和我们监察窗口的pid内容一致。当然你还可以通过ppid()查看父进程的pid。
用户想要知道pid,则需要去内核中查找,因此就需要接口层提供接口,这个接口就是getpid()函数。
4、ctrl + c 就是在用户层面终止进程,我们也可以使用指令 kill -9 XXX(这里代表pid),直接杀掉进程。
进程创建的代码的方式——重操作,轻原理
我们在前面提到了pid_t ppid(),是获取当前进程父进程的pid。我们可以通过代码来写一写:
这时候却发现两次进程的pid是不一样的,但是ppid父进程的却是一样。
其实,进程每次启动,对应的pid都不一样这是正常的,只要父进程的pid(ppid)是一样的,那这个父进程是谁呢?
输入指令:ps ajx | head -1 && ps ajx XXX (这里输入ppid的值)
最终我们发现,这个父进程原来是bash
这不就是之前讲解Linux权限,所提到的“王婆”吗?不就是个命令行解释器嘛。。。
fork创建子进程
运行上述代码
明明有只有一行printf,却打印了两行,而且各自的pid和ret都不一样,这是啥情况?
- 其实是fork函数具有多个返回值。fork函数目前我们可以理解为,创建了一个子进程,fork函数之后的代码,父子共享。
- 创建了一个进程,本质是系统中多了一个内核task_struct + 代码和数据。
- 父进程的代码和数据是从磁盘加载出来的,子进程的代码和数据,默认继承父进程的代码和数据。
所以对于fork返回值等于0时才会进入子进程,因此fork 之后通常要用 if 进行分流操作:
这不难看出子进程的ppid就是父进程的pid,而父进程的ppid上面也验证过了,就是bash的pid。
两个问题
为什么要创建子进程?
目前我们只是初学阶段,对日后的知识概念还并不熟悉,将来肯定会有需要我们让子进程和父进程执行不一样的代码。到了那个时候就会运用到了!
同一个变量为什么会有两个值?
对于这一段代码,我们不难发现最后的运行结果竟然又是打印了两次,你说是返回了两次而且还是返回了两个不同的返回值,对于该部分的理解,我们需要讲解到虚拟地址 + 地址空间那一部分我们才能理解清楚。
目前我们只需要知道,fork之后,父子进程一起执行剩下的代码就好了。