Linux--进程概念(2)

1. fork创建子进程

1.1 如何理解fork创建子进程

linux下查看子进程:

printf("pid:%d ppid:%d\n",getpid(),getppid());
pid:3903 ppid:2337
pid:3904 ppid:3903
//pid是子进程,ppid是父进程
//第一行的pid是当前运行进程,ppid是父进程bash
//第二行的pid是另一子进程,而上一子进程3903作为此行父进程ppid
  1. 指令./cmd、运行普通指令command和fork创建子进程,在操作系统的角度其实没有差别,创建子进程的方式都是一样的;
  2. fork本质是创建子进程,就相当于系统里多了一个进程,即与进程相关的内核数据结构+进程的代码和数据在系统里多了一份。(struct_task+代码数据)
  3. 对于fork来说,由于创建的子进程没有自己的进程代码和数据,默认情况下会“继承”父进程的代码和数据;而其内核数据结构也会以父进程为模板,初始化子进程的内核数据结构
  4. fork之后,子进程和父进程的代码是共享的。但是其中代码无法修改,所以仅仅只是“共享而已”;默认情况下,数据也是共享的,如果需要修改,就会发生写时拷贝
  • 这个“共享”如何理解?
    在这里插入图片描述

所以共享其实就是:父子进程页表里的指针指向同一块空间。

写时拷贝如上:写时拷贝后物理内存会开辟新的空间给子进程。

  1. 进程是具有独立性的,而在“共享”的同时保持“独立型”,就需要写时拷贝;

既然如此,为什么不在创建的时候就分开?

  • 理由

a.子进程不一定会写入数据,所以应该是按需分配,需要的时候才创建。
b.延时分配;在这之前也许那块空间可以分配给别人,可以高效利用空间。

  1. 创建子进程后,父子进程谁先运行是不确定的,取决于编译器调度性。

1.2 fork的返回值

如果说创建子进程仅仅是为了做和父进程一样的事,那么是没有意义的;所以创建子进程主要还是为了做和父进程不一样的事情。这一点可以通过其返回值来完成:

返回规则:若失败返回<0,若成功给父进程返回子进程的pid,并且给子进程返回0。

pid_t ret = fork();//fork返回值类型为pid_t
printf("pid:%d ppid:%d ret:%d\n",getpid(),getppid(),ret);
pid:8312 ppid:2337 ret:8313
pid:8313 ppid:8312 ret:0

可以看到第一行父进程返回值确实是子进程的pid8313

  • 疑问
  1. 如何理解一个函数有两个返回值?
  2. 如何理解这些返回值的设置?
  3. 从什么时候开始产生了子进程?

在fork函数中,里面有创建子进程的语句,那么在执行完该逻辑后,子进程就会被创建,当fork函数开始返回的时候,由于return也是语句,那么父子进程都会执行该语句,所以也就有了两个返回值。

而返回值是临时数据,会被保存在临时变量中,这个保存的过程发生了数据的写入,上文说过,父子进程“共享”的数据在需要被修改的时候,会发生写时拷贝来达到目的,此时就是写时拷贝的过程。

对于为什么返回值是那样的结果,这是由于每个子进程的父进程只有一个,而一个父进程可以有多个子进程,那么父进程需要通过对应的pid来找到子进程,而子进程只需要调用getppid()即可找到父进程。

  • 何时创建子进程

而对于第三个问题:不要认为fork函数执行完了,return完以后才创建好子进程,在return之前,子进程就已经创建好了

这是因为fork函数的原理其实是把父进程的相关内容(task_struct,mm_struct,页表,文件等其他信息)都拷贝了一份进去,并且在这之后就完成了:

1. 分配新的内存块和内核数据结构给子进程
2. 将父进程部分数据结构内容拷贝至子进程
3. 添加子进程到系统进程列表当中

所以走到这里子进程已经创建好了,而下一步才是程序返回return,开始调度器调度。

  • 结论

