1.PCB及task_struct
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。书上称之为 PCB ( process control block ), Linux 操作系统下的 PCB 是 : task_struct。
task_struct和PCB之间的关系,打个比方:就如同王婆和媒婆一样,王婆是媒婆的一种,媒婆是对各种说媒的婆的统称。
标示符 : 描述本进程的唯一标示符,用来区别其他进程。状态 : 任务状态,退出代码,退出信号等。优先级 : 相对于其他进程的优先级。程序计数器 : 程序中即将被执行的下一条指令的地址。内存指针 : 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针上下文数据 : 进程执行时处理器的寄存器中的数据 [ 休学例子,要加图 CPU ,寄存器 ] 。I / O 状态信息 : 包括显示的 I/O 请求 , 分配给进程的 I / O 设备和被进程使用的文件列表。记账信息 : 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。其他信息
如何理解进程
进程=对应的代码和数据+对应的task_struct结构
1.如何查看进程
进程的信息可以通过 /proc 系统文件夹查看
PS命令:
PID和PPID可通过系统调用来获取
可通过fork来创建子进程
fork的使用:(一般要进行if分流)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();if(ret < 0){perror("fork");return 1;}else if(ret == 0){ //childprintf("I am child : %d!, ret: %d\n", getpid(), ret);}else{ //fatherprintf("I am father : %d!, ret: %d\n", getpid(), ret);}sleep(1);return 0;
}
2.进程状态
我个人对进程状态的理解:
运行:task_struct 结构体在运行队列中排队
阻塞:等待非CPU资源就绪
挂起:当内存不足的时候,OS通过适当地调整进程的代码和数据到磁盘,此时进程被OS展示转移到磁盘时的状态,就是挂起状态
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运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。S睡眠状态(sleeping) : 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠( interruptible sleep ))。D磁盘休眠状态(Disk sleep) 有时候也叫不可中断睡眠状态( uninterruptible sleep ),在这个状态的进程通常会等待IO 的结束。T停止状态(stopped) : 可以通过发送 SIGSTOP 信号给进程来停止( T )进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。X死亡状态(dead) :这个状态只是一个返回状态,你不会在任务列表里看到这个状态Z僵尸状态(zombie)
认识进程状态
上面的STAT列就是进程状态,R代表它是运行进程,R后面的+号代表它是前台进程
观察不难看出来,上面./myproc进程的状态变成了R,没有了+号,此时它是变成了后台进程。
如何理解Z僵尸状态(zombie)
当父进程通过fork来创建子进程后,当子进程退出后,但是还没有被父进程读取,此时子进程就是僵尸进程。
下面通过代码来进程实验:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{pid_t id = fork();if (id < 0){perror("fork");return 1;}else if (id > 0){ // parentprintf("parent[%d] is sleeping...\n", getpid());sleep(30);}else{printf("child[%d] is begin Z...\n", getpid());sleep(5);exit(1);}return 0;
}
结果为:(通过while :; do ps aux | head -1 && ps aux | grep myproc; sleep 1; echo "###########################" ;done;来查看)
僵尸进程的危害
- 资源占用:虽然僵尸进程本身不占用CPU时间,并且它们所占用的内存是微乎其微的,但是它们会保持进程ID。由于Linux系统对进程数有严格的限制,当僵尸进程数量达到一定数目时,会导致系统中无法再产生新的进程,因为新的进程无法使用已经被僵尸进程占用的进程ID。
- 系统性能影响:如果系统中存在大量的僵尸进程,会占用一部分系统资源,这可能会影响到服务器的性能,尤其是在高并发或资源密集型的应用场景中。
- 系统稳定性问题:僵尸进程的存在可能引发一些潜在的系统稳定性问题。例如,如果父进程因为某种原因(如崩溃或挂起)未能正确清理其僵尸子进程,这些僵尸进程可能会在系统中长时间存在,对系统稳定性构成威胁。
如何避免僵尸进程的产生
父进程应该通过调用
wait
或waitpid
等函数来等待子进程的结束,并获取其退出状态信息。这样,子进程在退出后,其相关资源可以被系统及时释放,避免形成僵尸进程。同时,对于可能产生大量子进程的应用,也需要注意合理管理子进程的生命周期,避免因为子进程管理不当而导致僵尸进程的产生。
如何理解孤儿进程
当父进程通过fork来创建子进程后,但是父进程先于子进程退出,此时子进程就是孤儿进程,不过此时该进程会被1号 init 进程所领养。
下面通过代码来进程实验:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int main()
{pid_t id = fork();if (id < 0){perror("fork");return 1;}else if (id == 0){ // childprintf("I am child, pid : %d\n", getpid());sleep(10);}else{ // parentprintf("I am parent, pid: %d\n", getpid());sleep(3);exit(0);}return 0;
}
结果为:通过( while :; do ps ajx | head -1 && ps ajx | grep myproc; sleep 1; echo "###########################" ;done;)来查看
通过上图,我们看到子进程(孤儿进程)被1号 Init 进程领养了。