#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <assert.h> #include <signal.h> #include <sys/wait.h> extern char* *environ; int showenv() { char **p = environ; while(*p){ fprintf(stderr, "%s\n", *p); p++; } } //函数fork创建新进程, 函数exec执行新程序, 函数sleep休眠进程, 函数wait同步进程和函数exit结束进程. int testfork() { pid_t pid; if((pid = fork()) == 0){ fprintf(stderr, "---- begin ----\n"); sleep(10); // 睡眠3秒会导致子进程成为僵死进程 execl("/bin/uname", "uname", "-a", 0); fprintf(stderr, "---- end ----\n"); } else if(pid > 0) fprintf(stderr, "fork child pid = [%d]\n", pid); else fprintf(stderr, "Fork failed.\n"); return 0; } //exec家族命名规律: 字母 l 表示变长命令行参数: 字母 v 表示函数采用指针数组命令行参数: 字母 P表示函数采用 PATH 变量查找程序: 字母 e 表示函数使用 envp 显示传递环境变。 //最终是调用execve系统调用 int testexec() { //执行 ls -l /root //extern char **environ; char *argv[] = {"-l","/root",0} ; execl("/bin/ls","-l","/root",0) ; //exec后 后面的代码都不会执行 execle("/bin/ls"," -l","/root",0,environ) ; execlp("ls", "-l", "/root",0) ; execv("/bin/ls", argv); execve ("/bin/ls" , argv, environ) ; execvp("ls" , argv) ; } /*vfork比起fork函数更快, 二者的区别如下: a) vfork创建的子进程并不复制父进程的数据, 在随后的exec调用中系统会复制新程序的数据到内存, 继而避免了一次数据复制过程 b) 父进程以vfork方式创建子进程后将被阻塞, 知道子进程退出或执行exec调用后才能继续运行. 当子进程只用来执行新程序时, vfork-exec模型比fork-exec模型具有更高的效率, 这种方法也是Shell创建新进程的方式. 函数system会阻塞调用它的进程, 并执行字符串string中的shell命令.他和vfork-exec模型是一样的 */ int testvfork() { pid_t pid; if((pid = vfork()) == 0){ fprintf(stderr, "---- begin ----\n"); sleep(3); execl("/bin/uname", "uname", "-a", 0); fprintf(stderr, "---- end ----\n"); } else if(pid > 0) fprintf(stderr, "fork child pid = [%d]\n", pid); else fprintf(stderr, "Fork failed.\n"); return 0; } //僵尸进程 //查看僵尸进程: //ps -ef | grep 13707(父进程id) 其中, 'defunct'代表僵死进程. /** 预防僵死进程: (1) wait法 父进程主动调用wait接收子进程的死亡报告, 释放子进程占用的系统进程表资源. (2) 托管法 如果父进程先于子进程而死亡, 则它的所有子进程转由进程init领养, 即它所有子进程的父进程ID号变为1. 当子进程结束时init为其释放进程表资源. (3) 忽略SIGC(H)LD信号 当父进程忽略SIGC(H)LD信号后, 即使不执行wait, 子进程结束时也不会产生僵死进程. (4) 捕获SIGC(H)LD信号 当父进程捕获SIGC(H)LD信号, 并在捕获函数代码中等待(wait)子进程 */ int testszobm() { pid_t pid; if((pid = fork()) == 0){ printf("child[%d]\n", getpid()); exit(0); } // wait(); printf("parent[%d]\n", getpid()); sleep(10);//父进程不退出,不处理子进程的信号。子进程会成为僵尸进程。 return 0; } void ClearChild(int nSignal){ pid_t pid; int nState; // WNOHANG非阻塞调用waitpid, 防止子进程成为僵死进程 while((pid = waitpid(-1, &nState, WNOHANG)) > 0); //程序在接收到SIGCLD信号后立即执行函数ClearChild, 并调用非阻塞的waitpid函数结束子进程结束信息, 如果结束到子进程结束信息则释放该子进程占用的进程表资源, //否则函数立刻返回. 这样既保证了不增加守护进程负担, 又成功地预防了僵死进程的产生. signal(SIGCLD, ClearChild); // 重新绑定 SIGCLD信号 } int InitServer(){ pid_t pid; assert((pid = fork()) >= 0); // 创建子进程 if(pid != 0){ // 父进程退出, 子进程被init托管 sleep(1); exit(0); } assert(setsid() >= 0); // 子进程脱离终端 umask(0); // 清除文件创建掩码 signal(SIGINT, SIG_IGN); // 忽略SIGINT信号 signal(SIGCLD, ClearChild); // 处理SIGCLD信号,预防子进程僵死 return 0; } //守护进程是一个在后台长期运行的进程, 它们独立于控制终端, 周期性地执行某项任务, 或者阻塞直到事件发生, 默默地守护着计算机系统的正常运行. int testdamo() { InitServer(); sleep(100); } int main(int argc, char *argv[]) { int n; showenv(); //testfork(); //testexec(); //testvfork(); //testszobm(); testdamo(); scanf("%d",&n); printf("%d\n",n); return 0; }