一、终端与进程
1.终端与bash进程
查看bash进程
[root@JD ~]# ps -ef | grep bash root 9381 27504 0 16:00 pts/0 00:00:00 grep --color=auto bash root 27504 27483 0 15:34 pts/0 00:00:00 -bash
打开一个新的窗口,再看bash进程
[root@JD ~]# ps -ef | grep bash root 9761 9750 0 16:01 pts/1 00:00:00 -bash root 10388 27504 0 16:02 pts/0 00:00:00 grep --color=auto bash root 27504 27483 0 15:34 pts/0 00:00:00 -bash
发现多了一个 -bash 进程,比上面多了 pts/1
pts 虚拟终端,每连接一个虚拟终端到linux操作系统,就会出现一个bash进程(shell)
2.终端上的开启进程
简单的C程序
#include<stdio.h> #include<unistd.h> int main(int argc, char *const *argv) { printf("nice to meet you. "); for (;;) { sleep(1); printf(" sleep 1s. "); } printf("Bye bye "); return 0; }
进行编译,然后运行
[root@JD myfiles]# gcc -o nginx nginx.c [root@JD myfiles]# ./nginx nice to meet you. sleep 1s. sleep 1s. sleep 1s. sleep 1s. sleep 1s.
按CTRL C,发现没有 执行 printf("Bye bye ")
打开另一个窗口
[root@JD ~]# ps -la F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 T 0 7331 27504 0 80 0 - 1054 do_sig pts/0 00:00:00 nginx 0 S 0 11234 27504 0 80 0 - 1054 hrtime pts/0 00:00:00 nginx 0 R 0 11666 11399 0 80 0 - 38310 - pts/2 00:00:00 ps
发现有该进程
我们把运行程序的那个窗口,也就是进程关闭,没运行程序的的终端发现已经没有该进程了
因此,随着终端的退出,这个终端上运行的进程也会退出
打开新的窗口,运行程序
然后再另一个窗口查看
[root@JD ~]# ps -ef | grep bash root 9761 9750 0 16:01 pts/1 00:00:00 -bash root 11399 11325 0 17:04 pts/2 00:00:00 -bash root 21200 21179 0 17:22 pts/3 00:00:00 -bash root 21717 21711 0 17:25 pts/0 00:00:00 -bash root 21937 21200 0 17:25 pts/3 00:00:00 grep --color=auto bash [root@JD ~]# ps -ef | grep ngnix root 21981 21200 0 17:25 pts/3 00:00:00 grep --color=auto ngnix [root@JD ~]# ps -ef | grep nginx root 21580 1 0 2019 ? 00:00:00 nginx: master process ./nginx nobody 21581 21580 0 2019 ? 00:00:00 nginx: worker process nobody 21582 21580 0 2019 ? 00:00:00 nginx: worker process root 21823 21717 0 17:25 pts/0 00:00:00 ./nginx root 22632 21200 0 17:27 pts/3 00:00:00 grep --color=auto nginx
我们发现 ./nginx 是 bash pts/0 的子进程
内核会启动一个 pid为1的进程,该进程产生子进程
[root@JD myfiles]# ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 2019 ? 00:12:04 /usr/lib/systemd/systemd --switched-root --system --deserializ
3.进程关系进一步分析
每个进程属于一个进程组,进程组是一个或者多个进程的集合,每个进程组有一个唯一的进程组id,可以调用系统函数来创建进程组,加入进程组
会话,session,一个或多个进程组的集合
一般,只要不进行特殊的系统函数调用,一个bash(shell)上运行的所有程序都属于一个会话,而这个会话有一个session leader,而这个bash就是session leader。当然,你可以调用系统函数,创建新的session。
pid进程id,ppid父进程id,sid会话id,tty终端,gprd进程组,comm执行的命令
[root@JD myfiles]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND 9761 9750 9761 pts/1 9761 bash 11399 11325 11399 pts/2 11399 bash 21200 21179 21200 pts/3 21200 bash 21580 1 21580 ? 21580 nginx 21581 21580 21580 ? 21580 nginx 21582 21580 21580 ? 21580 nginx 21717 21711 21717 pts/0 21717 bash 23192 21711 23192 ? 23192 bash
4.strace工具的使用
linux下调试分析诊断工具,可以跟踪程序执行时进程的系统调用以及所收到的信号
如果xshell等终端断开,系统会发送SIGHUP信号(终端断开信号)给 session leader,即 bash 进程。bash 进程收到SIGHUP 信号后,bash 会把这个信号发送给 session 里面的所有进程,收到这个 SIGHUP 信号的缺省动作就是退出。
在第一个窗口运行我们编写的那个程序
第二个窗口
[root@JD myfiles]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND 3128 32377 32377 pts/1 3128 nginx 3150 21711 3150 ? 3150 bash 11399 11325 11399 pts/2 11399 bash 21200 21179 21200 pts/3 21200 bash 21580 1 21580 ? 21580 nginx 21581 21580 21580 ? 21580 nginx 21582 21580 21580 ? 21580 nginx 21717 21711 21717 pts/0 21717 bash 32377 32367 32377 pts/1 32377 bash 32421 32359 32421 pts/4 32421 bash
然后使用strace命令跟踪该进程
[root@JD myfiles]# strace -e trace=signal -p 3128 strace: Process 3128 attached rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
在第三个窗口,跟踪父进程 pts/1
[root@JD ~]# strace -e trace=signal -p 32377 strace: Process 32377 attached
当关闭第一窗口的时候,两个窗口都收到了信号
第二个窗口
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=32377, si_uid=0} --- +++ killed by SIGHUP +++
第三个窗口
root@JD ~]# strace -e trace=signal -p 32377 strace: Process 32377 attached --- SIGHUP {si_signo=SIGHUP, si_code=SI_KERNEL} --- --- SIGCONT {si_signo=SIGCONT, si_code=SI_KERNEL} --- rt_sigreturn({mask=[CHLD]}) = -1 EINTR (Interrupted system call) kill(-3128, SIGHUP) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD TSTP TTIN TTOU], [CHLD], 8) = 0 rt_sigprocmask(SIG_SETMASK, [CHLD], NULL, 8) = 0 rt_sigaction(SIGHUP, {SIG_DFL, [], SA_RESTORER, 0x7f11093bd3b0}, {0x456be0, [HUP INT ILL TRAP ABRT BUS FPE USR1 SEGV USR2 PIPE ALRM TERM XCPU XFSZ VTALRM SYS], SA_RESTORER, 0x7f11093bd3b0}, 8) = 0 kill(32377, SIGHUP) = 0 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=32377, si_uid=0} --- +++ killed by SIGHUP +++
bash 先向 3128 进程组发出了 SIGHUP 信号,正是运行程序的进程所在的那个进程组
即,bash先发送SIGHUP给同一个session里边的所有进程,然后再发送SIGHUP给自己
5.终端关闭时如何让进程不退出
- 程序拦截SIGHUP信号
- 进程和bash进程不在同一个session中
拦截
#include<stdio.h> #include<unistd.h> #include<signal.h> int main(int argc, char *const *argv) { printf("nice to meet you. "); // 系统函数,设置某个信号来的时候处理程序 signal(SIGHUP, SIG_IGN); // SIG_IGN:要求系统忽略这个信号 for (;;) { sleep(1); printf(" sleep 1s. "); } printf("Bye bye "); return 0; }
在一个窗口编译并运行
在另一个窗口查看进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx666' PID PPID SID TT PGRP COMMAND 19382 19341 19382 pts/1 19382 bash 21200 21179 21200 pts/3 21200 bash 21299 21717 21717 pts/0 21299 nginx666 21323 21711 21323 ? 21323 bash 21717 21711 21717 pts/0 21717 bash 32421 32359 32421 pts/4 32421 bash
关闭运行的程序的第一个窗口,第二窗口再次查看进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx666' PID PPID SID TT PGRP COMMAND 19382 19341 19382 pts/1 19382 bash 21200 21179 21200 pts/3 21200 bash 21299 1 21717 ? 21299 nginx666 32421 32359 32421 pts/4 32421 bash
发现,虽然那个 bash 没有了,但其子进程 nginx666 还在,但是它终端变成了 ?,其父进程id变为了1,成了一个孤儿进程。
不在同一session中
#include<stdio.h> #include<unistd.h> int main(int argc, char *const *argv) { pid_t pid; printf("nice to meet you. "); // 系统函数,设置某个信号来的时候处理程序 signal(SIGHUP, SIG_IGN); // SIG_IGN:要求系统忽略这个信号 pid = fork(); // 系统函数,用来创建新进程,子进程会从fork()调用之后开始执行 if(pid < 0) { printf("fork()进程出错! "); } else if(pid == 0) { // 子进程 printf("子进程开始执行! "); setsid(); // 新建立一个不同的session,但是进程组组长使用setsid()是无效的 for (;;) { sleep(1); printf("child sleep 1s. "); } return 0; } else { for (;;) { sleep(1); printf(" sleep 1s. "); } } printf("Bye bye "); return 0; }
创新编译并执行
[root@JD myfiles]# gcc -o nginx888 nginx.c [root@JD myfiles]# ./nginx888 nice to meet you. 子进程开始执行! child sleep 1s. sleep 1s. sleep 1s. child sleep 1s. sleep 1s. child sleep 1s.
另一个窗口查看进程,有两个nginx888,子进程没有终端,即不在一个session中
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx888' PID PPID SID TT PGRP COMMAND 19382 19341 19382 pts/1 19382 bash 21784 21729 21784 pts/2 21784 bash 31647 21784 21784 pts/2 31647 nginx888 31648 31647 31648 ? 31648 nginx888 32190 32126 32190 pts/3 32190 bash 32206 21729 32206 ? 32206 bash 32421 32359 32421 pts/4 32421 bash
关闭第一个窗口
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx888' PID PPID SID TT PGRP COMMAND 19382 19341 19382 pts/1 19382 bash 31648 1 31648 ? 31648 nginx888 32190 32126 32190 pts/3 32190 bash 32421 32359 32421 pts/4 32421 bash
仍保留那个子进程
还有setid命令,启动一个进程,且能让启动的进程在一个新的session中,这样终端关闭时进程就不会退出
[root@JD myfiles]# setsid ./nginx [root@JD myfiles]# nice to meet you. sleep 1s.
nohup(no hang up 不要挂断)
该命令会把屏幕输出重定位到当前目录的 nohup.out 文件中
后台运行 &
执行这个程序的同时,终端可以执行其他任务,不用后台执行则要等待程序执行完再做其他工作。
终端断开后,后台运行的进程也会被杀掉
二、信号
1.信号的基本概念
进程间常用通信手段:发送信号
信号:通知(事情通知),用来通知某个进程发生了某个事情
事情,信号都是突发事件,信号是异步发送的,信号也被称为“软件中断”
信号的产生
a)某个进程发送给另外一个进程或者发送给自己
b)由内核(操作系统)发送给某个进程
通过在键盘输入命令CTRL+C(中断信号),kill命令
内存访问异常,除数为0等等,硬件都会检测到并且通知内核
信号名字都是SIG开头,如上节的SIGHUP
UNIX以及类UNIX操作系统,支持的信号数量各不相同,10-60个之间
信号既有名字,其实也都是一些数字,信号是一些正整数数量,包含在一些头文件中,#include<signal.h>
头文件,包含路径:/user/local/include /usr/include/
库文件,连接路径:/usr/local/lib /usr/lib
[root@JD ~]# find / -name "signal.h" | xargs grep -in "SIGHUP" /usr/include/asm/signal.h:20:#define SIGHUP 1 /usr/include/asm-generic/signal.h:10:#define SIGHUP
xargs 给其他命令传递参数,上面它可以把 signal.h 的内容(而非文件名)传给grep进行搜索
-in 忽略大小写,显示行号
查看
[root@JD ~]# cd /usr/include/asm/
[root@JD asm]# vi signal.h
里面的信号
#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 #define SIGUNUSED 31
2.通过kill命令认识一些信号
kill进程id,它的工作是发信号给进程,
kill能给进程发送都中信号
在第一窗口中运行程序
[root@JD myfiles]# ./nginx999 nice to meet you. sleep 1s. sleep 1s.
第二个窗口查看进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND 21580 1 21580 ? 21580 nginx 21581 21580 21580 ? 21580 nginx 21582 21580 21580 ? 21580 nginx 24225 24210 24225 pts/1 24225 bash 30298 30293 30298 pts/4 30298 bash 30364 30262 30364 pts/5 30364 bash 31023 24225 24225 pts/1 31023 nginx999 31805 24210 31805 ? 31805 bash 32128 32078 32128 pts/0 32128 bash 32459 32450 32459 pts/2 32459 bash
追踪该进程
[root@JD ~]# strace -e trace=signal -p 31023 strace: Process 31023 attached rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
在第三个窗口kill该进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND 21580 1 21580 ? 21580 nginx 21581 21580 21580 ? 21580 nginx 21582 21580 21580 ? 21580 nginx 24225 24210 24225 pts/1 24225 bash 30298 30293 30298 pts/4 30298 bash 30364 30262 30364 pts/5 30364 bash 31023 24225 24225 pts/1 31023 nginx999 32128 32078 32128 pts/0 32128 bash 32459 32450 32459 pts/2 32459 bash 32689 24210 32689 ? 32689 bash [root@JD ~]# kill 31023
第一个窗口结果
... sleep 1s. sleep 1s. sleep 1s. sleep 1s. sleep 1s. sleep 1s. Terminated
第二个窗口结果
... rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=30298, si_uid=0} --- +++ killed by SIGTERM +++
如过用kill进程 id,那么就是发送往该进程发送 SIGTERM 信号
如果使用 kill -1 Id
[root@JD ~]# kill -1 4527
发现第二个窗口
[root@JD ~]# strace -e trace=signal -p 4527 strace: Process 4527 attached rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=3995, si_uid=0} --- +++ killed by SIGHUP +++
kill -数字 进程ID,就发出这个数字对应的信号,没数字就相当于 kill -15 ID
#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #define SIGTTIN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGIO 29 #define SIGPOLL SIGIO /* #define SIGLOST 29 */ #define SIGPWR 30 #define SIGSYS 31 #define SIGUNUSED 31
绝大部分缺省都是杀死进程
3.进程的状态
[root@JD myfiles]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 3720 3686 3720 pts/3 3720 bash Ss+ 3995 3978 3995 pts/6 3995 bash Ss+ 11843 24210 11843 ? 11843 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S 24225 24210 24225 pts/1 24225 bash Ss 30298 30293 30298 pts/4 30298 bash Ss+ 30364 30262 30364 pts/5 30364 bash Ss+ 32128 32078 32128 pts/0 32128 bash Ss+ 32459 32450 32459 pts/2 32459 bash Ss+
4.常用的信号
5.信号处理的相关动作
当某个信号出现时,可以按三种方式之一进行处理,我们称之为信号的处理或者信号相关的动作
(1)执行系统默认动作,绝大多数信号的默认动作是杀死这个进程
(2)忽略此信号(但是不包括SIGKILL和SIGSTOP)
kill -9 进程ID 一定是能把进程杀死的,不能被忽略
(3)捕捉该信号:可以写个处理函数,信号来的时候,就用处理函数来处理,但是不包括SIGKILL和SIGSTOP
三、Unix/Linux 操作系统体系结构和信号编程初步
1.体系结构
类UNIX操作系统体系结构分为两个状态:用户态和内核态
(a)操作系统/内核,用来控制计算机硬件资源,提供应用程序运行的环境
我们写程序,要么运行在用户态,要么运行在内核态,当程序运行一些特殊代码的时候,可能切换到内核态,这种切换由操作系统控制,不需人为介入。
(b)系统调用:就是一些函数(系统函数),我们只需去调用
(c)shell bash(borne again shell 重新装配的shell),sell的一种,linux默认采用的shell
通俗理解,bash是一个可执行程序,主要作用是把用户输入的命令翻译给操作系统(命令解释器)
login 启动 bash
分割系统调用和应用程序,胶水作用
(d)用户态、内核态之间的切换
运行于用户态的进程可以执行的操作和访问的资源会受到极大限制,而运行在内核态的进程可以执行任何操作并且在资源的使用上没有限制
一个进程大部分时间是处于用户态下的,只有需要内核所提供的服务时,才会切换到内核态,内核态的事情完成后,又转回到用户态
比如,malloc()、printf()都会转到内核态又转回用户态
疑问:为什么要区分用户态,内核态
大概有两个目的:
(1)一般情况下,程序都运行在用户态状态,权限小,不至于危害到系统其它部分;当你干一些危险的事情的时候,系统给你提供接口,让你去干;
(2)既然这些接口是系统提供给你的,那么这些接口也是操作系统统一管理的;
资源是有限的, 如果大家都来访问这些资源,如果不加以管理,一个是访问冲突,一个是被访问的资源如果耗尽,那系统还可能崩溃;
系统提供这些接口,就是为了减少有限的资源的访问以及使用上冲突;
那么什么时候从用户态切换到内核态去呢?
a)系统调用,比如调用malloc();
b)异常事件,比如来了个信号;
c)外围设备中断:
总结起来,大家只需要知道 用户态,内核态,两者根据需要自动切换
2.signal函数范例
信号来了,可以忽略,可以捕捉,可以用signal函数
#include <stdio.h> #include <stdlib.h> //malloc #include <unistd.h> #include <signal.h> //信号处理函数 void sig_usr(int signo) { if(signo == SIGUSR1) { printf("收到了SIGUSR1信号! "); } else if(signo == SIGUSR2) { printf("收到了SIGUSR2信号! "); } else { printf("收到了未捕捉的信号%d! ",signo); } } int main(int argc, char *const *argv) { if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); } if(signal(SIGUSR2,sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR2信号! "); } for(;;) { sleep(1); //休息1秒 printf("休息1秒 "); } printf("再见! "); return 0; }
编译并运行
[root@JD myfiles]# gcc -o nginx3_3_1 nginx3_3_1.c
[root@JD myfiles]# ./nginx3_3_1
第二个窗口查看进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 17795 23573 23573 pts/1 17795 nginx3_3_1 S+ 18603 23528 18603 ? 18603 bash Ss 19339 19287 19339 pts/3 19339 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S 23548 23529 23548 pts/0 23548 bash Ss+ 23573 23528 23573 pts/1 23573 bash Ss 23825 23811 23825 pts/2 23825 bash Ss+
向进程发送信号
[root@JD ~]# kill -USR1 17795
发现第一个窗口受到了信号
... 休息1秒 休息1秒 休息1秒 收到了SIGUSR1信号! 休息1秒 休息1秒 ...
发送USR2也一样
如果不捕捉信号,默认是杀死该进程,比如我们注释掉解释 USR2 信号
#include <stdlib.h> //malloc #include <unistd.h> #include <signal.h> //信号处理函数 void sig_usr(int signo) { if(signo == SIGUSR1) { printf("收到了SIGUSR1信号! "); } else if(signo == SIGUSR2) { printf("收到了SIGUSR2信号! "); } else { printf("收到了未捕捉的信号%d! ",signo); } } int main(int argc, char *const *argv) { if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信 号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); } // if(signal(SIGUSR2,sig_usr) == SIG_ERR) // { // printf("无法捕捉SIGUSR2信号! "); // } for(;;) { sleep(1); //休息1秒 printf("休息1秒 "); } printf("再见! "); return 0; }
编译并运行
窗口二查看进程,并发送信号
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 19339 19287 19339 pts/3 19339 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S 23548 23529 23548 pts/0 23548 bash Ss+ 23573 23528 23573 pts/1 23573 bash Ss 23825 23811 23825 pts/2 23825 bash Ss+ 28453 23573 23573 pts/1 28453 nginx3_3_1 S+ 28472 23528 28472 ? 28472 bash Ss [root@JD ~]# kill -USR2 28453
窗口一的结果
[root@JD myfiles]# ./nginx3_3_1 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 休息1秒 User defined signal 2
你的进程收到了信号,这个事 就会被内核注意到
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> int g_mysign = 0; void muNEfunc(int value) //我这个函数能够修改这个全局变量g_mysign的值 { //.....其他处理代码 g_mysign = value; //.....其他处理代码 } //信号处理函数 void sig_usr(int signo) { //int tmpsign = g_mysign; muNEfunc(22); //因为一些实际需求必须要在sig_user这个信号处理函数里调用muNEfunc int myerrno = errno; // 备份,后面恢复 if(signo == SIGUSR1) { printf("收到了SIGUSR1信号! "); } else if(signo == SIGUSR2) { printf("收到了SIGUSR2信号! "); } else { printf("收到了未捕捉的信号%d! ",signo); } //g_mysign = tmpsign; errno = myerrno; } int main(int argc, char *const *argv) { if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); } if(signal(SIGUSR2,sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR2信号! "); } for(;;) { sleep(1); //休息1秒 printf("休息1秒 "); muNEfunc(15); printf("g_mysign=%d ",g_mysign); //拿g_mysign做一些其他用途; } printf("再见! "); return 0; }
可重入函数
严格意义:muNEfunc()函数不应该是一个可重入函数,不应该写在信号处理函数中
所谓的可重入函数:就是我们在信号处理函数中 调用它 是安全的;
可重入函数:在信号处理程序中保证调用安全的函数,这些函数是可重入的并被称为异步信号安全的;
有一些大家周知的函数都是不可重入的,比如malloc(),printf();
int errno = 20;
在写信号处理函数的时候,要注意的事项:
a)在信号处理函数中,尽量使用简单的语句做简单的事情,尽量不要调用系统函数以免引起麻烦;
b)如果必须要在信号处理函数中调用一些系统函数,那么要保证在信号处理函数中调用的 系统函数一定要是可重入的,可重入函数表:
c)如果必须要在信号处理函数中调用那些可能修改errno值的可重入的系统函数,那么 就得事先备份errno值,在信号处理函数返回之前,将errno值恢复;
不可重入函数的错用演示
一旦在信号处理函数中用了不可重入函数,可能导致程序错乱,不正常:
#include <stdio.h> #include <stdlib.h> //malloc #include <unistd.h> #include <signal.h> //信号处理函数 void sig_usr(int signo) { //这里也malloc,这是错用,不可重入函数不能用在信号处理函数中; int* p; p = (int *) malloc (sizeof(int)); //用了不可重入函数; free(p); if(signo == SIGUSR1) { printf("收到了SIGUSR1信号! "); } else if(signo == SIGUSR2) { printf("收到了SIGUSR2信号! "); } else { printf("收到了未捕捉的信号%d! ",signo); } } int main(int argc, char *const *argv) { if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); } if(signal(SIGUSR2,sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR2信号! "); } for(;;) { //sleep(1); //休息1秒 //printf("休息1秒 "); int* p; p = (int *) malloc (sizeof(int)); free(p); } printf("再见! "); return 0; }
发信号,会收不到,程序出些奇怪错误
signal因为兼容性、可靠性等等一些历史问题,不建议使用(我们的策略,坚决不用),建议用sigaction()函数代替;我们摸不清楚(有可能有坑)的东西(地方)我们主动回避,不去踩。
四、信号编程进阶、sigprocmask
1.信号集
一个进程,必须能够记住 这个进程 当前阻塞了哪些信号
000000000000000000000
我们需要 “信号集 ”的这么一种数据类型(结构),能够把这60多个信号都表示下(都装下)。
0000000000,0000000000,0000000000,00,0000000000,0000000000,0000000000,00 (64个二进制位)
linux 是用sigset_t结构类型来表示信号集的;
typedef struct{ unsigned long sig[2]; } sigset_t
信号集的定义:信号集表示一组信号的来(1)或者没来(0)
信号集相关的数据类型: sigset_t;
2.信号相关函数
a)sigemtpyset():把信号集中的所有信号都清0,表示这60多个信号都没有来;
00000000000000000000000000.....
b)sigfillset();把信号集中的所有信号都设置为1,跟sigemptyset()正好相反;
11111111111111111111111111.....
c)用sigaddset(),sigdelset()就可以往信号集中增加信号,或者从信号集中删除特定信号;
d)sigprocmask,sigmember
一个进程,里边会有一个信号集,用来记录当前屏蔽(阻塞)了哪些信号;
如果我们把这个信号集中的某个信号位设置为1,就表示屏蔽了同类信号,此时再来个同类信号,那么同类信号会被屏蔽,不能传递给进程;
如果这个信号集中有很多个信号位都被设置为1,那么所有这些被设置为1的信号都是属于当前被阻塞的而不能传递到该进程的信号;
sigprocmask()函数,就能够设置该进程所对应的信号集中的内容;
3.sigprocmask等信号函数范例演示
#include <stdio.h> #include <stdlib.h> //malloc #include <unistd.h> #include <signal.h> //信号处理函数 void sig_quit(int signo) { printf("收到了SIGQUIT信号! "); if(signal(SIGQUIT,SIG_DFL) == SIG_ERR) { printf("无法为SIGQUIT信号设置缺省处理(终止进程)! "); exit(1); } } int main(int argc, char *const *argv) { sigset_t newmask,oldmask; //信号集,新的信号集,原有的信号集,挂起的信号集 if(signal(SIGQUIT,sig_quit) == SIG_ERR) //注册信号对应的信号处理函数,"ctrl+" { printf("无法捕捉SIGQUIT信号! "); exit(1); //退出程序,参数是错误代码,0表示正常退出,非0表示错误,但具体什么错误,没有特别规定,这个错误代码一般也用不到,先不管他; } sigemptyset(&newmask); //newmask信号集中所有信号都清0(表示这些信号都没有来); sigaddset(&newmask,SIGQUIT); //设置newmask信号集中的SIGQUIT信号位为1,说白了,再来SIGQUIT信号时,进程就收不到,设置为1就是该信号被阻塞掉呗 //sigprocmask():设置该进程所对应的信号集 if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集 { //一个 ”进程“ 的当前信号屏蔽字,刚开始全部都是0的;所以相当于把当前 "进程"的信号屏蔽字设置成 newmask(屏蔽了SIGQUIT); //第三个参数不为空,则进程老的(调用本sigprocmask()之前的)信号集会保存到第三个参数里,用于后续,这样后续可以恢复老的信号集给线程 printf("sigprocmask(SIG_BLOCK)失败! "); exit(1); } printf("我要开始休息10秒了--------begin--,此时我无法接收SIGQUIT信号! "); sleep(10); //这个期间无法收到SIGQUIT信号的; printf("我已经休息了10秒了--------end----! "); if(sigismember(&newmask,SIGQUIT)) //测试一个指定的信号位是否被置位(为1),测试的是newmask { printf("SIGQUIT信号被屏蔽了! "); } else { printf("SIGQUIT信号没有被屏蔽!!!!!! "); } if(sigismember(&newmask,SIGHUP)) //测试另外一个指定的信号位是否被置位,测试的是newmask { printf("SIGHUP信号被屏蔽了! "); } else { printf("SIGHUP信号没有被屏蔽!!!!!! "); } //现在我要取消对SIGQUIT信号的屏蔽(阻塞)--把信号集还原回去 if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) //第一个参数用了SIGSETMASK表明设置 进程 新的信号屏蔽字为 第二个参数 指向的信号集,第三个参数没用 { printf("sigprocmask(SIG_SETMASK)失败! "); exit(1); } printf("sigprocmask(SIG_SETMASK)成功! "); if(sigismember(&oldmask,SIGQUIT)) //测试一个指定的信号位是否被置位,这里测试的当然是oldmask { printf("SIGQUIT信号被屏蔽了! "); } else { printf("SIGQUIT信号没有被屏蔽,您可以发送SIGQUIT信号了,我要sleep(10)秒钟!!!!!! "); int mysl = sleep(10); if(mysl > 0) { printf("sleep还没睡够,剩余%d秒 ",mysl); } } printf("再见了! "); return 0; }
sleep()函数能够被打断:
(1)时间到达了;
(2)来了某个信号,使sleep()提前结束,此时sleep会返回一个值,这个值就是未睡够的时间;
sigaction()函数;
五、fork函数
1.fork() 函数
创建进程;
进程的概念:一个可执行程序,执行起来就是一个进程,再执行起来一次,它就又是一个进程(多个进程可以共享同一个可执行文件)
文雅说法:进程 定义为程序执行的一个实例;
在一个进程(程序)中,可以用fork()创建一个子进程,当该子进程创建时,
它从fork()指令的下一条(或者说从fork()的返回处)开始执行与父进程相同的代码;
a)说白了:fork()函数产生了一个和当前进程完全一样的新进程,并和当前进程一样从fork()函数里返回;
原来一条执行通路(父进程),现在变成两条(父进程+子进程)
fork():一分二
(1.1)fork()函数简单范例
#include <stdio.h> #include <stdlib.h> //malloc,exit #include <unistd.h> //fork #include <signal.h> //信号处理函数 void sig_usr(int signo) { printf("收到了SIGUSR1信号,进程id=%d! ",getpid()); } int main(int argc, char *const *argv) { pid_t pid; printf("进程开始执行! "); //先简单处理一个信号 if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); exit(1); } //--------------------------------- pid = fork(); //创建一个子进程 //要判断子进程是否创建成功 if(pid < 0) { printf("子进程创建失败,很遗憾! "); exit(1); } //现在,父进程和子进程同时开始 运行了 for(;;) { sleep(1); //休息1秒 printf("休息1秒,进程id=%d! ",getpid()); } printf("再见了! "); return 0; }
编译并执行
[root@JD myfiles]# gcc -o nginx3_6_1 nginx3_6_1.c [root@JD myfiles]# ./nginx3_6_1 进程开始执行!
启动第二个终端,查看进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 1181 1165 1181 pts/0 1181 bash Ss 1408 1181 1181 pts/0 1408 nginx3_6_1 S+ 1409 1408 1181 pts/0 1408 nginx3_6_1 S+ 1560 1165 1560 ? 1560 bash Ss 1613 1573 1613 pts/1 1613 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S
发现是一父一子两个进程
fork()之后,是父进程fork()之后的代码先执行还是子进程fork()之后的代码先执行是不一定的,这个跟内核调度算法有关
fork()之前的代码是父子进程都要执行的
//先简单处理一个信号 if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); exit(1); }
在第二个终端发送信号
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 1181 1165 1181 pts/0 1181 bash Ss 1613 1573 1613 pts/1 1613 bash Ss 4698 1181 1181 pts/0 4698 nginx3_6_1 S+ 4699 4698 1181 pts/0 4698 nginx3_6_1 S+ 4721 1165 4721 ? 4721 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S [root@JD ~]# kill -USR1 4698 [root@JD ~]# kill -USR1 4699
第一个终端
...
收到了SIGUSR1信号,进程id=4698! 休息1秒,进程id=4698! 休息1秒,进程id=4699! 休息1秒,进程id=4698! 休息1秒,进程id=4699! 休息1秒,进程id=4698! 休息1秒,进程id=4699! 休息1秒,进程id=4699! 休息1秒,进程id=4698! 休息1秒,进程id=4699! 收到了SIGUSR1信号,进程id=4699!
...
第二个终端,监视父进程
[root@JD ~]# strace -e trace=signal -p 4698 strace: Process 4698 attached rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
第三个终端,kill子进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 1181 1165 1181 pts/0 1181 bash Ss 1613 1573 1613 pts/1 1613 bash Ss 4698 1181 1181 pts/0 4698 nginx3_6_1 S+ 4699 4698 1181 pts/0 4698 nginx3_6_1 S+ 6802 6747 6802 pts/2 6802 bash Ss 6818 1165 6818 ? 6818 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S [root@JD ~]# kill -9 4699
第二个终端显示
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=4699, si_uid=0, si_status=SIGKILL, si_utime=0, si_stime=0} --- rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
父进程收到什么信号:SIGCHLD信号 ,子进程变成了僵尸进程Z
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E 'bash|PID|nginx' PID PPID SID TT PGRP COMMAND STAT 1181 1165 1181 pts/0 1181 bash Ss 1613 1573 1613 pts/1 1613 bash Ss 4698 1181 1181 pts/0 4698 nginx3_6_1 S+ 4699 4698 1181 pts/0 4698 nginx <defunct> Z+ 6802 6747 6802 pts/2 6802 bash Ss 9003 1165 9003 ? 9003 bash Ss 21580 1 21580 ? 21580 nginx Ss 21581 21580 21580 ? 21580 nginx S 21582 21580 21580 ? 21580 nginx S
(1.2)僵尸进程的产生、解决,SIGCHLD
僵尸进程的产生:在Unix系统中,一个子进程结束了,但是他的父进程还活着,但该父进程没有调用(wait/waitpid)函数来进行额外的处置,那么这个子进程就会变成一个僵尸进程;
僵尸进程:已经被终止,不干活了,但是依旧没有被内核丢弃掉,因为内核认为父进程可能还需要该子进程的一些信息;
僵尸进程会占用资源,作为开发者,坚决不允许僵尸进程的存在;
如何干掉僵尸进程:
a)重启电脑
b)手工的把僵尸进程的父进程kill掉,僵尸进程就会自动消失;
SIGCHLD信号:一个进程被终止或者停止时,这个信号会被发送给父进程。所以,对于源码中有fork()行为的进程,我们 应该拦截并处理SIGCHLD信号。
#include <stdio.h> #include <stdlib.h> //malloc,exit #include <unistd.h> //fork #include <signal.h> #include <sys/wait.h> //waitpid //信号处理函数 void sig_usr(int signo) { int status; switch(signo) { case SIGUSR1: printf("收到了SIGUSR1信号,进程id=%d! ",getpid()); break; case SIGCHLD: printf("收到了SIGCHLD信号,进程id=%d! ",getpid()); //这里大家学了一个新函数waitpid,有人也用wait,但老师要求大家掌握和使用waitpid即可; //这个waitpid说白了获取子进程的终止状态,这样,子进程就不会成为僵尸进程了; pid_t pid = waitpid(-1,&status,WNOHANG); //第一个参数为-1,表示等待任何子进程, //第二个参数:保存子进程的状态信息(大家如果想详细了解,可以百度一下)。 //第三个参数:提供额外选项,WNOHANG表示不要阻塞,让这个waitpid()立即返回 if(pid == 0) //子进程没结束,会立即返回这个数字,但这里应该不是这个数字 return; if(pid == -1) //这表示这个waitpid调用有错误,有错误也理解返回出去,我们管不了这么多 return; //走到这里,表示 成功,那也return吧 return; break; } //end switch } int main(int argc, char *const *argv) { pid_t pid; printf("进程开始执行! "); //先简单处理一个信号 if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数 { printf("无法捕捉SIGUSR1信号! "); exit(1); } if(signal(SIGCHLD,sig_usr) == SIG_ERR) { printf("无法捕捉SIGCHLD信号! "); exit(1); } //--------------------------------- pid = fork(); //创建一个子进程 //要判断子进程是否创建成功 if(pid < 0) { printf("子进程创建失败,很遗憾! "); exit(1); } //现在,父进程和子进程同时开始 运行了 for(;;) { sleep(1); //休息1秒 printf("休息1秒,进程id=%d! ",getpid()); } printf("再见了! "); return 0; }
waitpid();
2.fork()函数进一步认识
#include <stdio.h> #include <stdlib.h> //malloc,exit #include <unistd.h> //fork #include <signal.h> int main(int argc, char *const *argv) { fork(); //一般fork都会成功所以不判断返回值了,我们假定成功 fork(); //((fork() && fork()) || (fork() && fork())); //printf("每个实际用户ID的最大进程数=%ld ",sysconf(_SC_CHILD_MAX)); for(;;) { sleep(1); //休息1秒 printf("休息1秒,进程id=%d! ",getpid()); } printf("再见了! "); return 0; }
b)fork()产生新进程的速度非常快,fork()产生的新进程并不复制原进程的内存空间,而是和
原进程(父进程)一起共享一个内存空间,但这个内存空间的特性是“写时复制”,也就是说:
原来的进程和fork()出来的子进程可以同时、自由的读取内存,但如果子进程(父进程)对
内存进行修改的话,那么这个内存就会复制一份给该进程单独使用,以免影响到共享这个内存空间的
其他进程使用;
3.完善fork()代码
#include <stdio.h> #include <stdlib.h> //malloc,exit #include <unistd.h> //fork #include <signal.h> int g_mygbltest = 0; int main(int argc, char *const *argv) { pid_t pid; printf("进程开始执行! "); //--------------------------------- pid = fork(); //创建一个子进程 //要判断子进程是否创建成功 if(pid < 0) { printf("子进程创建失败,很遗憾! "); exit(1); } //现在,父进程和子进程同时开始 运行了 //for(;;) //{ // sleep(1); //休息1秒 // printf("休息1秒,进程id=%d! ",getpid()); //} //printf("再见了! "); //走到这里,fork()成功,执行后续代码的可能是父进程,也可能是子进程 if(pid == 0) { //子进程,因为子进程的fork()返回值会是0; //这里专门针对子进程的处理代码 while(1) { g_mygbltest++; sleep(1); //休息1秒 printf("真是太高兴了,我是子进程的,我的进程id=%d,g_mygbltest=%d! ",getpid(),g_mygbltest); } } else { //这里就是父进程,因为父进程的fork()返回值会 > 0(实际返回的是子进id程) //这是专门针对父进程的处理代码 while(1) { g_mygbltest++; sleep(5); //休息5秒 printf("......。。,我是父进程的,我的进程id=%d,g_mygbltest=%d! ",getpid(),g_mygbltest); } } return 0; }
fork()回返回两次:父进程中返回一次,子进程中返回一次,而且,fork()在父进程中返回的值和在子进程中返回的值是不同的
子进程的fork()返回值是0;
父进程的fork()返回值是新建立的子进程的ID,因为全局量g_mygbltest的值发生改变,导致主,子进程内存被单独的分开,所以每个的g_mygbltest值也不同。
(3.1)一个和fork()执行有关的逻辑判断(短路求值)
||或:有1出1,全0出0;
&&与:全1出1,有0出0;
#include <stdio.h> #include <stdlib.h> //malloc,exit #include <unistd.h> //fork #include <signal.h> int main(int argc, char *const *argv) { ((fork() && fork()) || (fork() && fork())); printf("每个实际用户ID的最大进程数=%ld ",sysconf(_SC_CHILD_MAX)); for(;;) { sleep(1); //休息1秒 printf("休息1秒,进程id=%d! ",getpid()); } printf("再见了! "); return 0; }
4.fork()失败可能性
a)系统中进程太多
缺省情况,最大的pid:32767
b)每个用户有个允许开启的进程总数
7788
六、守护进程
1.普通进程运行观察
编写程序,编译并运行
[root@JD myfiles]# vi nginx3_71 #include <stdio.h> #include <stdlib.h> //malloc #include <unistd.h> #include <signal.h> int main(int argc, char *const *argv) { printf("进程开始执行! "); for(;;) { sleep(1); //休息1秒 //printf("休息1秒,进程id=%d! ",getpid()); } printf("再见了! "); return 0; } [root@JD myfiles]# mv nginx3_71 nginx3_71.c [root@JD myfiles]# gcc -o nginx3_71 nginx3_71.c [root@JD myfiles]# ./nginx3_71 进程开始执行!
终端被占用
在第二个窗口查看进程
[root@JD ~]# ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx' 9881 8320 8320 pts/3 9881 nginx3_71 S+ ./nginx3_71
a)进程有对应的终端,如果终端退出,那么对应的进程也就消失了,它的父进程是一个bash
b)终端被占住了,你输入各种命令这个终端都没有反应;
2.守护进程基本概念
守护进程 一种长期运行的进程:这种进程在后台运行,并且不跟任何的控制终端关联;
基本特点:
a)生存期长[不是必须,但一般应该这样做],一般是操作系统启动的时候他就启动,操作系统关闭的时候他才关闭;
b)守护进程跟终端无关联,也就是说他们没有控制终端,所以你控制终端退出,也不会导致守护进程退出;
c)守护进程是在后台运行,不会占着终端,终端可以执行其他命令
linux操作系统本身是有很多的守护进程在默默的运行,维持着系统的日常活动。大概30-50个
[root@JD ~]# ps -efj UID PID PPID PGID SID C STIME TTY TIME CMD root 1 0 1 1 0 2019 ? 00:12:18 /usr/lib/systemd/systemd --switched-root --system root 2 0 0 0 0 2019 ? 00:00:00 [kthreadd] root 3 2 0 0 0 2019 ? 00:00:06 [ksoftirqd/0] root 5 2 0 0 0 2019 ? 00:00:00 [kworker/0:0H] root 7 2 0 0 0 2019 ? 00:00:14 [migration/0] root 8 2 0 0 0 2019 ? 00:00:00 [rcu_bh] root 9 2 0 0 0 2019 ? 00:12:02 [rcu_sched] root 10 2 0 0 0 2019 ? 00:00:00 [lru-add-drain] root 11 2 0 0 0 2019 ? 00:00:17 [watchdog/0] root 12 2 0 0 0 2019 ? 00:00:12 [watchdog/1] root 13 2 0 0 0 2019 ? 00:00:11 [migration/1] root 14 2 0 0 0 2019 ? 00:00:10 [ksoftirqd/1] root 16 2 0 0 0 2019 ? 00:00:00 [kworker/1:0H] root 18 2 0 0 0 2019 ? 00:00:00 [kdevtmpfs] root 19 2 0 0 0 2019 ? 00:00:00 [netns] root 20 2 0 0 0 2019 ? 00:00:01 [khungtaskd] root 21 2 0 0 0 2019 ? 00:00:00 [writeback] root 22 2 0 0 0 2019 ? 00:00:00 [kintegrityd] root 23 2 0 0 0 2019 ? 00:00:00 [bioset] root 24 2 0 0 0 2019 ? 00:00:00 [bioset] root 25 2 0 0 0 2019 ? 00:00:00 [bioset] root 26 2 0 0 0 2019 ? 00:00:00 [kblockd] root 27 2 0 0 0 2019 ? 00:00:00 [md] root 28 2 0 0 0 2019 ? 00:00:00 [edac-poller] root 29 2 0 0 0 2019 ? 00:00:00 [watchdogd] root 36 2 0 0 0 2019 ? 00:00:00 [kswapd0] root 37 2 0 0 0 2019 ? 00:00:00 [ksmd] root 38 2 0 0 0 2019 ? 00:00:10 [khugepaged] root 39 2 0 0 0 2019 ? 00:00:00 [crypto] root 47 2 0 0 0 2019 ? 00:00:00 [kthrotld] root 49 2 0 0 0 2019 ? 00:00:00 [kmpath_rdacd] root 50 2 0 0 0 2019 ? 00:00:00 [kaluad] root 51 2 0 0 0 2019 ? 00:00:00 [kpsmoused] root 52 2 0 0 0 2019 ? 00:00:00 [ipv6_addrconf] root 65 2 0 0 0 2019 ? 00:00:00 [deferwq] root 99 2 0 0 0 2019 ? 00:00:25 [kauditd] root 625 2 0 0 0 2019 ? 00:00:00 [ata_sff] root 679 2 0 0 0 2019 ? 00:00:00 [scsi_eh_0] root 684 2 0 0 0 2019 ? 00:00:00 [scsi_tmf_0] root 691 2 0 0 0 2019 ? 00:00:00 [scsi_eh_1] root 696 2 0 0 0 2019 ? 00:00:00 [scsi_tmf_1] root 747 2 0 0 0 2019 ? 00:00:00 [ttm_swap] root 1165 3087 1165 1165 0 18:07 ? 00:00:02 sshd: root@pts/0 root 1174 3087 1174 1174 0 18:07 ? 00:00:00 sshd: root@notty root 1176 1174 1176 1176 0 18:07 ? 00:00:00 /usr/libexec/openssh/sftp-server root 1181 1165 1181 1181 0 18:07 pts/0 00:00:00 -bash ...
a)ppid = 0:内核进程,跟随系统启动而启动,声明周期贯穿整个系统;
b)cmd列名字带[]这种,叫内核守护进程;
c)PID为1的老祖init(上面centos7不是init):也是系统守护进程,它负责启动各运行层次特定的系统服务;所以很多进程的PPID是init。而且这个init也负责收养孤儿进程;
d)cmd列中名字不带[]的普通守护进程(用户级守护进程)
共同点总结:
a)大多数守护进程都是以超级 用户特权运行的;
b)守护进程没有控制终端,TT这列显示?
内核守护进程以无控制终端方式启动
普通守护进程可能是守护进程调用了setsid的结果(无控制端)
3.守护进程编写规则
(1)调用umask(0)
umask是个函数,用来限制(屏蔽)一些文件权限的。
(2)fork()一个子进程(脱离终端)出来,然后父进程退出( 把终端空出来,不让终端卡住),固定套路。
fork()的目的是想成功调用setsid()来建立新会话,目的是子进程有单独的sid,而且子进程也成为了一个新进程组的组长进程。同时,子进程不关联任何终端了。
------------------------------------------------------------------------------------------------
一些概念
(3.1)文件描述符:正数,用来标识一个文件。
当你打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符(其实就是代表这个文件的),后续对这个文件的操作的一些函数,都会用到这个文件描述符作为参数。
linux中三个特殊的文件描述符,数字分别为0,1,2
0:标准输入【键盘】,对应的符号常量叫STDIN_FILENO
1:标准输出【屏幕】,对应的符号常量叫STDOUT_FILENO
2:标准错误【屏幕】,对应的符号常量叫STDERR_FILENO
类Unix操作系统,默认从STDIN_FILENO读数据,向STDOUT_FILENO来写数据,向STDERR_FILENO来写错误。
类Unix操作系统有个说法:一切皆文件,所以它把标准输入,标准输出,标准错误 都看成文件。
与其说 把 标准输入,标准输出,标准错误 都看成文件 到不如说:
象看待文件一样看待 标准输入,标准输出,标准错误
象操作文件一样操作 标准输入,标准输出,标准错误
同时,你程序一旦运行起来,这三个文件描述符0,1,2会被自动打开(自动指向对应的设备);
文件描述符虽然是数字,但是,如果我们把文件描述符直接理解成指针(指针里边保存的是地址——地址说白了也是个数字);
write(STDOUT_FILENO,"aaaabbb",6);
(3.2)输入输出重定向
输出重定向:我标准输出文件描述符,不指向屏幕了,假如我指向(重定向)一个文件
重定向,在命令行中用 >即可。
输入重定向 < 。
(3.3)空设备(黑洞)
/dev/null :是一个特殊的设备文件,它丢弃一切写入其中的数据(象黑洞一样);
-------------------------------------------------------------------------------------------------------------------
守护进程虽然可以通过终端启动,但是和终端不挂钩。
守护进程是在后台运行,它不应该从键盘上接收任何东西,也不应该把输出结果打印到屏幕或者终端上来
所以,一般按照江湖规矩,我们要把守护进程的 标准输入,标准输出,重定向到 空设备(黑洞),从而确保守护进程不从键盘接收任何东西,也不把输出结果打印到屏幕。
经常有下面代码:
int fd; fd = open("/dev/null",O_RDWR) ;//打开空设备 dup2(fd,STDIN_FILENO); //复制文件描述符 ,像个指针赋值,把第一个参数指向的内容赋给了第二个参数; dup2(fd,STDOUT_FILENO); if(fd > STDERR_FILENO) close(fd); //等价于fd = null;
(3.4)实现范例
守护进程可以用命令启动,如果想开机启动,则需要借助 系统初始化脚本来启动。
#include <stdio.h> #include <stdlib.h> //malloc #include <unistd.h> #include <signal.h> #include <sys/stat.h> #include <fcntl.h> //创建守护进程 //创建成功则返回1,否则返回-1 int ngx_daemon() { int fd; switch (fork()) //fork()子进程 { case -1: //创建子进程失败,这里可以写日志...... return -1; case 0: //子进程,走到这里,直接break; break; default: //父进程,直接退出 exit(0); } //只有子进程流程才能走到这里 if (setsid() == -1) //脱离终端,终端关闭,将跟此子进程无关 { //记录错误日志...... return -1; } umask(0); //设置为0,不要让它来限制文件权限,以免引起混乱 fd = open("/dev/null", O_RDWR); //打开黑洞设备,以读写方式打开 if (fd == -1) { //记录错误日志...... return -1; } if (dup2(fd, STDIN_FILENO) == -1) //先关闭STDIN_FILENO[这是规矩,已经打开的描述符,动他之前,先close],类似于指针指向null,让/dev/null成为标准输入; { //记录错误日志...... return -1; } if (dup2(fd, STDOUT_FILENO) == -1) //先关闭STDIN_FILENO,类似于指针指向null,让/dev/null成为标准输出; { //记录错误日志...... return -1; } if (fd > STDERR_FILENO) //fd应该是3,这个应该成立 { if (close(fd) == -1) //释放资源这样这个文件描述符就可以被复用;不然这个数字【文件描述符】会被一直占着; { //记录错误日志...... return -1; } } return 1; } int main(int argc, char *const *argv) { if(ngx_daemon() != 1) { //创建守护进程失败,可以做失败后的处理比如写日志等等 return 1; } else { //创建守护进程成功,执行守护进程中要干的活 for(;;) { sleep(1); //休息1秒 printf("休息1秒,进程id=%d! ",getpid()); //你就算打印也没用,现在标准输出指向黑洞(/dev/null),打印不出任何结果【不显示任何结果】 } } return 0; }
4.守护进程不会收到的信号
内核发给你,另外的进程发给你的;
(4.1)SIGHUP信号
守护进程不会收到来自内核的 SIGHUP 信号; 潜台词就是 如果守护进程收到了 SIGHUP信号,那么肯定是另外的进程发给你的。
很多守护进程把这个信号作为通知信号,表示配置文件已经发生改动,守护进程应该重新读入其配置文件。
4.2)SIGINT、SIGWINCH信号
守护进程不会收到来自内核的 SIGINT(ctrl+C),SIGWINCH(终端窗口大小改变) 信号;
5.守护进程和后台进程的区别
(1)守护进程和终端不挂钩;后台进程能往终端上输出东西(和终端挂钩);
(2)守护进程关闭终端时不受影响,守护进程会随着终端的退出而退出;
(3)其他的,大家自己总结;