目录
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) + 进程的代码和数据,那它们是怎么来的呢?
- task_struct是以父进程为模板,由操作系统来进行初始化获得的;
- 进程的代码和数据在默认情况下,会继承父进程的全部代码和数据;
- fork之后子进程和父进程代码是共享的,内存中只有一份代码;
- 子进程拥有全部的代码,只是因为程序一直往后执行,所以可以看到子进程执行fork后续的代码,但若修改子进程的PC,子进程也可以执行fork之前的代码;
- 在不考虑修改的情况下,父子进程数据也是共享的,但若修改时,则是通过“写时拷贝”完成的,以此来实现进程的独立性。
写时拷贝
原本内存中,父子进程共享数据,但当父(子)进程想要修改数据时,那边会在内存中拷贝一份需要修改的数据,然后供此进程使用。
我们创建子进程就是为了让和父进程干一样的事吗?
其实我们还可以使用 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号进程为操作系统;
坚持打卡!
😃