目录内核态和用户态在系统中用户或者OS自己怎么知道当前处于内核态还是用户态sigaction可重入函数volatile c语言关键字SIGCHLD信号内核态和用户态系统调用表结构是属于操作系统的3GB--用户空间 1GB---内核空间0.系统调用的过程也是在进程地址空间上进行的所有的函数调用都是地址空间之间的跳转系统调用基于软中断完成整个进程的地址空间0-3GB 用户区一般用户访问0-3GB不需要任何系统调用3-4GB内核区只要拿到虚拟地址就能访问0-3GB的代码和数据了根据页表从虚拟地址到物理地址的映射操作系统也是软件一定也在内存用户页表会存在很多份内核页表系统只有一份所有进程共享0-4GB 是每个进程的视野每个32位进程看到的世界┌─────────────────────────────────┐ │ 进程视角我拥有0-4GB的完整空间 │ │ 但实际上 │ │ • 0-3GB我的私有空间 │ │ • 3-4GB内核的共享空间 │ └─────────────────────────────────┘现实情况物理内存中同时有多个进程每个都以为自己独占0-4GB0-4GB是每个进程的虚拟地址空间范围不是一个进程。操作系统通过页表机制让每个进程都以为自己独占了0-4GB实际上它们的物理内存是隔离的。结论1.这意味着无论进程如何调度我们总能找到操作系统问题2用户和内核都在同一个0-4GB的地址空间上了如果用户随便拿一个虚拟地址3-4GB,用户不就可以随便访问内核中的代码和数据了吗OS为了保护自己不相信任何人必须采用系统调用的方式进行访问所以为了保证这样的概念引入了用户态和内核态用户态以用户身份只能访问自己的0-3GB内核态以内核的身份运行你通过系统调用的方式访问OS3-4GB在系统中用户或者OS自己怎么知道当前处于内核态还是用户态在CPU内部有一些寄存器特定位置会记录下是内核态还是用户态cs寄存器低两位比特位0内核态3用户态CPU内部有标志位记录当前什么态所以int 0x80 syscall就是转换态CPL是x86架构中最关键的特权级概念直接决定了CPU当前执行的代码具有什么权限。CPL的本质CS寄存器的低2位[全局or局部]段描述符表 RPLDPL所以在键盘上ctrlc,键盘ctrlc后会以硬件中断的方式告诉操作系统键盘有东西被按下操作系统执行键盘所对应的中断处理方法从键盘中读取ctrlc识别到是一个终止前台任务的处理所以直接执行我们对应的中断处理方法就是向目标进程直接发信号发送的就是当前进程修改task_struct判定表比特位。当把中断处理完进程继续运行发生缺陷陷阱异常中断而导致当前进程陷入内核执行完毕返回进行判定检查没有block有pending,handler,自定义所以我们就执行信号处理CPU内部的软中断比如int 0x80或者syscall我们叫做 陷阱CPU内部的软中断比如除零/野指针等我们叫做 异常。捕捉信号的另一种做法sigaction• sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回-1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:• 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为⼀个函数指针表示用自定义函数捕捉信号,或者说向内核注册了⼀个信号处理函数,该函数返回值为void,可以带⼀个int参数,通过参数可以得知当前信号的编号,这样就可以用同⼀个函数处理多种信号。显然,这也是⼀个回调函数,不是被main函数调用而是被系统所调用。NAMEsigaction, rt_sigaction - examine and change a signal actionSYNOPSIS#include signal.hint sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);信号编号 如何处理信号 输出型参数---历史上信号带回来方便恢复 自定义捕捉方法RETURN VALUEsigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;---信号集int sa_flags;void (*sa_restorer)(void);};test.cpp#includeiostream #includeunistd.h #includesignal.h #includecstdlib void handler(int signum) { std::couthello signalsignumstd::endl; exit(0); } int main() { struct sigaction act,oact; act.sa_handler handler; sigaction(SIGINT,act,oact);//对2号信号进行了自定义捕捉 while(true) { std::couthello worldstd::endl; sleep(1); } return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out hello world hello world hello world ^Chello signal2当某个信号的处理函数被调用时,内核自动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么它会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。sa_flags字段包含⼀些选项,代码都把sa_flags设为0,sa_sigaction是实时信号的处理函数当前正在处理的信号会被自动屏蔽test.cpp#includeiostream #includeunistd.h #includesignal.h #includecstdlib void handler(int signum) { std::couthello signalsignumstd::endl; while(true) { //不断获取pending表 sigset_t pending; sigpending(pending); for(int i 31;i1;i--) { if(sigismember(pending,i)) std::cout1; else std::cout0; } std::coutstd::endl; sleep(1); } exit(0); } int main() { struct sigaction act,oact; act.sa_handler handler; sigemptyset(act.sa_mask); act.sa_flags 0; sigaction(SIGINT,act,oact);//对2号信号进行了自定义捕捉 while(true) { std::couthello worldgetpid()std::endl; sleep(1); } return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out hello world208965 hello world208965 hello world208965 hello world208965 ^Chello signal2 0000000000000000000000000000000 0000000000000000000000000000000 ^C0000000000000000000000000000010 0000000000000000000000000000010 ^C0000000000000000000000000000010 ^\Quit把其他信号加进来test.cpp#includeiostream #includeunistd.h #includesignal.h #includecstdlib void handler(int signum) { std::couthello signalsignumstd::endl; while(true) { //不断获取pending表 sigset_t pending; sigpending(pending); for(int i 31;i1;i--) { if(sigismember(pending,i)) std::cout1; else std::cout0; } std::coutstd::endl; sleep(1); } exit(0); } int main() { struct sigaction act,oact; act.sa_handler handler; sigemptyset(act.sa_mask); sigaddset(act.sa_mask,3); sigaddset(act.sa_mask,4); act.sa_flags 0; sigaction(SIGINT,act,oact);//对2号信号进行了自定义捕捉对2,3,4都屏蔽 while(true) { std::couthello worldgetpid()std::endl; sleep(1); } return 0; }终端1 rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out hello world209374 hello world209374 hello world209374 hello world209374 hello world209374 hello world209374 hello world209374 hello signal2 0000000000000000000000000000000 0000000000000000000000000000000 0000000000000000000000000000010 0000000000000000000000000000010 0000000000000000000000000000010 0000000000000000000000000000110 0000000000000000000000000000110 0000000000000000000000000000110 0000000000000000000000000000110 0000000000000000000000000001110 0000000000000000000000000001110 0000000000000000000000000001110 0000000000000000000000000001110 Killed 终端2 rootiZ5waahoxw3q2bZ:~/linux-learning/linux# kill -2 209374 rootiZ5waahoxw3q2bZ:~/linux-learning/linux# kill -2 209374 rootiZ5waahoxw3q2bZ:~/linux-learning/linux# kill -3 209374 rootiZ5waahoxw3q2bZ:~/linux-learning/linux# kill -4 209374 rootiZ5waahoxw3q2bZ:~/linux-learning/linux# kill -9 209374可重入函数main执行流handler执行流insert方法被两个以上的执行流重复进入了--函数被重入了--不可重入函数/可重入函数大部分的函数都是不可重入的如果一个函数符合以下条件之一则是不可重入的:• 调用了malloc或free,因为malloc也是用全局链表来管理堆的。• 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。函数只有自己的临时变量---可重入的volatile c语言关键字声明易变变量防止编译器优化test.cpp#includeiostream #includeunistd.h #includesignal.h #includecstdlib int flag 0; void handler(int signu) { std::cout更改全局变量flag- 1std::endl; flag 1; } int main() { signal(2,handler); while(!flag); std::coutprocess quit normal!std::endl; return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out ^C更改全局变量0- 1 process quit normal!main里面并不会对flag进行修改比如编译器优化级别比较高的情况下flag-register-优化到寄存器中CPU两种运算算数运算逻辑运算1.把变量从内存导入到CPU中2.在CPU寄存器内进行某种计算3.如果需要可以写回不需要反之但是编译器优化flag-register-优化到寄存器中1、2省去1不再进行缓存直接导入寄存器gcc编译时可以指定优化级别man gcc/g优化级别-O0 -O1 -O2...默认-O0rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp -O0 rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out ^C更改全局变量0- 1 process quit normal! rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp -O1 rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out ^C更改全局变量0- 1 ^C更改全局变量1- 1 ^C更改全局变量1- 1 ^\Quitctrlc退出不了优化太高了寄存器覆盖了进程看到变量的真实情况内存不可见了所以拿volatile修饰一下volatile int flag0;保证内存空间可见性test.cpp#includeiostream #includeunistd.h #includesignal.h #includecstdlib volatile int flag 0; void handler(int signu) { std::cout更改全局变量flag- 1std::endl; flag 1; } int main() { signal(2,handler); while(!flag); std::coutprocess quit normal!std::endl; return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp -O1 rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out ^C更改全局变量0- 1 process quit normal! rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp -O3 rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out ^C更改全局变量0- 1 process quit normal!SIGCHLD信号17) SIGCHLD进程讲过用wait和waitpid函数清理僵尸进程,父进程可以阻塞等待子进程结束,也可以非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)。采用第⼀种方式,父进程阻塞了就不能处理自己的⼯作了;采用第⼆种方式,父进程在处理自己的工作的同时还要记得时不时地轮询⼀下,程序实现复杂。其实,子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以⾃定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。请编写⼀个程序完成以下功能:父进程fork出子进程,子进程调用exit(2)终止,父进程自定义SIGCHLD信号的处理函数,在其中调用wait获得子进程的退出状态并打印。默认动作是SIGCHLD P1990 Ign Child stopped or terminated子进程在终止时会给父进程发SIGCHLD信号---默认动作是Igntest.cpp#includeiostream #includesignal.h #includeunistd.h #includecstdio #includesys/wait.h void Say(int num) { std::coutfather get a signalnumstd::endl; } int main() { //父进程 signal(SIGCHLD,Say);//父进程 pid_t idfork(); if(id0) { std::coutI am child,exitstd::endl; sleep(3); exit(3); } waitpid(id,nullptr,0); std::coutI am father,exitstd::endl; return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out I am child,exit father get a signal17 I am father,exittest.cpp#includeiostream #includesignal.h #includeunistd.h #includecstdio #includesys/wait.h void WaitAll(int num) { while(true) { pid_t nwaitpid(-1,nullptr,WNOHANG);//waitpid默认是阻塞的 //如果子进程被创建很多可以将所有子进程进行回收 //WNOHANG叫做非阻塞轮询 if(n0) { break; } else if(n0) { std::coutwaitpid errorstd::endl; break; } } std::coutfather get a signalnumstd::endl; } int main() { //父进程 signal(SIGCHLD,WaitAll);//父进程 pid_t idfork();//如果我们有10个子进程呢6个退出了4个没退 if(id0) { sleep(3); std::coutI am child,exitstd::endl; exit(3); } while(true) { std::coutI am father,exitstd::endl; sleep(1); } return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out hello I am father,exit I am father,exit I am father,exit I am child,exit I am father,exit waitpid error father get a signal17 father get a signI am father,exit I am father,exit I am father,exit I am father,exit I am father,exit ^CrootiZ5waahoxw3q2bZ:~/linux-learning/linux# while :; do ps ajx | head -1 ps ajx | grep hello ; sleep 1;done PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211124 211123 207845 pts/1 211123 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211129 211128 207845 pts/1 211128 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 211133 211134 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211138 211137 207845 pts/1 211137 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 211133 211134 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211154 211153 207845 pts/1 211153 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 211133 211134 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211166 211165 207845 pts/1 211165 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211173 211172 207845 pts/1 211172 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211178 211177 207845 pts/1 211177 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211183 211182 207845 pts/1 211182 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211198 211197 207845 pts/1 211197 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211208 211207 207845 pts/1 211207 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211133 211133 208299 pts/0 211133 S 0 0:00 ./a.out hello 207845 211213 211212 207845 pts/1 211212 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211219 211218 207845 pts/1 211218 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211236 211235 207845 pts/1 211235 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211253 211252 207845 pts/1 211252 S 0 0:00 grep --colorauto hello ^C事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外⼀种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用但不保证在其它UNIX系统上都可用。请编写程序验证这样做不会产生僵尸进程。将SIGCHLD的处理动作置为SIG_IGNtest.cpp#include iostream #include signal.h #include unistd.h #include cstdio #include sys/wait.h int main() { // 父进程 signal(SIGCHLD, SIG_IGN);// 父进程 for (int i 0; i 10; i) { pid_t id fork(); // 如果我们有10个子进程呢6个退出了4个没退 if (id 0) { sleep(3); std::cout I am child,exit std::endl; exit(3); // if(i6) exit(3); // else pause(); } } while (true) { std::cout I am father,exit std::endl; sleep(1); } return 0; }rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# g test.cpp rootiZ5waahoxw3q2bZ:~/linux-learning/linux/26-6-25# ./a.out hello I am father,exit I am father,exit I am father,exit I am child,exit I am child,exit I am child,exit I am child,exit I am child,exit I am child,exit I am child,exit I am child,exit I am child,exit I am child,exit I am father,exit I am father,exit I am father,exit I am father,exit I am father,exit ^CrootiZ5waahoxw3q2bZ:~/linux-learning/linux# while :; do ps ajx | head -1 ps ajx | grep hello ; sleep 1;done PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211475 211474 207845 pts/1 211474 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211480 211479 207845 pts/1 211479 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211485 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211486 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211487 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211488 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211489 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211490 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211491 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211492 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211493 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211494 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211498 211497 207845 pts/1 211497 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211485 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211486 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211487 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211488 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211489 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211490 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211491 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211492 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211493 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211494 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211523 211522 207845 pts/1 211522 R 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211485 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211486 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211487 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211488 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211489 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211490 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211491 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211492 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211493 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 211484 211494 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211553 211552 207845 pts/1 211552 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211560 211559 207845 pts/1 211559 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211565 211564 207845 pts/1 211564 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211570 211569 207845 pts/1 211569 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 208299 211484 211484 208299 pts/0 211484 S 0 0:00 ./a.out hello 207845 211585 211584 207845 pts/1 211584 S 0 0:00 grep --colorauto hello PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 207845 211596 211595 207845 pts/1 211595 S 0 0:00 grep --colorauto hello ^Csignal(SIGCHLD,SIG_IGN)忽略处理不是SIGCHLD默认动作是Ign吗为什么还要手动设置signal(SIGCHLD,SIG_DFL)是默认处理动作可以认为SIG_DEL缺省动作是Ign感谢你的观看期待我们下次再见