> 文章列表 > 【hello Linux】进程概念(下)

【hello Linux】进程概念(下)

【hello Linux】进程概念(下)

目录

1. 通过系统调用创建进程—fork

1.1 通过fork创建进程:

1.2 如何不退出 vim 直接执行命令呢?

3. fork创建进程的本质

4. 父子进程的分流:

 2. 进程状态

 3. 信号

3.1 显示全部信号

3.1 停止进程

3.2 继续进程

3.3 杀死进程

 后台进程

 4. 僵尸进程与孤儿进程

4.1 僵尸进程

4.2 孤儿进程


 Linux🌷

1. 通过系统调用创建进程—fork

1.1 通过fork创建进程:

经过运行,确实证明了它们是父子关系。

那父进程是由哪个进程创建的呢?

经过查看它是由 bash 创建的。

1.2 如何不退出 vim 直接执行命令呢?

1. 在 vim 下使用man手册查看fork的用法

2. 在vim下编译.c文件

3. 在vim下运行程序

上述命令执行完后,我们按q直接便可以进入vim的命令模式

3. fork创建进程的本质

在操作系统中,创建进程有多种方式:

1. 在shell中执行各种命令;

2. 使用fork()系统调用;

其实在操作系统角度,上述两种创建进程的方式是没有差别的!!!

fork 创建进程的本质

fork会创建进程那就意味着,操作系统中多了一个进程,也就是说多了:与进程相关的内核的数据结构(task_struct) + 进程的代码和数据,那它们是怎么来的呢?

  1. task_struct是以父进程为模板,由操作系统来进行初始化获得的;
  2. 进程的代码和数据在默认情况下,会继承父进程的全部代码和数据;
  3. fork之后子进程和父进程代码是共享的,内存中只有一份代码;
  4. 子进程拥有全部的代码,只是因为程序一直往后执行,所以可以看到子进程执行fork后续的代码,但若修改子进程的PC,子进程也可以执行fork之前的代码;
  5. 在不考虑修改的情况下,父子进程数据也是共享的,但若修改时,则是通过“写时拷贝”完成的,以此来实现进程的独立性。

写时拷贝

原本内存中,父子进程共享数据,但当父(子)进程想要修改数据时,那边会在内存中拷贝一份需要修改的数据,然后供此进程使用。

我们创建子进程就是为了让和父进程干一样的事吗?

其实我们还可以使用 if else 对 fork() 的返回值进行判断,根据返回值的不同,进行分流,让它们执

行不同的事情。

4. 父子进程的分流:

fork的返回值: 

失败:<0;

成功:给父进程返回子进程的pid;

           给子进程返回0;

下述代码是一个父子进程分流的样例:

如何理解fork会有两个返回值呢?

其实fork是一个函数,首先执行fork内部的核心功能(创建子进程),完了之后便执行return 返回值,

在return之前,子进程已经创建完毕,父子进程各自执行return,return之后的返回值也是数据,这

是便发生了“写时拷贝”,产生了不同的返回值。

 fork之后,其实父子进程谁先运行,这是不确定的,是由调度器(CPU)来进行调度的。

 2. 进程状态

进程是有状态信息的。其本质是一种分类,是为了方便OS快速判断进程,完成特定的功能。

进程的状态信息存在task_struct中。

在内核源代码中进程的状态有如下定义:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
}; 

R:运行状态

 注意:

运行状态并不意味着进程一定在运行中,它表明进程要么是在运行中,要么在运行队列里。

操作系统会对运行队列中的task_struct进行调度,每次调度相隔时间很短,我们察觉不到。

S:可中断睡眠 / 浅度睡眠

D:不可中断睡眠 / 深度睡眠

当进程需要等待除了CPU外的其他资源时,便会陷入睡眠状态,例如:磁盘、网卡、显示器

等资源;

我们把,从运行状态的task_struct从run_queue,放到等待队列中,就叫做挂起等待,也称为

阻塞;

从等待队列,放到运行队列,被CPU调度就叫做唤醒进程。

深度睡眠:D

当进程需要磁盘资源时,对磁盘进行申请,磁盘的动作是很慢的,CPU的动作是很快的;

此时内存资源极度缺乏,OS看见此进程在睡眠(其实在申请磁盘资源),便杀死此进程,腾出资

源,这时此进程便进入深度睡眠状态;

在此状态,该进程是不可被 ctrl c 退出的,也不能 kill -9 pid 杀死该进程。

最简单的方式便是重启shell;

另一种便是修改内核,将进程状态由D改为其他状态,然后使用kill ,一般很少会遇见。

 S:可中断睡眠 / 浅度睡眠

我们很疑惑:进程不是一直在运行吗?为什么还是处于浅度睡眠态呢?

这个程序是将内容输出到显示器上的,显示器是一个外设,对于CPU来说速度特别慢;

进程其实是一直处在R与S状态的切换中的。

而R:运行状态演示的那个代码没有输出操作,不用等待IO因此处于R态;

在S状态,按 ctrl c 可退出进程的运行;

 T:停止状态

彻底停止,什么都不做,不同于S,S只是睡眠,比如sleep(1),更新:1秒最后会为0;

t:追踪状态

因为追踪而进行定位,它的典型应用为:F9打断点后,查看一些信息;

X:死亡状态

进程死亡便要回收:进程资源 = 进程相关的内核数据结构 + 进程的代码和数据

Z:僵尸状态

有僵尸状态是为了辨别进程退出死亡的原因!也就是进程推出的信息,它也是一种数据,是存在

task_struct中的

3. 信号

3.1 显示全部信号

kill -l

 在信号里面有三个我们常用的:

3.1 停止进程

kill -19 pid

3.2 继续进程

kill -18 pid

3.3 杀死进程

kill -9 pid

 后台进程

 后台进程:可以在运行时输命令,无法ctrl c杀死,只能kill -9 pid杀死 

将一个进程在后台运行:

//原本正常运行程序
./test//后台运行
./test &

将后台进程提到前台:

后台运行是可以输入命令的
直接在正在运行进程的会话中输入fg命令

4. 僵尸进程与孤儿进程

4.1 僵尸进程

僵死状态(Zombies)是一个比较特殊的状态。
当子进程退出并且父进程(使用wait()系统调用,后面讲)
没有读取到子进程退出的返回代码时就会产生僵死()进程;
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

也就是说:

当父进程一直存在,子进程被杀,但子进程的资源没有被回收,那么子进程便会变为僵尸状态;

当一个进程处于僵尸状态时,kill 9也无法杀死;

 杀死子进程查看发现子进程变成僵尸状态。

僵尸进程的危害:

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任
务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?
是的
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)
中,换句话说,Z状态一直不退出,PCB一直都要维护?是的
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因
为数据结构对象本身就要占用内存,想向C中定义一个结构体变量(对象),是要在内存的
某个位置进行开辟空间!
内存泄漏?是的!
如何避免?后面讲

4.2 孤儿进程

当父进程先退出,那么父进程创建的子进程便会称为孤儿进程,该进程会被1号进程领养;

1号进程为操作系统;

杀死父进程,查看发现子进程变为孤儿,被1号进程领养,1号进程为操作系统;

 坚持打卡!

😃