赞
踩
src目录,源代码目录
1. core,核心代码
2. event,event(事件)模块相关代码
3. http,http(Web服务)模块相关代码
4. mail,邮件模块相关代码
5. os,操作系统相关代码
6. stream,流处理相关代码
Visual Studio Code
搜索“main(”
#include <stdio.h>
int main(int argc, char *const *argv){
printf("study together!\n");
return 0;
}
# gcc 3.1.c -o 3.1 && ./3.1
Linux下文件权限符"drwxrwxrwx"
r
), write(w
), execute(x
)泛读Nginx中重要代码,提取使用。
每个虚拟终端(pts)连到虚拟机,都会打开一个bash进程(shell壳)。输入命令黑窗就是bash(shell,命令行解释器是个可执行程序),用于解释用户输入的命令。
ps -ef | grep bash
whereis bash
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *const *argv){
printf("开始!\n");
while(1){
sleep(1);
printf("休息1秒!\n");
}
printf("退出!\n");
return 0;
}
gcc -o 3.2 3.2.c && ./3.2
ps -la
#l,长显示输出格式
#a,显示终端上的所有进程,包括其他终端上的进程
随着终端退出,该终端上运行的进程(nginx)也退出了。
init进程(操作系统启动时内核自己创建出来的,具有超级用户特权),进程ID(PID)是1。
—fork➡
—exec➡
—fork➡
—fork➡
进程ID(PID),父进程ID(PPID),进程组ID(PGRP,与进程组ID相同的进程叫进程组组长),会话ID(SID,等于会话首进程ID)。
通常一个bash(shell)上所有进程都属于同一个会话。
会话首进程(session leader,创建会话的进程),通常是这个shell(bash)。
ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx'
#e,所有进程
#o,指定显示列
#sid,session id;tty,终端;pgrp,进程组;comm,执行的命令。
#-E,开启扩展正则表达式,'bash|PID|nginx'中‘|’表示或者
Xshell终端断开,系统会给会话首进程(这个bash进程)发送SIGHUP信号(终端断开),bash进程收到SIGHUP信号后,将这个信号发给session里的所有进程(最后发给自己),收到SIGHUP信号的进程默认动作就是退出。
调试分析诊断工具,跟踪程序执行时进程的系统调用(system call)和所接收到的信号。
sudo strace -e trace=signal -p 1157
#si_pid,发送信号者(谁发来的信号)
#kill,发送信号
跟踪PID1157的进程上与信号(signal)有关的系统调用。
int abc = 4294966139;
等价于
int abc = -1157;
kill -1157(负值表示给进程组发送信号)表示给进程组1157发送信号。
kill sid(通常会话id和bash进程id一致),bash收到SIGHUP信号时,先将该信号发给session内其它进程(各个进程组内进程,只要session ID相同),然后将SIGHUP信号发送给自己。
gcc nginx.c -o nginx
ps -eo pid,ppid,sid,tty,pgrp,comm | grep -E 'bash|PID|nginx'
ps -ef | grep -E 'bash|PID|nginx'
(1)拦截SIGHUP信号
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *const *argv){
signal(SIGHUP, SIG_IGN);//ignore SIGUP
printf("begin!\n");
while(1){
sleep(1);
printf("sleep 1s!\n");
}
printf("end!\n");
return 0;
}
gcc 3.3.c -o 3.3 && ./3.3
ps -ef | grep '3\.3'
终端(bash)断开后,该进程TT变为?(孤儿进程),PPID变为1(init进程收养)。
(2)进程和终端bash进程在不同session中
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *const *argv) {
setsid();
printf("begin!\n");
while(1){
sleep(1);
printf("sleep 1s!\n");
}
printf("end!\n");
return 0;
}
进程组组长调用setsid()无效。
gcc 3.4.c -o 3.4 && ./3.4
ps -ef | grep '3\.4'
#include <stdio.h> #include <unistd.h> #include <signal.h> int main(int argc, char *const *argv) { printf("开始!\n"); pid_t pid = fork(); if(pid<0){ printf("fork进程出错!\n"); } else if(pid==0) { //子进程 setsid();//子进程调用setsid()有效,TT变为?。 while(1) { sleep(1); printf("子进程休息1s!\n");//注意即使TT为?,当前终端(关联终端)仍会输出 } } else {//父进程 //setsid();//父进程,进程组组长,调用setsid()无效。 while(1) { sleep(1); printf("父进程休息1s!\n"); } } printf("退出!\n"); return 0; }
gcc 3.5.c -o 3.5 && ./3.5
ps -ef | grep '3\.5'
关闭当前终端后,父进程退出,子进程仍运行,变成孤儿进程。
(3)setsid命令启动进程
使启动的进程在新的session中,终端关闭时不会退出。
nginx.c
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char *const *argv){
printf("begin!\n");
while(1){
sleep(1);
printf("sleep 1s!\n");
}
printf("end!\n");
return 0;
}
gcc nginx.c -o nginx
setsid ./nginx
PPID为1,TT为?。
(4)nohup(no hang up不挂断)
nohup命令启动的程序会忽略SIGHUP信号,与程序内编写忽略SIGHUP信号等价。
nohup ./nginx
nohup命令将输出重定向到当前目录的nohup.out文件中。
关闭终端后,进程PPID为1,TT为?。
./nginx &
#fg,切换当前后台进程到前台
#fg jobnumber(是命令编号),选定进程
#bg jobnumber,后台暂停进程后台继续执行
#jobs,查看后台进程
#kill %jobnumber(jobs)
#kill pid(ps)
#Ctrl+C,终止前台
#Ctrl+Z,前台变后台且暂停
#nohup ./nginx &,后台运行且终端断开进程不退出
后台执行,终端能同时做其他事情;
Ctrl+C(中断键)能停止前台进程,无法终止后台进程;
终端连接断开,后台进程被关闭(退出)。
信号(突然事件,异步发生,“软件中断”),就是一个通知(事件通知),用来通知进程发生了事件。
信号产生:
进程发送给其他进程或自己
ngnix热升级启动新master进程时,向旧msater进程发送信号。
内核发送给进程
①键盘输入命令动作,Ctrl+C(中断信号);kill命令。
②内存访问异常。除数为0,硬件检测到并通知内核。
信号名,SIG开头(宏定义),数字(1开始编号),signal.h。
sudo find / -name "signal.h" | xargs grep -in "SIGHUP"
#xargs,打开文件找
#i,忽略大小写
#n,显示行号
向进程发送信号。
绝大多数信号的默认动作,是终止收到信号的进程。
kill pid(默认-15),向进程发送SIGTERM终止信号
kill -1 pid,向进程发送SIGHUP挂断信号
kill -2 pid,向进程发送SIGINT中断信号
kill参数 | 该参数发出信号 | 内核默认动作 |
---|---|---|
-1 | SIGHUP(连接断开) | 终止掉进程(进程没了) |
-2 | SIGINT(终端中断符,Ctrl+C) | 终止掉进程(进程没了) |
-3 | SIGQUIT(终端退出符,Ctrl+\) | 终止掉进程(进程没了) |
-9 | SIGHUPKILL(终止) | 终止掉进程(进程没了) |
-18 | SIGCONT(暂停进程继续) | 忽略(进程运行不受影响) |
-19 | SIGSTOP(停止),可用SIGCONT继续,但任务被放到了后台 | 停止进程(不是终止,进程还在) |
-20 | SIGSTP(终端停止符,Ctrl+Z),可用SIGCONT继续,但任务被放到了后台 | 停止进程(不是终止,进程还在) |
ps -eo pid,ppid,sid,tty,pgrp,comm,stat | grep -E "bash|PID|nginx"
# stat,查看进程状态
ps -ef| grep -E "bash|PID|nginx"
ps aux| grep -E "bash|PID|nginx"
#aux,BSD风格的显示格式
进程状态 | 含义 |
---|---|
D | 不可中断的休眠状态(通常是I/O的进程),可以处理信号,有延迟 |
R | 可执行状态 & 运行状态(在运行队列里的状态) |
S | 可中断的休眠状态之中(等待某事件完成),可以处理信号 |
T | 停止或被追踪(被作业控制信号锁停止) |
Z | 僵尸进程 |
X | 死掉的进程 |
< | 高优先级进程 |
N | 低优先级进程 |
L | 有些页被锁进内存 |
s | session leader,其下有子进程 |
t | 追踪期间被调试器停止 |
+ | 位于前台的进程组 |
信号名称 | 信号含义 |
---|---|
SIGHUP(连接断开) | 终端断开信号。Xshell断开发送,-1,发送到终端所在的会话首进程。 |
SIGALRM(定时器超时) | alarm创建定时器,超时产生。 |
SIGINT(中断) | Ctrl+C,终止进程。后台进程忽略。 |
SIGSEGV(无效内存) | SEGV,段违例(segmentation violation),内存访问异常,除数为0等,硬件检测到并通知内核。 |
SIGIO(异步I/O) | 通信套接口有数据到达或发生异步错误,内核向进程通知该信号。 |
SIGCHILD(子进程改变) | 进程终止或停止,该信号发送给父进程。 |
SIGUSR1、SIGUSR2(用户定义信号) | 用于应用程序。 |
SIGTERM(终止) | kill pid产生,退出前处理工作,优雅退出。 |
SIGKILL(终止) | 不能被忽略,不能被进程本身捕捉,杀死任意进程。 |
SIGSTOP(停止) | 不能被忽略,不能被进程本身捕捉,停止执行进程。 SIGCONT信号继续执行,但会放入后台。 |
SIGQUIT(终端退出符) | Ctrl+\,shell会将后台进程对该信号的处理设置为忽略(后台进程不会收到该信号)。 |
SIGCONT(使暂停进程继续) | 继续运行暂停的进程。 |
SIGTSTP(终端停止符) | Ctrl+Z,进程被停止且放入后台,可以用SIGCONT继续执行。 |
执行系统默认动作
绝大多数情况,内核(操作系统)会杀死(或暂停)进程。
忽略此信号
2个信号(特权信号)不能忽略,SIGKILL(-9,终止)和SIGSTOP(-19,暂停)。
signal(SIGHUP, SIG_IGN);
(1)操作系统/内核——软件。用于控制计算机硬件资源,提供应用程序运行的环境。程序,要么运行在用户态,要么运行在内核态。一般情况下运行在用户态,程序执行特殊代码时,切换到内核态(操作系统控制)。
应用程序通过shell、公共函数库进行系统调用(内核的对外接口,访问磁盘,内存等资源)。
(2)系统调用函数库,系统内部高度封装,调用即可。
(3)bash(borne again shell),shell的一种,命令行解释器。位于系统调用和应用程序之间,分隔系统调用和应用程序,胶水(将系统调用和应用程序粘在一起)作用。
whereis bash
ps
exit
操作系统内核部分有进程管理(CPU)、内存管理(RAM)、文件系统(磁盘/存储设备)、设备驱动(各种终端设备)、网络(网卡)等各种资源的管理。操作系统内核和用户空间(应用程序),通过系统调用(system call)接口实现对各种资源功能的调用。
进程大部分时间处于用户态下,需要内核提供服务才切换到内核态,内核态任务处理完成后,又切换回用户态。
分用户态,内核态的目的:
①用户态权限小,内核态权限大。权限大,能做危险事情(处理内存、处理时钟等),交由内核态处理,避免系统处于危险状态甚至崩溃,只提供调用接口。
②系统提供并统一管理调用接口。系统资源有限,避免发生访问冲突,资源耗尽等导致系统崩溃,系统接口用于减少有限资源访问和使用的冲突。
用户态切换到内核态时间:
①系统调用。malloc
②异常事件。收到信号,调用信号处理函数,系统跳到内核态做一些调用该信号处理函数的准备工作
③外围设备的中断。外围设备完成用户的请求操作后,会向CPU发出中断信号,此时CPU就会暂停执行下一条即将执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令处于用户态下,就会发生用户态到内核态的转换。
忽略或捕捉信号。
#include <stdio.h> #include <unistd.h> #include <signal.h> void sig_usr(int signo) { if(signo == SIGUSR1) { printf("收到了SIGUSR1信号!\n"); } else if(signo == SIGUSR1) { printf("收到了SIGUSR2信号!\n"); } else { printf("收到了未捕捉的信号%d!\n", signo); } } int main(int argc, char *const *argv) { printf("开始!\n"); if(signal(SIGUSR1, sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR1信号!\n"); } if(signal(SIGUSR2, sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR2信号!\n"); } while(1) { sleep(1); printf("休息1s!\n"); } printf("退出!\n"); return 0; }
kill -USR1 pid
kill -USR2 pid
进程执行过程中,信号到来引发的用户态和内核态之间的切换过程:
#include <stdio.h> #include <unistd.h> #include <signal.h> int g_mysign = 0; void muNEfunc(int value) { g_mysign = value; } void sig_usr(int signo) { muNEfunc(22); if(signo == SIGUSR1) { printf("收到了SIGUSR1信号!\n"); } else if(signo == SIGUSR1) { printf("收到了SIGUSR2信号!\n"); } else { printf("收到了未捕捉的信号%d!\n", signo); } } int main(int argc, char *const *argv) { printf("开始!\n"); if(signal(SIGUSR1, sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR1信号!\n"); } if(signal(SIGUSR2, sig_usr) == SIG_ERR) { printf("无法捕捉SIGUSR2信号!\n"); } while(1) { sleep(1); printf("休息1s!\n"); muNEfunc(15);//此时收到SIGUSR1信号会改变g_mysign的值 printf("g_mysign=%d\n", g_mysign); } printf("退出!\n"); return 0; }
可重入的函数(或异步信号安全的函数),指在信号处理函数中调用是安全的函数。
可重入函数(即可以被中断的函数)可以被一个以上的任务调用,而不担心数据破坏。可重入函数在任何时候都可以被中断,而一段时间之后又可以恢复运行,而相应的数据不会破坏或者丢失。
信号处理函数中处理方法:
(1)尽量调用简单语句,尽量不要调用系统函数(malloc,printf等非可重入函数)。
(2)只调用可重入系统函数(搜索Linux可重入函数)。
(3)调用了可能修改errno值的可重入系统函数,先备份errno,信号处理函数返回前恢复errno值。
#include <errno.h>
void sig_usr(int signo) {
int myerrno = errno;//备份errno
//调用可能修改errno值的可重入系统函数
errno = myerrno;//还原errno
}
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> void sig_usr(int signo) { if(signo==SIGUSR1) { printf("SIGUSR1\n"); }else if(signo==SIGUSR2) { printf("SIGUSR2\n"); }else{ printf("Unknown Signal: %d\n", signo); } } int main(int argc, char *const *argv) { if(signal(SIGUSR1, sig_usr) == SIG_ERR) { printf("Catch SIGUSR1 Error\n"); } if(signal(SIGUSR2, sig_usr) == SIG_ERR) { printf("Catch SIGUSR2 Error\n"); } while(1) { int *p; p = (int *)malloc(sizeof(int)); sleep(1); printf("Sleep 1s\n"); free(p); } printf("Over\n"); return 0; }
kill -USR1 pid
kill -USR2 pid
正常运行。
修改sig_usr。
void sig_usr(int signo) { int *p = (int *)malloc(sizeof(int)); //非可重入函数 //像printf()和malloc()之类的标准库函数,它们会使用全局的数据(比如malloc()需要使用进程的堆数据结构)是不可重入的,即使加锁也无法解决,加锁可以使得库函数Tread-safe,但是无法解决可重入。 //malloc并不是简单的一条指令,内部比如有加锁和开锁功能,main函数中调用malloc函数中刚加锁,然后信号处理函数中断,sig_usr调用malloc,再一次加锁,发生错误。 free(p); if(signo==SIGUSR1) { printf("SIGUSR1\n"); }else if(signo==SIGUSR2) { printf("SIGUSR2\n"); }else{ printf("Unknown Signal: %d\n", signo); } }
当收到某个信号,启动执行信号处理函数的时候,通常会“屏蔽/阻塞”其后的相同信号,直到信号处理函数执行结束。
进程会记录当前阻塞了哪些信号。收到某个信号时,系统将标记正在处理该信号的标志设置为1,然后去执行信号处理函数,若执行期间再次收到该信号,系统检测到该信号的标志已经为1,后面的相同信号就需要排队等待(等待调用信号处理函数处理)或直接被忽略(丢失)。当信号处理函数执行完毕,再把该信号对应标志设置回0。
sigset_t表示信号集数据类型。
typedef struct {
unsigned long sig[2];//8*4*2个信号,来(1)或没来(0)
}sigset_t;
(1)sigemptyset
所有信号清零,所有信号没来。
(2)sigfillset
所有信号设置为1,到来的任何信号都会排队或被忽略。
(3)sigaddset增加信号(设置为1),sigdelset删除信号(设置为0)
(4)sigprocmask设置进程信号集(进程有默认信号集),sigismember检测信号集特定信号是否被置位。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> //信号处理函数,可被中断 //即信号处理函数注册多个信号,当处理第一个信号时,可被第二个信号中断 void sig_quit(int signo) { printf("SIGQUIT\n"); /* //第二次收到SIGQUIT信号时,采用默认处理,即显示Quit后立即终止。 if(signal(SIGQUIT, SIG_DFL) == SIG_ERR) { printf("SIGQUIT Default Handle Error!\n"); exit(1); } */ } int main(int argc, char *const *argv) { sigset_t newmask;//新信号集 sigset_t oldmask;//旧信号集 sigset_t pendmask; //Ctrl+\, SIGQUIT if(signal(SIGQUIT, sig_quit) == SIG_ERR) { //注册信号对应的信号处理函数 printf("Catch SIGQUIT Error!\n"); exit(1); } //newmask信号集中所有信号清零(这些信号都没有来) sigemptyset(&newmask); //newmask信号集中SIGQUIT信号置1,再来SIGQUIT信号进程收不到 sigaddset(&newmask, SIGQUIT); //设置进程信号集 //SIG_BLOCK表示,设置进程新的信号屏蔽字newmask为进程当前信号屏蔽字(开始全部为0)和newmask的并集(或),(屏蔽了SIGQUIT信号,只有SIGQUIT位为1) //oldmask,保存老的信号集(全部为0,无信号屏蔽),用于恢复。 if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) { printf("sigprocmask(SIG_BLOCK) Error!\n"); exit(1); } printf("sleep 10s--begin--, cannot receive SIGQUIT!\n"); sleep(10); printf("sleep 10s--end--, cannot receive SIGQUIT!\n"); if(sigismember(&newmask, SIGQUIT)) { printf("SIGQUIT is shielded\n");//SIGQUIT信号被进程屏蔽 } else { printf("SIGQUIT is not shielded"); } if(sigismember(&newmask, SIGHUP)) { printf("SIGHUP is shielded"); } else { printf("SIGHUP is not shielded\n");// } //取消SIGQUIT信号的屏蔽(阻塞)——信号集还原 //SIG_SETMASK表示,设置当前进程信号屏蔽字为oldmask(全0,无信号屏蔽) if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) { printf("sigprocmask(SIG_SETMASK) Error!\n"); exit(1); } printf("sigprocmask(SIG_SETMASK) Success!\n"); if(sigismember(&oldmask, SIGQUIT)) { printf("SIGQUIT is shielded\n"); } else { printf("SIGQUIT is not shielded, sleep 10s\n"); int mysl = sleep(10);//收到信号会中断休眠,返回剩下时间 if(mysl>0) printf("left %ds\n", mysl); } printf("Over\n"); return 0; }
运行进程时连续输入5个Ctrl+\(SIGQUIT),进程却只收到1个,说明信号有等待机制(信号SIGQUIT被屏蔽时发出SIGQUIT信号,后续该信号屏蔽被取消时能够收到),但无法允许多个相同的信号排队等待(5个SIGQUIT信号被合并成1个)
多个进程可共享同一个可执行文件(程序),进程一般定义为程序执行的一个实例。fork用于进程中创建子进程,当该子进程创建时,它从fork函数的下一条语句(或者说从fork的返回处)开始执行与父进程相同的代码。fork后父子进程执行的先后顺序并不确定(与内核调度算法有关)。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> //信号处理函数 void sig_usr(int signo) { printf("Signal=%d, PID=%d\n", signo, getpid()); } int main(int argc, char *const *argv) { pid_t pid; printf("Start\n"); if(signal(SIGUSR1, sig_usr) == SIG_ERR) { printf("Catch SIGUSR1 Error\n"); exit(1); } pid = fork(); if(pid<0) { printf("Create Process Error\n"); exit(1); } //父子进程同时运行 while(1) { sleep(1); printf("Sleep 1s, PID=%d\n", getpid()); } printf("Over, PID=%d\n", getpid()); return 0; }
子进程被杀死(SIGKILL)后,父进程收到了SIGCHILD信号,子进程变为僵尸进程(COMMAND显示defunct(失效),STAT显示Z)
子进程终止了,父进程还活着,但该父进程未调用函数(wait/waitpid)来进行额外处置(处置子进程终止这件事),子进程将变成僵尸进程(未释放资源,比如PID)。
杀掉父进程,僵尸进程会自动消失;
子进程终止或停止时,父进程会收到SIGCHILD信号,可以在信号处理函数中调用wait/waitpid函数,释放僵尸进程。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> //信号处理函数 void sig_usr(int signo) { int status; switch(signo) { case SIGUSR1: printf("SIGUSR1, PID=%d\n", getpid()); break; case SIGCHILD: printf("SIGCHILD, PID=%d\n", getpid()); //-1,任意子进程 //status,保存子进程的状态信息 //WNOHANG,非阻塞 pid_t pid = waitpid(-1, &status, WNOHANG); //pid==0,子进程未结束,这里不会为0,子进程结束才触发SIGCHILD信号处理函数。 //pid==-1,waitpid调用有错误 break; } } int main(int argc, char *const *argv) { pid_t pid; if(signal(SIGUSR1, sig_usr) == SIG_ERR) { printf("Catch SIGUSR1 Error\n"); exit(1); } if(signal(SIGCHILD, sig_usr) == SIG_ERR) { printf("Catch SIGCHILD Error\n"); exit(1); } pid = fork(); if(pid<0) { printf("Create Process Error\n"); exit(1); } //父子进程同时运行 while(1) { sleep(1); printf("Sleep 1s, PID=%d\n", getpid()); } printf("Over, PID=%d\n", getpid()); return 0; }
fork产生的子进程和父进程共享内存空间,采用写时复制(修改内存后,复制一份内存给进程单独使用,避免相互影响),提高执行效率。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> //4个进程 int main(int argc, char *const *argv) { fork(); fork(); while(1) { sleep(1); printf("Sleep 1s, PID=%d\n", getpid()); } printf("Over, PID=%d\n", getpid()); return 0; }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> int g_mygbltest = 0; int main(int argc, char *const *argv) { pid_t pid; printf("Start\n"); pid = fork(); if(pid<0) { printf("Create Process Error\n"); exit(1); } if(pid==0) {//子进程 while(1) { g_mygbltest++; sleep(1); printf("Child Process, PID=%d, g_mygbltest=%d\n", getpid(), g_mygbltest); } } else {//父进程 while(1) { g_mygbltest++; sleep(1); printf("Parent Process, PID=%d, g_mygbltest=%d\n", getpid(), g_mygbltest); } } printf("Over, PID=%d\n", getpid()); return 0; }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> //7个进程 int main(int argc, char *const *argv) { (fork() && fork()) || (fork() && fork()); while(1) { sleep(1); printf("Sleep 1s, PID=%d\n", getpid()); } printf("Over, PID=%d\n", getpid()); return 0; }
( | ( | fork() | && | fork() | ) | ) | || | ( | ( | fork() | && | fork() | ) | ) | 进程 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
①pid=0 False | ①①pid=0 False | 1 | |||||||||||||
①②pid>0 True | ①②①pid=0 False | 2 | |||||||||||||
①②②pid>0 True | 3 | ||||||||||||||
②pid>0 True | ②①pid=0 False | ②①①pid=0 False | 4 | ||||||||||||
②①②pid>0 True | ②①②①pid=0 False | 5 | |||||||||||||
②①②②pid>0 True | 6 | ||||||||||||||
②②pid>0 True | 7 |
printf("每个用户允许创建的最大进程数=%ld\n", sysconf(_SC_CHILD_MAX));
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
//7个进程
int main(int argc, char *const *argv) {
while(1) {
sleep(1);
printf("Sleep 1s, PID=%d\n", getpid());
}
printf("Over, PID=%d\n", getpid());
return 0;
}
终端退出,进程消失。
运行进程的终端被占用,不能做其他事。
一种长期在后台运行,不与任何控制终端关联的进程。
基本特点:
ps -efj
#e,所有进程
#f,完整格式信息
#j,显示与任务或者作业有关信息
PPID为0的是内核进程,超级用户特权进程,随系统启动。
CMD列中,中括号[]为内核守护进程,kthreadd是其他内核守护进程的父进程,PID为2。
老祖宗进程init,系统守护进程,收养孤儿进程,PID为1.
不带中括号[]的普通守护进程(用户级守护进程):
rsyslogd,系统消息日志有关的进程
cron,某个时间执行命令
sshd,安全远程登录进程
守护进程的共同点:
调用umask(0)
umask函数,限制(屏蔽)一些文件权限。umask(0),不让它限制文件权限(守护进程可能要创建文件),并给这个文件设置一定的权限。
fork子进程,然后退出父进程
(1)文件描述符,标识文件
0,标准输入,键盘输入,STDIN_FILENO
1,标准输出,屏幕显示,STDOUT_FILENO
2,错误输出,屏幕显示,STDERR_FILENO
程序运行时,自动打开3个文件描述符。
(2)输入输出重定向
ls -la > myoutfile
cat < myoutfile
cat < myoutfile > myoutfile2
(3)空设备
“/deb/null”,黑洞设备,特殊的设备文件,丢弃(吞噬)一切写入其中的数据。
cat nginx.c > /dev/null
//读写方式打开黑洞设备 int fd = open("/dev/null", O_RDWR); //NULL //先关闭STDIN_FILENO(已经打开的文件描述符,改动之前先关闭) //让STDIN_FILENO指向fd所指的内容(/dev/null) //类似于指针指向NULL,让/dev/null成为标准输入 dup2(fd, STDIN_FILENO); //close(STDIN_FILENO), STDIN_FILENO=fd; //先关闭STDOUT_FILENO(已经打开的文件描述符,改动之前先关闭) //让STDOUT_FILENO指向fd所指的内容(/dev/null) //类似于指针指向NULL,让/dev/null成为标准输出 dup2(fd, STDOUT_FILENO); //close(STDOUT_FILENO), STDOUT_FILENO=fd; //通过STDIN_FILENO,STDOUT_FILENO输入输出都会被/dev/null吞噬 //STDERR_FILENO未关闭 if(fd>STDERR_FILENO) close(fd); //fd = null,释放资源,这个文件描述符fd可以被复用。
(4)范例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/stat.h> #include <fcntl.h> //创建守护进程 int ngx_daemon() { int fd; switch(fork()) { case -1: //创建子进程失败,这里可以写日志...... return -1; case 0: //子进程,直接跳出 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,/dev/null成为标准输入 //记录错误日志...... return -1; } if(dup2(fd, STDOUT_FILENO) == -1){ //关闭STDOUT_FILENO,/dev/null成为标准输出 //记录错误日志...... return -1; } if(fd>STDERR_FILENO) { if(close(fd) == -1){ //释放资源,文件描述符(数字)可以被复用 //记录错误日志...... return -1; } } return 1; } int main(int argc, char *const *argv) { if(ngx_daemon() != 1) { //创建守护进程失败,可以写日志等。 return 1; } while(1) { //创建守护进程成功,执行守护进程中工作 sleep(1); //无任何显示结果 printf("Sleep 1s, PID=%d\n", getpid()); } return 0; }
开机启动守护进程,可以拥有超级用户特权。
SIGHUP信号
守护进程不会收到来自内核的SIGHUP信号,如果收到,肯定是进程发送过来的。
SIGHUP信号,连接(终端)断开信号,终端检测到连接断开,会发送SIGHUP信号到终端所在的会话首进程。
守护进程和终端不关联,终端断开不会收到SIGHUP信号,可以将该信号挪作他用,比如配置文件改动,守护进程重新读入配置文件。sudo ./nginx -s reload,向已启动master进程发送SIGHUP信号(-1)。
SIGINT、SIGWINCH信号
守护进程不会收到来自内核的SIGINT、SIGWINCH信号,如果收到,肯定是进程发送过来的。
SIGINT,终端中断符,Ctrl+C(中断组合键)。
SIGWINCH,终端大小改变。
这些信号可以拿来和守护进程通信。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。