那么通过这些理论,就可以做到父子进程分别处在处理不同的逻辑,效率变高:

  if(ret == 0)
  {//子进程}
  else if(ret > 0)
  {//父进程}
  else {//返回失败另作处理}

2. 进程状态

  • 进程状态的意义

进程状态的信息存放在task_struct(PCB)中

方便OS快速判断进程,完成特定的功能,比如调度,本质就是一种分类。

进程不是只会等待CPU资源,其他外设如网卡、磁盘等也会有进程等待队列;
所谓的进程在运行的时候,也有可能因为运行需要,会在不同的队列里。

在不同的队列里,运行状态是不一样的如S(sleeping浅度睡眠、可唤醒睡眠)、D状态(disk sleeping深度睡眠、不可唤醒睡眠),这便是资源调度。

2.1 R运行状态(running)

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

int main()
{
	while(1);
	return 0;
}

运行起来后查看进程,可以看到两次查看test的进程都是处于运行状态,因为程序处于死循环运行状态:

(加号表示前台运行程序,与后台运行程序的区别就是前台运行可以被kill命令的继续和终止响应,而后台运行则不可以,但是可以被kill -9强行杀死,后台运行命令:./可执行程序名 &,或者被终止的进程再次恢复后就会成为后台运行)

在这里插入图片描述

但是把程序增加一句打印代码后:

int main()
{
	while(1)
	{
		printf("aaaaaaaaa\n");
	}
	return 0;
}

输出结果:
在这里插入图片描述

可以看到原来运行状态的进程变成了睡眠状态(S)

同样是死循环,但是运行的状态却不同,这是因为打印到显示器本质上是打印到外设,访问外设对于CPU来说是很慢的,在CPU看来,只需将其进程挂起并且将等待队列中的进程不断地频繁切换,就能给人带来程序在不断运行的效果。

2.2 睡眠状态(S/D)

  • 概念

睡眠状态分为S(sleeping浅度睡眠、可唤醒睡眠)、D状态(disk sleeping深度睡眠、不可唤醒睡眠)

挂起和唤醒:从运行状态的task_struct(run_queue),放到等待队列中,就叫做挂起等待(阻塞);从等待队列放到运行队列,被CPU调度就叫唤醒进程。

进程的状态信息在struct_task(PCB),通过PCB来形成等待队列

在这里插入图片描述

  • 作用

当一个进程处于D状态,是不可中断、深度睡眠状态,该进程不可被OS杀掉。 因为该进程也许会在与磁盘进行写入工作,正在等待磁盘写入数据;如果此时被OS杀掉,会造成磁盘的响应无法接收,造成未知来源的进程错误。

2.3 僵尸状态(Z)

进程会有死亡状态(返回状态),这时候需要回收进程资源,也就是进程相关的内核数据和对应的代码和数据。除此之外,还需要辨别死亡的原因(进程退出的信息);僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。

也就是说如果没人检测或者回收进程,该进程就进入Z进程;僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

如下列代码:
在这里插入图片描述

这个代码的意思是,父子进程同时进行,子进程进行死循环打印,而父进程休眠20s,在这20s内我们把子进程kill -9杀死,那么由于父进程此时无动作,所以子进程的退出状态码没有被读取,那么就会进入僵尸状态

在20s内杀死子进程,并运行下列程序:

while :; do ps axj | head -1 && ps ajx | grep test | grep -v grep; sleep 1;echo "##############";done

可以观察到子进程从S状态变成了Z状态:
在这里插入图片描述

进程退出时,不会立即释放PCB里的内容,而是把退出的信息写到PCB中,使得OS可以查看到。

在这里插入图片描述

图中运行态+就绪态就是R(run运行)状态,阻塞对应的就是S/D(睡眠/深度睡眠)状态,停止就是Z(僵尸状态)。

2.4 僵尸进程危害

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

2.5 孤儿进程

在僵尸进程的基础上,让子进程一直运行,而父进程休眠5s后提前终止进程:

在这里插入图片描述

可以看到运行结果:很明显此时的PPID是1号进程(操作系统),这时候的情况其实是因为父进程过早退出, 导致子进程成为了孤儿进程,此时的操作系统便会领养该进程:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值