Linux进程
1.通过系统调用创建进程fork初识
使用man fork
可以看到fork的功能(创建一个子进程)
在fork()函数之前需要调用关于printf的进程,在fork()函数之后,不但要执行自己的进程,还需要创建一个子进程并且执行它,所以这里的printf应该会被执行两遍。结果如下:
首先你会发现这应该是两个进程,因为他们的pid不相同,其中3371时3372的父进程
30538时3371的父进程bash。
值得注意的就是fork()函数的返回值pid_t ,是有两个返回值的(给父进程返回子进程的PID,给子进程返回0)
- fork 之后通常要用 if 进行分流
1.1如何理解进程创建?
创建进程,是系统多了一个进程,那么系统就要多一组管理该进程的数据结构(task_struct)+该进程对应的代码和数据。此后就会把描述该进程的数据结构放在由许多描述进程的数据结构所构成的类似于由链表所组成的结构中,来方便管理。
-
进程的数据=代码+数据
-
代码是逻辑,一般是不可被修改的
-
数据,既可以读又可以写
-
父进程创建子进程的时候,代码是共享的,数据是各自私有一份(写时拷贝) 通过数据私有可以表现出进程的独立性。
-
创建完毕之后,两者穿插开始执行,没有先后顺序,具体要看调度策略和进程的优先级,比如说先来先服务,时间片轮转等。
1.2 fork为什么有俩个返回值?如何深刻的理解?
首先fork是一个函数,还是系统调用。
调用fork()函数,在fork()内部,就会对子进程进行创建,在return id这条语句执行之前,fork()函数已经将子进程创建完毕。现在已经有两个进程,依次返回自己的id数据( 私有的数据 ) ,所以就有了两个返回值。
2.进程状态
一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
- R运行状态(running):进程在运行当中或者在运行队列中。
- S睡眠状态(sleeping):进程在等待事件完成(也有的时候叫可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(disk sleep):也叫不可中断休眠状态(uninterruptible sleep ),在这个状态通常在等待io结束。
- T停止状态(stopped): 可以通过发送SIGSTOP信号给进程来停止这个进程,这个被暂停的进程可已通过发送SIGCONT信号来让进程继续运行。
- X死亡状态(dead): 这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
2.1 进程状态的查看
ps aux / ps axj
2.2 R运行状态(running)
当你不再涉及关于IO的时候,就不会影响CPU的运行,%100CPU都在运行,此时就能看到R状态。
是R状态就一定在CPU上跑吗?(面试题)
这里就引入了一个运行队列的概念:运行队列是由多个PCB所构成的,如果此时你的进程就在运行队列中,那么就可以被称之为R状态。
所以真正的R状态:可以被直接调度的进程。
2.3 S休眠状态(浅度休眠状态)
写一个死循环,并运行他。为什么是s状态呢,因为cpu是很快的,有printf就意味着有io要显示到控制台,导致cpu调度进程99%的时间都在等待,导致查看的时候是S状态,+代表进程在前台运行可以被ctrl+c终止。
2.4 T停止状态(stopped)
通过kill -l列出命令信息 。前31个叫普通信号,后31个是实时信号。
SIGSTOP命令是19号
kill -19来让它进入T状态
3. 僵尸进程
- **僵尸状态(zombies)**是一个比较特殊的状态。当进程退出且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。
- 僵尸进程会以终止状态保持在进程表中,并且一直在等待父进程读取自己的退出代码。
- 所以只要子进程退出,父进程还在运行,但父进程没有读取到子进程退出状态的代码子进程进入Z状态。
手动写一个僵尸进程,当10s过后子进程退出。
#include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6 pid_t ret = fork();
7 if(ret>0)
8 {
9 while(1)
10 {
11 printf("pid:%d , ppid:%d , ret:%d\n",getpid(),getppid(),ret);
12 sleep(1);
13 }
14 }
15
16 else if(ret==0)
17 {
18 int cout=0;
19 while(cout<5)
20 {
21 printf("pid:%d , ppid:%d , ret: %d\n",getpid(),getppid(),ret);
22 cout++;
23 sleep(2);
24 }
25 exit(0);
26 }
27 else{
28 printf("fork err");
29
30 }
31 sleep(1);
32 return 0;
33 }
编写一个脚本进行观察,发现子进程出现僵尸状态。
while :; do ps axj | head -1 && ps axj | grep proc| grep -v grep; sleep 1 ; echo "#############"; done
3.1 为什么要有僵尸状态?
保持进程基本退出信息,方便父进程读取,获得退出原因。
- 有什么特征?
一般的话,子进程处于僵尸状态,task_struct是会被保留,进程的退出信息,是放在进程控制块的。
3.2 僵尸进程的危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
- 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 该进程的资源没有办法回收就会造成内存泄漏
4.孤儿进程
父进程先退出,子进程就称之为“孤儿进程”
孤儿进程被1号init进程领养,当然要有init进程回收喽。
所以os考虑了这种情况的发生,在父进程退出,子进程还在运行(子进程被称为孤儿进程)的时候重新给他找一个父亲,如图所示就是1号进程systemd。