Linux进程
进程的相关概念(一)链接: link.
1. 通过系统调用创建进程
使用man fork
可以看到fork的功能(创建一个子进程)以及所需要包含的头文件(#include <unistd.h>
)和函数声明,pid_t fork(void)
我们说fork是系统调用子进程的函数,那么来证明一下!在fork()函数之前需要调用关于printf的进程,在fork()函数之后,不但要执行自己的进程,还需要创建一个子进程并且执行它,所以这里的printf应该会被执行两遍。结果如下:
首先你会发现这应该是两个进程,因为他们的PID不相同,一个为20722,一个为20723,对于父进程的ppid来说,是不会改变的,始终为16977,因为这个是bash的pid。但是你有会发现第二个进程的ppid是第一个进程的pid,所以可以确定,fork()函数的确创建了一个子进程。
这里还有个值得注意的就是fork()函数的返回值pid_t ,是有两个返回值的(给父进程返回子进程的PID,给子进程返回0)
但是上述的代码编写使用进程是不正确的,使用进程应该是在fork()调用之后,使用其返回值对其进行分流(因为你所创建出来的子进程总不能和父进程做同样的事情,那么就没有创建它出来的意义了,所以这里选择用if语句对其进行分流。)
执行结果:
对于所编写的代码来说,fork()函数之后写入了两个while(1)的死循环,但是两条语句都执行了,也就验证了上面,在fork之后是要执行两个分支的。
1.1 如何理解进程的创建?
创建进程,是系统多了一个进程,那么系统就要多一组管理该进程的数据结构(task_struct)+该进程对应的代码和数据。此后就会把描述该进程的数据结构放在由许多描述进程的数据结构所构成的类似于由链表所组成的结构中,来方便管理。
1.2 fork为什么会有两个返回值?
因为调用fork()函数,在fork()内部,就会对子进程进行创建,在return id
这条语句执行之前,fork()函数已经将子进程创建完毕,又因为return id
中的id是数据,是父子进程都私有的,所以返回的时候要返回两个返回值。
进程的数据=代码+数据
代码是逻辑,一般是不可被修改的(你的代码实现过程中所需要的if、while语句)
数据,既可以读又可以写(比如你在一段代码中所创建的变量a、b等)
父进程创建子进程的时候,代码是共享的,数据是各自私有一份(写时拷贝) 通过数据私有可以表现出进程的独立性
父子进程fork完毕,谁先运行呢?答案:不确定,这个是由调度器来决定的,因为谁也不知道,他们在队列中的先后顺序情况。
2. 进程状态
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
6.Z僵尸状态(zombie):)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲) 没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
2.1 进程状态的查看
ps aux / ps axj
命令
其中ps aux
经常用来查看进程名
其中ps axj
经常用来查看进程间的父子关系
2.2 R运行状态(running)
当你不再涉及关于IO的时候,就不会影响CPU的运行,%100CPU都在运行,此时就能看到R状态
是R状态就一定在CPU上跑吗?(就一定现在在占用CPU的资源吗?)
这里就引入了一个运行队列的概念:运行队列是由多个PCB所构成的,如果此时你的进程就在运行队列中,那么就可以被称之为R状态。
所以真正的R状态:可以被直接调度的进程。
2.3 S休眠状态(浅度休眠状态)
程序运行会一直打印“.”,但是为什么会显示S+的状态呢,是因为,死循环是你的CPU跑的,但是这里涉及了IO,我的CUP打印的速度超级快,但是你的显示器所打印的速度又非常的慢,然后去检测CPU状态(可以理解为,运行周期呢%99的时间CPU在休息,%1的时间在运行,然而你每次检测基本上都是休息状态,也就是休眠),这里的“+”代表的是在前台运行 当你执行 ./proc &
就相当于放在了后台运行,此时你的“+”就会消失,并且此时你无法在使用ctrl + c对程序进行终止。但是可以使用发送命令kill -9 +对应的进程pid
来进行终止。
所以S状态可以理解为等待的状态,当你一旦需要使用该进程可以立刻将其唤醒。 此时他在等待队列中。
2.4 D磁盘休眠状态(深度睡眠状态)
也叫不可中断休眠状态。
示例:此时你需要读取硬盘上的数据,然后操作系统调度内存去硬盘中取数据,这个过程是需要花时间的,然后你的硬盘接收到内存的信息之后,还需要找相应的文件,这个过程也是需要时间的,但是突然,你的内存不够了,操作系统开始采用杀进程的方式来供后续操作使用,找了一圈,发现你这个进程此时什么也没干(其实他在等待硬盘给他返回他要找的东西),就无缘无故的直接把它杀掉了,那么后续硬盘找到了信息之后,发现调度他的这个进程找不到了,他也不知道该把此时找到的磁盘内容给谁了,就会造成问题。所以这里就引入了D磁盘休眠状态,相当于让此时这个进程带了一个记号,如果操作系统寻找到我的时候,即使我现在没有工作,也不能把我杀掉。只要我拿不到数据,或者说我没有完成我该干的工作,那么我就会一直存在。
2.5 T停止状态(stopped)
kill -l
是查询此时我所能给一个进程发送的列表,前31个叫普通信号,后31个是实时信号。
kill -19 +对应的pid --------让该进程停止
kill -18 +对应的pid --------让该进程继续
2.6 Z僵尸状态(zombie)
简单点说:父进程还存在,但是子进程死掉了。
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
这里编写一个监控脚本。
这里希望父进程一直运行,但是子进程只运行5次,也就是10s之后,子进程就死掉了。
僵尸进程的危害:
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
该进程的资源没有办法回收就会造成内存泄漏
那么为什么会有僵尸状态呢?
1.子进程是否正常运行完
2.是否发生了异常
3.发生了什么异常
保存进程的基本退出信息,方便父进程读取,获得退出的原因
有什么特征呢?
一般,僵尸的时候,task_struct是会被保留的,进程的退出信息是放在进程控制块的。查询完毕之后,才会改变状态为X(死亡状态)
2.7 孤儿进程
简单点说:当你的父进程无缘无故的死掉了,那么你此时的子进程就是孤儿进程。 OS考虑到了这个问题,孤儿进程被1号systemd的进程领养,当然要有systemd进程回收。