ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Linux的进程控制块:task_struct结构体

Linux的进程控制块:task_struct结构体 一标示符描述本进程的唯一标示符用来区别其他进程。通过系统调用获取进程标示符进程标示符PID本质是task_struct结构体中的一个整型变量。父进程标示符PPIDLinux系统中新的进程往往是通过父进程的方式创建出来的。怎么获取当前程序的标示符呢#includestdio.h #includesys/types.h #includeunistd.h int main() { while(1) { printf(pid%d,ppid%d\n,getpid(),getppid()); sleep(20); } return 0; }打印结果pid29847,ppid7906怎么查看当前程序的标示符有两个办法1命令行直接输入ps axj | head -1 ps axj | grep testtest是当前程序名ps axj | head -1 ps axj | grep 29847可以看出当前进程的父进程是7906,7906的父进程是7905上图的7905就是bash命令自己的程序执行的命令lspwd...都是将程序名命令交给bash命令行解释器创建子进程并且bash本身也是一个进程。2命令行输入ll /proc/2984729847是当前程序的PID这里面主要看这两个信息cwd和execwd当期进程所在的工作路径。exe当前进程程序名的路径。如何修改当期进程所在的工作路径intchdir(const char* path)更改当前进程所在的工作路径。可以看到cwd显示的工作路径已经变了!怎么创建进程通过系统调用创建进程-forkpid_t fork(void)RETURN VALUE返回值On success, the PID of the child process is returned in the parent, and 0 is returned in the child.On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.成功时父进程会返回子进程的进程 IDPID而子进程会返回 0。 失败时父进程会返回 -1不会创建子进程并会相应地设置错误号errno。#includestdio.h #includesys/types.h #includeunistd.h int main() { printf(pid%d,ppid%d\n,getpid(),getppid()); fork(); printf(fork之后pid%d,ppid%d\n,getpid(),getppid()); return 0; }打印结果根据打印结果可以看到在fork之后printf打印了两次。为什么会这样呢通过对fork的返回值分析可以得出结论当父进程bash返回创建的子进程的PID是当前程序的进程返回0就是创建的子进程返回-1创建进程失败。所以fork之后通常要用if进行分流。#includestdio.h #includesys/types.h #includeunistd.h int main() { printf(pid%d,ppid%d\n,getpid(),getppid()); pid_t retfork(); if(ret -1) { perror(fail!);//创建进程失败 return 1; } else if(ret 0) { //子进程 while(1) { printf(我是一个子进程pid%d,ppid%d\n,getpid(),getppid()); sleep(5); } } else { //ret0,父进程 while(1) { printf(我是一个父进程pid%d,ppid%d\n,getpid(),getppid()); sleep(5); } } return 0; }fork为什么会有两个返回值两个返回值可以用来进行父子分流。本质是父子各自执行了return语句。一个进程内核数据结构程序的代码和数据。那么新创建的子进程的代码和数据是什么呢?新创建的子进程是以父进程当前程序的进程为模版创建的。新创建的子进程本质是task_struct内的一个指针默认指向父进程的代码和数据。默认情况下fork之后代码和数据一般是父子共享的。为什么给子进程返回0给父进程返回新创建的子进程的PID是为了标识指定的一个子进程未来可以控制特定的子进程。一个变量ret是怎么让 if 和 else 同时成立的这个问题先简单说一下后面再具体说。二进程状态Linux2.6.18版本内核源码对于进程状态的描述在 / fs / proc / array.c 目录下131行。/* * 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 *task_state_array[] { R (running), /* 0 */ S (sleeping), /* 1 */ D (disk sleep), /* 2 */ T (stopped), /* 4 */ T (tracing stop), /* 8 */ Z (zombie), /* 16 */ X (dead) /* 32 */ };2.1R (running)运行状态并不意味着进程一定在运行中它表明进程要么是在运行中要么在调度队列中。凡是在这个调度队列中的进程状态都是运行状态。写一个死循环就可以看到R状态。R后面的等会儿再说。2.2S (sleeping)睡眠状态和D (disk sleep)磁盘休眠状态这两个统称为阻塞状态。1S (sleeping)睡眠状态意味着进程在等待事件完成这里的睡眠有时候也叫做可中断睡眠浅度睡眠。使用scanf一直不输入一个数据就可以演示出睡眠状态。使用命令 kill -9 进程PID可中断睡眠这个进程可以被信号干掉。2D (disk sleep)磁盘休眠状态有时候也叫做不可中断睡眠状态深度睡眠不能被信号OS干掉在这个状态的进程通常会等待IO的结束。一般看不到这个状态。运行挂起状态和阻塞挂起状态1阻塞挂起状态挂起task_struct在内存因为内存资源不足导致进程对应的数据和代码被交换到swap分区磁盘中的一个区域。内存--磁盘swap out换出磁盘--内存swap in换入。阻塞挂起状态场景内存不足时系统优先将阻塞状态的代码和数据换出到swap分区。2运行挂起状态就绪挂起状态内存不足时将调度队列中低优先级的进程的代码和数据换出到swap分区。运行与阻塞的本质是看task_struct在谁提供的队列中。调度队列和等待队列。2.3T (stopped)暂停状态先解释一下进程状态后面的 。后面带 就是前台进程不带 是后台进程。直接运行程序默认是前台进程在./程序名后面加一个符号就会变成后台进程。S后面的没有了变成了后台进程。后台进程无法被Ctrlc终止掉。发送信号才能终止掉kill -9 程序PID。怎么让进程处于暂停状态可以通过发送SIGSTOP-19信号停止进程。被暂停的进程可以通过发送SIGCONT-18信号让进程继续运行。前面我说过前后台进程当后台进程的程序需要从键盘获取输入时该进程就被直接暂停了。因为前台进程可以从键盘获取输入后台进程不能从键盘获取输入。暂停状态和睡眠状态区别做对的事情是睡眠状态做错的事情被暂停了是暂停状态。2.4t (tracing stop)追踪暂停状态使用gdb调试时可以看到 t 状态。2.5X (dead)死亡状态和Z (zombie)僵尸状态创建一个进程是为了让这个进程帮助我们完成任务当这个进程死亡时我们必须要知道这个进程任务完成的怎么样了所以一个进程死亡要先处于Z状态OS不能释放必须读取完这个进程退出的相关信息才会变成X状态此时才能干掉这个进程。当一个进程终止的时候如果不对它进行回收动作一般是这个进程的父进程进行回收终止的进程就会处于僵尸状态。#includestdio.h #includesys/types.h #includeunistd.h int main() { printf(我是一个进程:pid%d,ppid%d\n,getpid(),getppid()); pid_t ret fork(); if(ret 0) { //父进程 while(1) { printf(我是一个父进程(fork):pid%d,ppid%d\n,getpid(),getppid()); sleep(30); } } else if(ret 0) { //子进程 while(1) { printf(我是一个子进程(fork):pid%d,ppid%d\n,getpid(),getppid()); sleep(30); } } else { //创建进程失败 perror(fail!); return 1; } return 0; }使用这一段代码可以演示出僵尸状态。这段程序在运行时使用信号-9干掉创建的子进程此时父进程不会对这个子进程进行回收这个子进程就会处于僵尸状态。2.6孤儿进程父进程如果提前退出子进程还没有退出那么这个子进程就被称为孤儿进程因为它的父进程比自己先退出。这个孤儿进程就会被1号init / systemd进程领养。演示孤儿进程#includestdio.h #includesys/types.h #includeunistd.h int main() { printf(我是一个进程:pid%d,ppid%d\n,getpid(),getppid()); pid_t ret fork(); if(ret 0) { //父进程 int cut5; while(cut--) { printf(我是一个父进程(fork):pid%d,ppid%d\n,getpid(),getppid()); sleep(1); } } else if(ret 0) { //子进程 while(1) { printf(我是一个子进程(fork):pid%d,ppid%d\n,getpid(),getppid()); sleep(1); } } else { //创建进程失败 perror(fail!); return 1; } return 0; }父进程循环执行五次之后就会退出父进程退出不会变成僵尸进程是因为父进程被bash进程回收了子进程就会变成孤儿进程和后台进程并被1号进程领养1号进程就是子进程的父进程子进程就会继续执行循环代码打印由于这个子进程变成了后台进程所以不能使用Ctrlc终止掉发送信号-9才能终止这个子进程终止之后这个子进程也不会变成僵尸进程它会被它的父进程1号进程回收掉。开始执行我是一个进程:pid20803,ppid10383 我是一个父进程(fork):pid20803,ppid10383 我是一个子进程(fork):pid20804,ppid20803父进程退出之后我是一个子进程(fork):pid20804,ppid1关于怎么区分前后台进程谁能从键盘获取数据输入谁就是前台 -- 由于键盘只有一个所以在获取输入的时候只能有一个进程获取键盘数据输入 -- 前台进程任何时刻只能有一个后台进程可以有多个。三四个问题1进程退出了退出信息是什么main函数的返回值或者程序在运行中收到的信号值。2进程退出了退出信息保存在哪里保存在进程自己的task_struct结构体里。路径/ include / linux / sched.h 第814和815行。2.6.18main函数的返回值或者程序在运行中收到的信号值都会保存在上面的整型变量里面。3检测Z状态进程回收Z状态进程本质是在做什么本质是在检查task_struct里面这部分的数据获取完之后让task_struct处于X状态然后释放。僵尸状态Z只保留进程的task_struct未来让父进程或者OS帮我们获取到子进程的退出数据。4具体怎么回收谁来回收父进程通过系统调用回收OS。如果一直不回收子进程呢就会永远处于Z状态task_struct就不会被释放也就会占据内存空间就会造成内存泄漏所以子进程必须回收。一个进程退出了就不会存在内存泄漏的问题了。但是大部分软件都是常驻进程一直在内存中的进程如果一直内存泄漏这个问题就严重了所以动态申请的空间必须要释放。
返回列表