参考 https://www.cnblogs.com/jackl...html
在Linux中task_struct结构体便是PCB。PCB是进程的惟一标识,PCB由链表实现(为了动态插入和删除)。
进程建立时,为该进程生成一个PCB;进程终止时,回收PCB。
PCB包含信息:一、进程状态(state);二、进程标识信息(uid、gid);三、定时器(time);四、用户可见寄存器、控制状态寄存器、栈指针等(tss)
每一个进程都有一个非负的惟一进程ID(PID)。虽然是惟一的,可是PID能够重用,当一个进程终止后,其余进程就可使用它的PID了。
PID为0的进程为调度进程,该进程是内核的一部分,也称为系统进程;PID为1的进程为init进程,它是一个普通的用户进程,可是以超级用户特权运行;PID为2的进程是页守护进程,负责支持虚拟存储系统的分页操做。除了PID,每一个进程还有一些其余的标识符:node
#if defined __USE_XOPEN_EXTENDED || defined __USE_XOPEN2K8 /* Return the session ID of the given process. */ extern __pid_t getsid (__pid_t __pid) __THROW; #endif /* Get the real user ID of the calling process. */ extern __uid_t getuid (void) __THROW; /* Get the effective user ID of the calling process. */ extern __uid_t geteuid (void) __THROW; /* Get the real group ID of the calling process. */ extern __gid_t getgid (void) __THROW; /* Get the effective group ID of the calling process. */ extern __gid_t getegid (void) __THROW;
五种进程之间转换关系如图:linux
每一个进程的task_struct和系统空间堆栈存放位置以下:两个连续的物理页【《Linux内核源代码情景分析》271页】c++
系统堆栈空间不能动态扩展,在设计内核、驱动程序时要避免函数嵌套太深,同时不宜使用太大太多的局部变量,由于局部变量都是存在堆栈中的。程序员
新进程的建立,首先在内存中为新进程建立一个task_struct结构,而后将父进程的task_struct内容复制其中,再修改部分数据。分配新的内核堆栈、新的PID、再将task_struct 这个node添加到链表中。所谓建立,其实是“复制”。算法
子进程刚开始,内核并无为它分配物理内存,而是以只读的方式共享父进程内存,只有当子进程写时,才复制。即“copy-on-write”。
fork都是由do_fork实现的,do_fork的简化流程以下图:shell
#include<unistd.h> pid_t fork(void) //子进程返回0,父进程返回子进程ID,出错返回-1.
fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。子进程中返回值为0,父进程中返回值为子进程的PID。程序员能够根据返回值的不一样让父进程和子进程执行不一样的代码。
一个形象的过程:安全
执行下面程序:bash
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; char *message; int n = 0; pid = fork(); while(1){ if(pid < 0){ perror("fork failed\n"); exit(1); } else if(pid == 0){ n--; printf("child's n is:%d\n",n); } else{ n++; printf("parent's n is:%d\n",n); } sleep(1); } exit(0);
运行结果:服务器
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./a.out parent's n is:1 child's n is:-1 parent's n is:2 child's n is:-2 parent's n is:3 child's n is:-3
能够发现子进程和父进程之间并无对各自的变量产生影响。
通常来讲,fork以后父、子进程执行顺序是不肯定的,这取决于内核调度算法。进程之间实现同步须要进行进程通讯。
一个父进程但愿子进程同时执行不一样的代码段,这在网络服务器中常见——父进程等待客户端的服务请求,当请求到达时,父进程调用fork,使子进程处理此请求。
一个进程要执行一个不一样的程序,通常fork以后当即调用exec
vfork与fork对比:
相同:
返回值相同
不一样:
fork建立子进程,把父进程数据空间、堆和栈复制一份;vfork建立子进程,与父进程内存数据共享;
vfork先保证子进程先执行,当子进程调用exit()或者exec后,父进程才往下执行
为何须要vfork?
由于用vfork时,通常都是紧接着调用exec,因此不会访问父进程数据空间,也就不须要在把数据复制上花费时间了,所以vfork就是”为了exec而生“的。
运行这样一段演示程序:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { pid_t pid; char *message; int n = 0; int i; pid = vfork(); for(i = 0; i < 10; i++){ if(pid < 0){ perror("fork failed\n"); exit(1); } else if(pid == 0){ n--; printf("child's n is:%d\n",n); if(i == 1) _exit(0); //return 0; //exit(0); } else{ n++; printf("parent's n is:%d\n",n); } sleep(1); } exit(0); }
执行结果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./a.out child's n is:-1 child's n is:-2 parent's n is:-1 parent's n is:0 parent's n is:1 parent's n is:2 parent's n is:3 parent's n is:4
能够发现子进程先被执行,exit后,父进程才被执行,同时子进程改变了父进程中的数据
子进程return 0 会发生什么?
运行结果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./a.out child's n is:-1 child's n is:-2 parent's n is:32767 parent's n is:32768 parent's n is:32769 parent's n is:32770 parent's n is:32771 parent's n is:32772
从上面咱们知道,结束子进程的调用是exit()而不是return,若是你在vfork中return了,那么,这就意味main()函数return了,注意由于函数栈父子进程共享,因此整个程序的栈就跪了。 若是你在子进程中return,那么基本是下面的过程: 1)子进程的main() 函数 return了,因而程序的函数栈发生了变化。 2)而main()函数return后,一般会调用 exit()或类似的函数(如:_exit(),exitgroup()) 3)这时,父进程收到子进程exit(),开始从vfork返回,可是尼玛,老子的栈都被你子进程给return干废掉了,你让我怎么执行?(注:栈会返回一个诡异一个栈地址,对于某些内核版本的实现,直接报“栈错误”就给跪了,然而,对于某些内核版本的实现,因而有可能会再次调用main(),因而进入了一个无限循环的结果,直到vfork 调用返回 error) 好了,如今再回到 return 和 exit,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。若是你用c++ 你就知道,return会调用局部对象的析构函数,exit不会。(注:exit不是系统调用,是glibc对系统调用 _exit()或_exitgroup()的封装) 可见,子进程调用exit() 没有修改函数栈,因此,父进程得以顺利执行。
【《vfork挂掉的一个问题》http://coolshell.cn/articles/...】
可执行文件装入内核的linux_binprm结构体。
进程调用exec时,该进程执行的程序彻底被替换,新的程序从main函数开始执行。由于调用exec并不建立新进程,只是替换了当前进程的代码区、数据区、堆和栈。
六种不一样的exec函数:
当指定filename做为参数时:
若是filename中包含/,则将其视为路径名。
不然,就按系统的PATH环境变量,在它所指定的各个目录中搜索可执行文件。
*出于安全方面的考虑,有些人要求在搜索路径中不要包括当前目录。
在这6个函数中,只有execve是内核的系统调用。另外5个只是库函数,他们最终都要调用该系统调用,以下图所示:
execve的实现由do_execve完成,简化的实现过程以下图:
运行这样一段演示程序:
#include <errno.h> #include <stdio.h> #include <stdlib.h> char command[256]; void main() { int rtn; /*child process return value*/ while(1) { printf( ">" ); fgets( command, 256, stdin ); command[strlen(command)-1] = 0; if ( fork() == 0 ) { execlp( command, NULL ); perror( command ); exit( errno ); } else { wait ( &rtn ); printf( " child process return %d\n", rtn ); } } }
a.out为打印hello,world的执行文件
运行结果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./test >./a.out hello,world child process return 0
从main返回,等效于调用exit
调用exit
exit 首先调用各终止处理程序,而后按需屡次调用fclose,关闭全部的打开流。
调用_exit或者_Exit
最后一个线程从其启动例程返回
最后一线程调用pthread_exit
调用abort
接到一个信号并终止
最后一个线程对取消请求做出响应
wait用于使父进程阻塞,等待子进程退出;waitpid有若干选项,如能够提供一个非阻塞版本的wait,也能实现和wait相同的功能,实际上,linux中wait的实现也是经过调用waitpid实现的。
waitpid返回值:正常返回子进程号;使用WNOHANG且没有子进程退出返回0;调用出错返回-1;
运行以下演示程序
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pid0,pid1; pid0 = fork(); if(pid0 < 0){ perror("fork"); exit(1); } else if(pid0 == 0){ sleep(5); exit(0);//child } else{ do{ pid1 = waitpid(pid0,NULL,WNOHANG); if(pid1 == 0){ printf("the child process has not exited.\n"); sleep(1); } }while(pid1 == 0); if(pid1 == pid0){ printf("get child pid:%d",pid1); exit(0); } else{ exit(1); } } return 0; } 当把第三个参数WNOHANG改成0时,就不会有上面五个显示语句了,说明父进程阻塞了。 a.out 的代码以下: #include <stdio.h> void main() { printf("hello WYJ\n"); } process.c的代码以下: #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <sys/times.h> #include <sys/wait.h> int main() { pid_t pid_1,pid_2,pid_wait; pid_1 = fork(); pid_2 = fork(); if(pid_1 < 0){ perror("fork1 failed\n"); exit(1); }else if(pid_1 == 0 && pid_2 != 0){//do not allow child 2 to excute this process. if(execlp("./a.out", NULL) < 0){ perror("exec failed\n"); }//child; exit(0); } if(pid_2 < 0){ perror("fork2 failded\n"); exit(1); }else if(pid_2 == 0){ sleep(10); } if(pid_2 > 0){//parent do{ pid_wait = waitpid(pid_2, NULL, WNOHANG);//no hang sleep(2); printf("child 2 has not exited\n"); }while(pid_wait == 0); if(pid_wait == pid_2){ printf("child 2 has exited\n"); exit(0); }else{ // printf("pid_2:%d\n",pid_2); perror("waitpid error\n"); exit(1); } } exit(0); }
运行结果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./proess hello,world child 2 has not exited child 2 has not exited child 2 has not exited child 2 has not exited child 2 has not exited child 2 has not exited child 2 has exited
WNOHANG 改成0运行结果:
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ./proess hello,world child 2 has not exited child 2 has exited
编写一个多进程程序:该实验有 3 个进程,其中一个为父进程,其他两个是该父进程建立的子进程,其中一个子进程运行“ls -l”指令,另外一个子进程在暂停 5s 以后异常退出,父进程并不阻塞本身,并等待子进程的退出信息,待收集到该信息,父进程就返回。
#include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t child1,child2,child; if((child1 = fork()) < 0){ perror("failed in fork 1"); exit(1); } if((child2 = fork()) < 0){ perror("failed in fork 2"); exit(1); } if(child1 == 0){ //run ls -l if(child2 == 0){ printf("in grandson\n"); } else if(execlp("ls", "ls", "-l", NULL) < 0){ perror("child1 execlp"); } } else if(child2 == 0){ sleep(5); exit(0); } else{ do{ sleep(1); printf("child2 not exits\n"); child = waitpid(child2, NULL, WNOHANG); }while(child == 0); if(child == child2){ printf("get child2\n"); } else{ printf("Error occured\n"); } } }
运行结果:
在unix/linux中,正常状况下,子进程是经过父进程建立的,子进程在建立新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远没法预测子进程 到底何时结束。 当一个 进程完成它的工做终止以后,它的父进程须要调用wait()或者waitpid()系统调用取得子进程的终止状态。
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工做。
在进程调用了exit以后,该进程并不是立刻就消失掉,而是留下了一个成为僵尸进程的数据结构,记载该进程的退出状态等信息供其余进程收集,除此以外,僵尸进程再也不占有任何内存空间。
一个进程使用fork建立子进程,若是子进程退出,而父进程并无调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
子进程结束以后为何会进入僵尸状态? 由于父进程可能会取得子进程的退出状态信息。
如何查看僵尸进程?
linux中命令ps,标记为Z的进程就是僵尸进程。
执行下面一段程序:
#include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { pid_t pid; pid = fork(); if (pid < 0) { printf("error occurred\n"); } else if (pid == 0) { exit(0); } else { sleep(60); wait(null); } }
运行结果:
ps -ef|grep defunc能够找出僵尸进程
ps -l 能够获得更详细的进程信息
root@iZbp1anc6yju2dks3nw5j0Z:~/test# ps PID TTY TIME CMD 6701 pts/8 00:00:00 bash 9756 pts/8 00:00:00 ps
其中S表示状态:
O:进程正在处理器运行
S:休眠状态
R:等待运行
I:空闲状态
Z:僵尸状态
T:跟踪状态
B:进程正在等待更多的内存分页
C:cpu利用率的估算值
收集僵尸进程的信息,并终结这些僵尸进程,须要咱们在父进程中使用waitpid和wait,这两个函数可以手机僵尸进程留下的信息并使进程完全消失。
是linux的后台服务进程。它是一个生存周期较长的进程,没有控制终端,输出无处显示。用户层守护进程的父进程是init进程。
守护进程建立步骤:
一、建立子进程,父进程退出,子进程被init自动收养;fork exit
二、调用setsid建立新会话,成为新会话的首进程,成为新进程组的组长进程,摆脱父进程继承过来的会话、进程组等;setsid
三、改变当前目录为根目录,保证工做的文件目录不被删除;chdir(“/”)
四、重设文件权限掩码,给子进程更大的权限;umask(0)
五、关闭不用的文件描述符,由于会消耗资源;close
#include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<sys/types.h> #define MAXFILE 65535 int main() { int fd, len, i; pid_t pid; char* buf = "tick\n"; len = strlen(buf); if ((pid = fork()) < 0) { perror("fork failed"); exit(1); } else if (pid > 0) { exit(0); } setsid(); if (chdir("/") < 0) { perror("chdir failed"); exit(1); } umask(0); for (i = 0; i < MAXFILE; i++) { close(i); } while (1) { if ((fd = open("/tmp/dameon.log", O_CREAT | O_WRONLY | O_APPEND, 0600)) < 0) { perror("open log failed"); exit(1); } write(fd, buf, len + 1); close(fd); sleep(10); } }
#include<stdio.h> #include<string.h> #include<fcntl.h> #include<unistd.h> #include<sys/types.h> #include<syslog.h> #define MAXFILE 65535 int main() { int fd,len,i; pid_t pid,child; char *buf = "tick\n"; len = strlen(buf); if((pid = fork()) < 0){ perror("fork failed"); exit(1); } else if(pid > 0){ exit(0); } openlog("Jack", LOG_PID, LOG_DAEMON); if(setsid() < 0){ syslog(LOG_ERR, "%s\n", "setsid"); exit(1); } if(chdir("/") < 0){ syslog(LOG_ERR, "%s\n", "chdir"); exit(1); } umask(0); for(i = 0; i < MAXFILE; i++){ close(i); } if((child = fork()) < 0){ syslog(LOG_ERR, "%s\n", "fork"); exit(1); } if(child == 0){ //printf("in child\n");//can not use terminal from now on. syslog(LOG_INFO, "in child"); sleep(10); exit(0); } else{ waitpid(child, NULL, 0); //printf("child exits\n");//can not use terminal from now on. syslog(LOG_INFO, "child exits"); closelog(); while(1){ sleep(10); } } }
真正编写调试的时候会发现须要杀死守护进程。如何杀死守护进程?ps -aux 找到对应PIDkill -9 PID