🌏博客主页:PH_modest的博客主页
🚩当前专栏:Linux跬步积累
💌其他专栏:
🔴 每日一题
🟡 C++跬步积累
🟢 C语言跬步积累
🌈座右铭:广积粮,缓称王!
基本概念
课本概念: 进程的一个执行实例,正在执行的程序等。
内核观点: 担当分配系统资源(CPU时间,内存)的实体。
当你的代码进行编译链接之后便会生成一个可执行程序,这个可执行程序本质上是一个文件(例如:main.cpp),是放在磁盘上的。当我们运行这个可执行程序时,本质上是将这个程序加载到内存后,CPU才能对其进行逐行的语句执行,当这个程序加载到内存后,我们就不应该将它叫做程序了,而应该称其为进程。
描述进程-PCB
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB,Linux操作系统下的PCB是:task_struck
我们可以使用 ps aux 来显示系统中存在的进程:
开机的时候启动的第一个程序就是我们的操作系统(操作系统是第一个加载到内存的),操作系统主要是做管理工作的,其中包括对进程的管理。
操作系统如何对进程进行管理呢?
首先双手奉上六字口诀:先描述,再组织。当一个进程出现时,操作系统会对其进行描述,之后对该进程的管理就转化为对其描述信息的管理。
进程信息被放在一个叫做进程控制块(PCB)的数据结构中,可以理解为进程属性的集合。
struct PCB
{
//状态相关的代码(一般是一个整形变量,不同的数字表示不同的状态)
//优先级相关的代码
//内存指针字段
//... 包含进程几乎所有的属性字段
struct PCB* next;
}
小结:
- 当磁盘内的可执行程序加载到内存时,内存会生成对应的PCB对其代码和数据进行管理。未来,所有对进程的控制和操作,都只和进程有关,和进程的可执行程序没有关系!
- 对进程的管理转化为对PCB对象的管理,而PCB对象是以链表形式连接起来的,又转化为对链表的增删查改。
- 因此,不难得出进程的组成:
进程 = 内核PCB对象 + 可执行程序
或者:
进程 = 内核数据结构 + 可执行程序
task_stuct - PCB的一种
进程控制块(PCB)是描述进程的,在C++中我们称之为面向对象,在C语言中我们称之为结构体,而Linux是用C语言写的,那么Linux中的进程控制块必定是用结构体来实现的。
- 在Linux中描述进程的结构体叫做 task_struct。
- task_struct 是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
task_struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器(pc指针/eip): 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据: 进程执行时处理器的寄存器中的数据。
- I/O状态信息: 包括显示I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。
- 其他信息。
补充:pc指针
- pc指针指向哪一个进程的代码,就表示哪一个进程被调度运行!
- 判断、循环、函数跳转的本质:修改pc指针。
组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以 task_struct 链表形式存在内核里。
查看进程
通过系统目录查看
在根目录下有一个名为proc的系统文件夹
文件夹当中包含大量进程信息,其中有些子目录的目录名位数字。
这些数字其实是某一些进程的PID,对应文件夹当中记录着对应进程的各种信息。我们若想查看PID为1的进程的进程信息,则直接查看该文件夹即可。
通过ps命令查看
单独使用ps命令,会显示所有进程信息。
ps命令与grep命令搭配使用,即可只显示某一进程的信息。
通过系统调用获取进程的PID和PPID
通过使用系统调用函数,getpid 和 getppid 即可分别获取进程的 PID 和 PPID。
示例:
当运行该代码生成的可执行程序后,便可循环打印该进程的PID和PPID。
我们可以通过ps命令查看该进程的信息,即可发现通过ps命令得到的进程的PID和PPID与使用系统调用函数getpid和getppid所获取的值相同。
通过系统调用创建进程 - fork初始
fork函数创建子进程
fork是一个系统调用级别的函数,其功能就是创建一个子进程。
例如:
若是代码当中没用fork函数,我们都知道代码的运行结果就是循环打印该进程的PID和PPID。而加入了fork函数后,代码运行结果如下:
运行结果是循环打印两行数据,第一行数据是该进程的PID和PPID,第二行数据是代码中fork函数创建的子进程的PID和PPID。我们可以发现fork函数创建的进程的PPID就是proc进程的PID,也就是说proc进程与fork函数创建的进程之间是父子关系。
每出现一个进程,操作系统就会为其创建PCB,fork函数创建的进程也不例外。
我们知道加载到内存当中的代码和数据是属于父进程的,那么fork函数创建的子进程的代码和数据又从何而来呢?
运行结果:
实际上,使用fork函数创建子进程,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行。需要注意的是,父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝)。
注意点: fork函数创建子进程之后就有了两个进程,这两个进程被操作系统调度的顺序是不确定的,这主要取决操作系统调度算法的具体实现。
使用if进行分流
一般情况我们不会直接让父子进程做相同的事情,通常情况会使用if进行分流,让父进程和子进程做不同的事情。
fork函数的返回值:
- 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
- 如果子进程创建失败,则在父进程中返回-1。
fork创建出子进程后,子进程会进入到if语句的循环打印中,而父进程会进入到else if语句的循环打印当中。