早期两个unix版本分支systemⅤ、BSD1.代码规范ANSI标准C2.系统级别的标准IEEEPOSIX标准X/OPEN :SUS规范 后面IEEE和X/OPEN 联合了都叫POSIXGNU项目推动系统开源的运动但缺少内核。Linux内核林纳斯·托瓦丝Linux ·Torvalds内核提供大量系统调用GNU C Libraryglibc C标准库遵循POSIXAPI:shell一部分是系统调用一部分封装到库函数glibc里1.进程与程序-elf列出所有进程 grep是筛选shell、bash是一个应用标准实用程序不是系统调用也不是库。2.进程创建fork创建进程fork();运行之后。成功产生子进程fork之后的内容父子进程的环境一样父进程环境里fork的返回值是子进程号子进程环境里fork的返回值是0。fork之后的代码父子进程都会运行。失败产生子进程fork在父进程里的返回值是-1没有子进程。注意从fork那一行开始子进程产生并且从那一行开始父子进程同时运行所以不是父进程运行后再运行子进程父进程卡住不影响子进程。#include sys/types.h #include unistd.h #include stdio.h int main () { pid_t pid fork(); if(pid 0){ perror(fork); return -1; }else if(pid 0){ printf(父进程pid %d\n,pid); }else if(pid 0){ printf(子进程pid %d\n,pid); } printf(Hello World!\n); return 0; }终端操作./a.out 然后ctrl z 终止。 fg继续前台运行 bg继续后台运行bash如何fork a.out输入./a.out 终端把./a.out 交给bashbash fork 一个bash的子进程然后用exec把bash的子进程替换成a.out运行。之后如果a.out是前台运行bash睡眠 在a.out结束时运行wait获取子进程结束的信息。如果是后台运行bash和a.out同时运行。多次fork带来的问题#include sys/types.h #include unistd.h #include stdio.h int main(int argc, const char *argv[]) { pid_t pid; for(int i 0; i 4; i) { pid fork(); if(pid 0) { perror(fork); return -1; } else if( pid 0) { printf(child process\n); } else { printf(parent process\n); } } int t 100; while(t--) sleep(1); return 0; }一些进程操作命令-elf列出所有进程 grep是筛选进程快照ps -elf|grep ./程序名进程树pstree -p 进程号杀死进程kill -9 进程号创建进程链单传。如果是父进程跳出循环。#include sys/types.h #include unistd.h #include stdio.h int main(int argc, const char *argv[]) { pid_t pid; for(int i 0; i 4; i) { pid fork(); if(pid 0) { perror(fork); return -1; } else if( pid 0) { printf(child process\n); } else { printf(parent process\n); break } } int t 100; while(t--) sleep(1); return 0; }创建多个子进程子进程不fork#include sys/types.h #include unistd.h #include stdio.h int main(int argc, const char *argv[]) { pid_t pid; for(int i 0; i 4; i) { pid fork(); if(pid 0) { perror(fork); return -1; } else if( pid 0) { printf(child process\n); break } else { printf(parent process\n); } } int t 100; while(t--) sleep(1); return 0; }return和exit函数echo $? 查看上一个运行程序的返回值exit会结束当前进程。并刷新缓冲区(立即执行写入)。return会结束包含它的当前函数不会影响上层函数。ctrl z 暂停、挂起进程 ctrl c 终止进程_exit系统调用函数exit是库函数它调用系统调用的_exit函数_exit是系统调用不会刷新缓冲区立刻结束进程。3.进程的状态ctrl z 和 ctrl c 只能对前台进程操作不能对后台进程操作。可以先fg将它转变成前台进程。./a.out 后台运行 ./a.out 前台运行top -p 进场号 只看一个进程的资源状态R运行态T停止态ctrl zctrl c 会直接杀死进程top里就空了S睡眠态如scanf等待输入Z僵死 提前退出wait函数wstatus是子进程终止时返回的信息pid_t是终止的子进程的进程号。返回值成功返回结束的子进程号失败返回-1bash创建子进程后如果子进程是前台运行bash睡眠阻塞调用wait等待子进程结束返回信息。如果是后台运行bash和a.out同时运行。僵尸进程父进程还在子进程提前退出了。必须是父进程还要持续运行很久子进程提前退出才叫僵尸。例获取进程退出状态正常退出被信号终止#include stdio.h #include stdlib.h #include unistd.h #include sys/wait.h int main(void) { int status; switch( fork() ) { case -1: perror(fork); exit(EXIT_FAILURE); break; case 0: sleep(40); return 1; break; default: if(wait(status) 0){ perror(wait); exit(EXIT_FAILURE); } } if(WIFEXITED(status)){ printf(child exit status:%d\n, WEXITSTATUS(status)); } else if(WIFSIGNALED(status)){ printf(signal:%d \n,WTERMSIG(status)); printf(------\n); } return 0; }举例wait常见问题wait两次第二次没有子进程wait失败等待所有子进程结束#include stdio.h #include stdlib.h #include unistd.h #include sys/wait.h int main(void) { int status; switch( fork() ) { case -1: perror(fork); exit(EXIT_FAILURE); break; case 0: sleep(1); return 10; break; default: } while(wait(status) 0){ if(WIFEXITED(status)){ printf(child exit status:%d\n, WEXITSTATUS(status)); } else if(WIFSIGNALED(status)){ printf(signal:%d \n,WTERMSIG(status)); }else { printf(------\n); } } return 0; }如果不wait父进程结束子进程会变孤儿会被收养。如果父进程没结束子进程结束了wait一个指定子进程#include sys/wait.h #include stdint.h #include stdlib.h #include unistd.h #include stdio.h int main(int argc, char *argv[]) { pid_t cpid fork(); if (cpid -1) { perror(fork); exit(EXIT_FAILURE); } else if (cpid 0) { sleep(1); exit(EXIT_SUCCESS); } else { printf(cpid %d\n, cpid); pid_t wpid waitpid(cpid, NULL, 0); printf(wpid %d\n, wpid); exit(EXIT_SUCCESS); } return 0; }备注wait(wstatus)的调用与waitpid(-1, wstatus, 0)等价。4.system与exec函数族system函数system启动一个sh的shell不是bash然后将输入的shell命令在shell里运行。exec函数族linux下每一个进程都有一套环境变量环境变量有继承的属性。继承进程之前的环境变量#include stdio.h #include stdlib.h #include unistd.h int main() { // 打印环境变量 USER 的初始值 printf(Initial value of USER: %s\n, getenv(USER)); // 设置环境变量 USER 为 britta if (putenv(USERbritta) ! 0) { perror(putenv); exit(EXIT_FAILURE); } // 使用 execl 调用 /usr/bin/printenv 命令打印环境变量 USER 和 SHELL execl(/usr/bin/printenv, printenv, USER, SHELL, (char *)NULL); // 如果 execl 调用失败输出错误信息 perror(execvpe); // 退出程序返回失败状态 exit(EXIT_FAILURE); }putenv(char*string1 char*string2); getenv(char *string1); printenv char*string练习自己创建一个system函数#include stdio.h #include sys/wait.h #include unistd.h int mysystem(const char *command) { //1.fork()创建子进程 switch(fork()) { case -1: perror(fork); return -1; case 0: //2.execl()进行进程替换 execl(/bin/sh, sh, -c, command, (char *) NULL); perror(execl); _exit(1); default: } int wstatus; pid_t wpid; //3.wait()等待子进程结束 wpid wait(wstatus); if(wpid 0) { perror(wait); return wpid; } if( WIFEXITED(wstatus) ) { return WEXITSTATUS(wstatus); } else if ( WIFSIGNALED(wstatus) ) { return WTERMSIG(wstatus); } else { return 0; } return 0; } int main(int argc, const char *argv[]) { mysystem(ls); return 0; }5.守护进程孤儿进程父进程结束了子进程依旧运行着。然后子进程会被init收养父进程变成init守护进程终端下面的首进程bash是会话会话下面是进程组进程组下面有具体进程。前台或后台bash作为会话和终端有绑定关系。setsid命令是干嘛的setsid命令就是用来在一个新的会话中运行一个程序让这个新程序成为一个新的会话进程从而脱离原始终端控制。setsid在子进程里运行所以子进程成为会话进程。1.脱离终端父进程结束了子进程没了继承关系但子进程仍属于bash会话。setsid成立一个会话子进程绑定新会话。彻底与bash的所在的终端断绝联系。2.关闭stdio流3.设置umask4.设置工作路径#include sys/types.h #include unistd.h #include stdio.h #include stdlib.h #include sys/stat.h #include fcntl.h #include time.h int main(int argc, const char *argv[]) { time_t now; const char *daemonfile /tmp/mydaemon.txt; //1.脱离终端 pid_t pid fork(); if(pid -1){ perror(); exit(EXIT_FAILURE); } if(pid 0) exit(EXIT_SUCCESS); if(setsid() -1){ perror(set); exit(EXIT_FAILURE); } //2.关闭stdio流 close(0); close(1); close(2); //open(/dev/null,O_RDONLY); //open(/dev/null,O_RDWR); //open(/dev/null,O_RDWR); //打开黑洞 //3.设置umask umask(0002); open(daemonfile,O_RDONLY|O_CREAT|O_APPEND,0666); open(daemonfile,O_RDWR|O_APPEND); open(daemonfile,O_RDWR|O_APPEND); //4.设置工作路径 if(chdir(/) -1){ perror(chdir); exit(EXIT_FAILURE); } while(1) { time(now); printf(%s,ctime(now)); //printf(stdout test\n); fflush(stdout); sleep(1); //perror(stderr test); } return 0; }6.信号信号是什么1.信号是事件发生时对进程的一种通知机制也叫软件中断信号与硬件中断相同点是都打断了程序执行的正常流程。2.中断进程收到信号暂停去执行信号处理器程序执行完再回来继续运行进程。3.应用层的程序在执行时会不断切换应用层状态和内核状态。一旦触发系统调用就陷入到内核。系统调用结束后再把程序交给用户空间cpu有两种模式用户模式和内核模式4.内核产生信号-》信号被发送进程-》进程响应信号并进行相应操作。信号源于内核信号的产生三种情况硬件异常、特殊终端字符、软件事件硬件异常举例段错误触发了一个信号进程收到这个信号终止了终端特殊字符ctrl c 使内核产生信号信号被发给进程进程软件事件信号无处不在、拖动鼠标、子进程结束通知父进程“我结束了”信号是个整数信号的内容是一个整数内核发送给进程的信号内容是数字进程收到对应数字进行相应操作。信号的响应过程进程的属性PID、umask掩码、文件掩码、信号掩码等属性。信号掩码将信号阻塞信号处于等待状态、挂起直到相应信号掩码被移除。信号的响应也叫信号的处置1.默认行为忽略是进程收到了信号但忽视。跟掩码不一样掩码是让信号不发给进程。2.忽略信号这个是主动的从代码上要求忽略某个信号3.执行信号处理器程序类似中断进程暂停执行信号处理程序进程继续。信号的类型SIGTSTPctrl z 进程停止信号集信号集是信号掩码专有的数据类型sigset_t 是数据类型可以定义变量信号集的初始化通常配合响应的函数接口且用指针传递在函数里修改外面的信号集。这些函数是指针转递在函数内部修改外部的信号集所以要传指针*setsigprocmask()函数 添加信号掩码进程的信号掩码是用信号集来做数据类型的。sigprocmask功能往这个信号集里添加掩码参数里要有指向对应信号集的指针。SIG_BLOCK设置的掩码信号集与原来的掩码信号集取并集。原比特位1和0不变SIG_UNBLOCK还用老掩码信号集但把新掩码信号集里的掩码信号内容删掉。SIG_SETMASK舍弃老的掩码信号集采用新掩码信号集。#include stdio.h #include unistd.h #include stdlib.h #include signal.h int main(int argc, const char *argv[]) { sigset_t blockSet, prevMask; sigemptyset(blockSet); sigaddset(blockSet, SIGINT); if ( sigprocmask(SIG_BLOCK, blockSet, prevMask) -1) { perror(sigprocmask); exit(0); } sleep(5); return 0; }7.信号处置sigaction函数示例1#include stdio.h #include signal.h #include unistd.h #include string.h // 信号处理函数 void sighandler(int sig) { printf(捕获信号%s\n, strsignal(sig) ); // 可以在这里进行一些清理工作 } int main() { struct sigaction sa; // 清空 sigaction 结构体 sigemptyset(sa.sa_mask); // 设置信号处理函数 sa.sa_handler sighandler; // 不使用信号处理函数的扩展功能 sa.sa_flags 0; // 使用 sigaction 注册信号处理函数 if (sigaction(SIGINT, sa, NULL) -1) { perror(sigaction); return 1; } //SIGINT会终止nanosleep()系统调用所以这里如果去掉while(1),会直接导致进程终止 while (1) sleep(1); return 0; }信号有可能中断系统调用。实例2#include stdio.h #include signal.h #include unistd.h #include string.h // 信号处理函数 void sigusr1_handler(int signum) { printf(捕捉到信号%s\n, strsignal(signum) ); sleep(15); // 模拟长时间处理 printf(阻塞完成\n); } int main() { struct sigaction sa; // 清空 sigaction 结构体 sigemptyset(sa.sa_mask); // 在处理 SIGUSR1 时阻塞 SIGUSR2 sigaddset(sa.sa_mask, SIGUSR2); // 设置信号处理函数 sa.sa_handler sigusr1_handler; // 不使用信号处理函数的扩展功能 sa.sa_flags 0; // 使用 sigaction 注册信号处理函数 if (sigaction(SIGINT, sa, NULL) -1) { perror(sigaction); return 1; } printf(进程ID:%d, 等待SIGINT信号...\n, getpid() ); while (1) sleep(1); return 0; }注意1.一个进程运行时响应一个信号的过程中同样的信号、和掩码信号集包含的信号会被阻塞并且第一个信号相应结束时只会执行一个被阻塞的信号其他舍弃。2.如果一个信号被响应时进程有收到一个信号这个信号和第一个信号不同也不在掩码信号集里面就会被立刻响应。这里分析下运行程序没给信号时程序会依照代码输出进程号。这时我们ctrl c内核发给进程SIGINT信号然后进程响应信号处理器函数sigusr1_handler这个过程中1.我输入ctrl z和SIGINT不相同也不再掩码里SIGTSTP信号会立即被进程响应。2.进程收到SIGINT或SIGUSR2都会阻塞阻塞结束后再执行。由于子进程是继承了父进程bash的信号处理方式。SIGUSR2在bash下的默认处理方式是终止进程。这里换个别的信号父进程没有的进程就没有反应不会响应。举例1信号处置时进程收到SIGINT这个信号然后执行信号处置函数信号处理器函数sigusr1_handler这个函数执行时我在给第二个信号SIGUSR2由于信号掩码里有SIGUSR2所以SIGUSR2被阻塞。举例2如果进程相应了信号SIGINT在这个过程中后续收到的同种信号会被自动加入到掩码后续输入的SIGINT会阻塞直到第一个SIGINT响应结束并且后面输入的SIGINT只会响应一次其他的作废。kill命令kill不一定是杀死kill -SIGUSR1 进程号代表向进程号发送信号SIGUSR1不是杀死跟杀死没关系kill -9 PID 才是杀死对应进程信号和对应的代码SIGKILL kill -9 无法捕获终止进程SIGSTOP kill -stop 无法捕获暂停进程SIGINT ctrl c 终止进程SIGTSTP ctrl z 暂停进程SIGQUIT ctrl \ 终止进程生成文件signal信号处置设置