1. 信号理论基础
**信号共性:
简单、不能携带大量信息、满足条件才发送
**信号的特质:
信号是软件层面上的"中断"。一旦型号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,在继续执行后续指令。
所有信号的产生以及处理全部都是有【内存】完成的
*** 信号产生
1. 按键产生,ctrl+z、ctrl+c
2. 系统调用产生, 如果kill
3. 软件条件产生, sleep
4. 硬件异常产生, 段错误、
段错误: 1. 访问了不是自己内存, 比如malloc自己申请了区域,访问malloc区域外了
2. 对只读区进行修改 char* ch="abc" ch[0]='a'
5.命令产生,比如kill命令
未决: 产生与递达之间状态
递达: 产生送到内核,直接被内核处理掉
信号处理方式: 默认、忽略、捕捉(自定义)
linux内核进程控制块pcb是一个结构体,task_struct,除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,
还包含信号相关信息,阻塞信号和未决信号
例子:
比如ctrl+c发送2号信号,那么未决信号集把第二位设置为1,那么内核发现第二未变成1了,处理2号信号,处理[默认、忽略、捕捉(自定义)]完毕第二位变成0
未决信号集把第二位设置为1
完毕第二位变成0
如果由于某种原因,把2号信号设置屏蔽,那么信号屏蔽字中2号变成1,那么信号不能递到内核,那么信号不能处理
等到屏蔽字中2号变成0,那么信号才可以被处理,未决信号集才把第二位设置为0
信号阻塞
2. 常用信号集
信号4要素:
信号编号、名称、 对应事件、默认处理动作
man 7 signal
Signal Value Action Comment
──────────────────────────────────────────────────────────────────────
SIGHUP 1 Term Hangup detected on controlling terminal
or death of controlling process
SIGINT 2 Term Interrupt from keyboard
SIGQUIT 3 Core Quit from keyboard
SIGILL 4 Core Illegal Instruction
SIGABRT 6 Core Abort signal from abort(3)
SIGFPE 8 Core Floating point exception
SIGKILL 9 Term Kill signal
SIGSEGV 11 Core Invalid memory reference
SIGPIPE 13 Term Broken pipe: write to pipe with no
readers
SIGALRM 14 Term Timer signal from alarm(2)
SIGTERM 15 Term Termination signal
SIGUSR1 30,10,16 Term User-defined signal 1
SIGUSR2 31,12,17 Term User-defined signal 2
SIGCHLD 20,17,18 Ign Child stopped or terminated
SIGCONT 19,18,25 Cont Continue if stopped
SIGSTOP 17,19,23 Stop Stop process
SIGTSTP 18,20,24 Stop Stop typed at terminal
SIGTTIN 21,21,26 Stop Terminal input for background process
SIGTTOU 22,22,27 Stop Terminal output for background processSIGUSR1 30,10,16 Term User-defined signal 1 不同操作系统对应value不一样,名称一样
1 SIGHUP: 当用户退出shell时,由该shell启动所有进程将接收这个信号,默认动作为停止进程
2 SIGINT: 相当于ctrl+c(终止/中断), 默认是终止进程
3 SIGQUIT: 相当于ctrl+\(退出), 默认是终止进程
6 SIGBUS: 总线错误,非法访问内存地址,默认终止进程产生core文件[用于gdb调试能够找到错误所在行,那么因为程序在终止的时候把错误信息写入了core文件]
8 SIGFPE: 发生致命运算错误时发生, 包括溢出,除数为0,默认终止进程产生core文件
10 SIGUSR1: 用户定义信号, 程序员可以在程序中定义并且使用该信号, 默认动作是终止进程
12 SIGUSR2: 用户定义信号, 程序员可以在程序中定义并且使用该信号, 默认动作是终止进程
*****
9 SIGKILL: 无条件终止进程, 不能被忽略、阻塞,上面的ctrl+c信号,如果把该信号捕捉掉,那么进程无法杀死,如果是病毒,完了
19 SIGSTOP:无条件停止
11 SIGSEGV: 进程在进程无效内存访问,默认终止进程产生core文件
13 SIGPIPE: 向一个没有读端的管道写数据,默认终止进程
14 SIGALRM: 定时器超时,超时时间 有系统调用alarm 设置,默认动作终止进程
17 SIGCHLD: 子进程终止时候、子进程接收到SIGSTOP信号停止时、子进程处停止态,接收到SIGCONT后唤醒时,告诉父进程,父进程可以去收尸了,默认忽略
20 SIGTSTP: ctrl+z. (暂停/停止)
3. linux 信号api函数
3.1. alarm 函数、 setitimer 函数
3.1.1 alarm函数
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
//alarm
int main00001(){
alarm(5); // 5s后发送SIGALRM 信号,退出程序
sleep(1);
int i=alarm(2); //返回4,返回上次剩余时间
printf("---%d---\n",i);
// alarm(0); // 取消闹钟
int j=0;
while(1){
printf("%s---%d----\n","hello....",j++);
}
/**
* time ./single.o
* 程序时间分析:
* real 0m3.014s
user 0m0.024s
sys 0m1.171s
程序运行时间= 系统时间+ 用户时间 + 等待时间
还有1s中是等待时间, 等待cpu、内存、终端输出,终端被占用
time ./single.o > out : 可以写如文件的数据是上面打印到终端的20倍
cat out | wc -l
*/
return 0;
}
3.1.2 setitimer 函数, alarm只是简单定时器,setitimer 可以设置间隔轮巡
void myfunc(){
printf("hello world\n");
}
/**
* setitimer 函数
*/
int main00005(){
struct itimerval it,oldit;
signal(SIGALRM,myfunc);
// 2s 到了发信号
it.it_value.tv_sec=2;
it.it_value.tv_usec=0;
// 没隔 5s发信号
it.it_interval.tv_sec=5;
it.it_interval.tv_usec=0;
if(setitimer(ITIMER_REAL,&it,&oldit) ==-1){
perror("setitimer error");
exit(-1);
}
while(1){};
return 0;
}
3.2. kill 命令、函数
/***
* kill 命令 发送信号(不仅仅用于杀死) kill -9 pid
* kill 函数
*/
int main00002(){
//int kill(pid_t pid, int sig);
// pid > 0 : 杀死指定进程
// = 0 : 杀死调用kill函数那个进程同时处于同一个进程组的进程
// <-1: 区绝对值,杀死该绝对值所对应的进程组合
// -1 : 发送信号给有权限发送的所有进程
__pid_t pid=fork();
if(pid==-1){
perror("fork fail");
exit(-1);
}else if(pid>1){
// father
while(1){}
}else if(pid==0){
// child
sleep(2);
// 子进程杀死父进程
kill(getppid(),SIGKILL);
}
return 0;
}
<-1: 区绝对值,杀死该绝对值所对应的进程组合
// <-1: 区绝对值,杀死该绝对值所对应的进程组合
int main00003(){
__pid_t pid=fork();
if(pid==-1){
perror("fork fail");
exit(-1);
}else if(pid>1){
// father
while(1){}
}else if(pid==0){
// child
while(1){}
// 子进程杀死父进程
//kill(getppid(),SIGKILL);
}
return 0;
}
ps ajx|grep hello
kill -9 -7275
= 0 : 杀死调用kill函数那个进程同时处于同一个进程组的进程
int main00004(){
__pid_t pid=fork();
if(pid==-1){
perror("fork fail");
exit(-1);
}else if(pid>1){
// father
while(1){}
}else if(pid==0){
// child
sleep(10);
// 杀死调用kill函数那个进程同时处于同一个进程组的进程
kill(0,SIGKILL);
}
return 0;
}
3.3信号阻塞
信号集操作函数:
sigset_t set: 自定义信号集
sigemptyset(sigset_t* set): 清空信号集
sigefillset(sigset_t* set): 全部置1
sigaddset(sigset_t* set,int signum): 将一个信号添加到集合中
sigdelset(sigset_t* set,int signum): 将一个信号从集合中移除
sigismember(sigset_t* set,int signum): 判断一个信号是否存在集合中 1在 0不在
设置阻塞信号屏蔽字和解除屏蔽:
int sigprocmask(int how,const sigset_t* set,sigset_t* oldset)
how: SIG_BLOCK 设置阻塞, mask = mask|set
SIG_UNBLOCK 取消阻塞, mask = mask & ~set
SIG_SETMASK 用自定义set替换mask,全部会覆盖
set: 自定义set
oldset: 就有的mask
查看未决信号集:
int sigpending(sigset_t *set);
set:传出的 未决信号集
过程分析:
set 部分自定信号集
自定义功能集 & 阻塞信号 阻塞信号集第二位变成1
按ctrl+c 未决信号机 位2 变 成1 ,信号阻塞,无法处理 ,ctrl+c无效
阻塞信号取消阻塞信号
void print_set(sigset_t * set){
int i;
for(i=1;i<32;i++){
if(sigismember(set,i)){ // 判断一个信号是否存在集合中 1在 0不在
putchar('1');
}else{
putchar('0');
}
}
printf("\n-------------------------------------- \n");
}
/***
* 设置阻塞信号,进程屏蔽信号,发送信号无效
*/
int main00006(){
sigset_t set,pedset; // 自定义信号集
sigemptyset(&set); // 清空信号集
// 2 SIGINT: 相当于ctrl+c(终止/中断), 默认是终止进程
sigaddset(&set,SIGINT); // SIGINT 设置为1
sigaddset(&set,SIGQUIT); // SIGQUIT 阻塞 ctrl+\
sigaddset(&set,SIGKILL); //无效
sigset_t oldset;
int ret=sigprocmask(SIG_BLOCK,&set,&oldset); //设置信号屏蔽字,阻塞 mask = mask|set
//
if(ret ==-1){
perror("sigprocmask error");
exit(-1);
}
// 查看未决信号,程序执行以后比如按ctrl+c,上面设置的是阻塞信息
int j=0;
while(1){
j++;
ret=sigpending(&pedset);
if(ret ==-1){
perror("sigprocmask error");
exit(-1);
}
print_set(&pedset);
sleep(2);
if(j==3){
// 解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL); // mask = mask & ~set,最终运算
printf("-----SIG_UNBLOCK----");
}
}
return 0;
}
3.4.注册信号捕捉函数
必须要 中断、异常、或者系统调用才可以进入内核 while(1){} 无法进入内核
3.4.1.signal 函数
/***
* signal函数, 不是 posix 标准,是 ANSI定义
* 注册信号捕捉函数
*/
void sig_catch(int signo){
printf("catch you !! %d\n",signo);
}
int main00007(){
// 注册信号捕捉函数,但内核处理信号的时候调用sig_catch函数处理
signal(SIGINT,sig_catch);
while(1){
}
return 0;
}
3.4.2.sigaction 函数信号 捕捉
void sig_catch2(int signo){
if(signo==SIGINT){
printf("catch you 2!! %d\n",signo);
sleep(10);
}else if(signo==SIGQUIT){
printf("catch you 3!! %d\n",signo);
}
}
/**
* sigaction 函数信号 捕捉
* 特点:可以避免信号捕捉
*/
int main00008(){
struct sigaction act,oldact;
act.sa_handler= sig_catch2; //设置捕捉函数
sigemptyset(&(act.sa_mask)); // 设置mask
// 功能
// 如果函数已经捕捉到了SIGINT 信号,sa_handler在执行
// 此时SIGINT 再次收到, 信号优先原则,那么又会调用 sa_handler函数执行,此时会陷入递归
// 在在函数执行时候把act.sa_mask 置0, 那么在此时收到信号,不会继续执行函数、函数只执行一次
// 函数执行完毕会恢复
sigaddset(&act.sa_mask,SIGQUIT); //设置在函数执行期间屏蔽其他信号
act.sa_flags=0; //通常用0
int ret= sigaction(SIGINT,&act,&oldact); // 注册信号捕捉函数
// sigaction(SIGQUIT,&act,&oldact);
if(ret==-1){
perror("sigaction error");
exit(-1);
}
while(1){};
return 0;
}
3.4.3.sigaction 函数信号 回收子进程
void sig_catch3(int signo) {
__pid_t pid = wait(NULL);
printf("---sig_catch3---%d--", signo);
if (pid == -1) {
perror("wait fail");
exit(-1);
}
}
/***
* 为什么会出现僵尸进程:
* 如果一次有多个子进程死了,发送SIGCHLD 信号, 但是 sigaction 的捕捉函数默已经被调用,在wait,
* 你们其他的 信号失效,没有被回收 出现阻塞
*
*/
int main00009() {
int i;
for (i = 0; i < 15; i++) {
__pid_t pid = fork();
if (pid == 0) {
break;
}
}
if (i == 15) {
printf("parent ..pid.. %d", getpid());
struct sigaction act, oldact;
act.sa_handler = sig_catch3; //设置捕捉函数
sigemptyset(&(act.sa_mask)); // 设置mask
act.sa_flags = 0; //通常用0
int ret = sigaction(SIGCHLD, &act, &oldact); // 注册信号捕捉函数
if (ret == -1) {
perror("sigaction error");
exit(-1);
}
while (1) {
};
} else {
printf("child --- %d--parent --%d \n",getpid(),getppid());
}
return 0;
}
为什么会出现僵尸进程: 如果一次有多个子进程死了,发送SIGCHLD 信号, 但是 sigaction 的捕捉函数默已经被调用,在wait,你们其他的 信号失效,没有被回收 出现阻塞
解决方法:
void sig_catch4(int signo) {
int status;
pid_t wpid;
// 没有子进程了回收失败
while((wpid=waitpid(-1,&status,WNOHANG)!=-1)){
if(WIFEXITED(status)){
printf("WIFEXITED----%d\n",WEXITSTATUS(status));
}
}
printf("---sig_catch3---%d--", signo);
}
/***
* 解决方法:循环回收
*/
int main00010() {
int i;
for (i = 0; i < 15; i++) {
__pid_t pid = fork();
if (pid == 0) {
break;
}
}
if (i == 15) {
// 问题,如果子进程代码先执行,那么sigaction 还没有注册,
printf("parent ..pid.. %d", getpid());
struct sigaction act, oldact;
act.sa_handler = sig_catch4; //设置捕捉函数
sigemptyset(&(act.sa_mask)); // 设置mask
act.sa_flags = 0; //通常用0
int ret = sigaction(SIGCHLD, &act, &oldact); // 注册信号捕捉函数
if (ret == -1) {
perror("sigaction error");
exit(-1);
}
while (1) {
};
} else {
printf("child --- %d--parent --%d \n",getpid(),getppid());
}
return 0;
}
4. 守护进程
4.1. 如何创建回话
如何创建回话:
1. 创建会话进程不能是进程组组长,[比如父进程],该进程[子进程]成为新会话的首进程
2. 新会话丢弃原有控制终端,该会话没有控制终端[这种程序不能和用户进程操作,适合在后台运行,守护进程]
3. 建立新会话,调用fork,父进程终止,子进程调用setisid()
4. 建立成功以后 pid=gid=sid
5. 建立会话需要root权限 [ubuntu不需要]
ppid pid gid sid
/**
* 如何创建回话:
1. 创建会话进程不能是进程组组长,[比如父进程],该进程[子进程]成为新会话的首进程
2. 新会话丢弃原有控制终端,该会话没有控制终端[这种程序不能和用户进程操作,适合在后台运行,守护进程]
3. 建立新会话,调用fork,父进程终止,子进程调用setisid()
4. 建立成功以后 pid=gid=sid
5. 建立会话需要root权限 [ubuntu不需要]
ppid pid gid sid
*/
int main00011() {
pid_t pid = fork();
if (pid < 0) {
perror("fork fail");
exit(-1);
} else if (pid == 0) {
printf("child process PID is %d\n", getpid());
printf("group id is %d\n", getpgid(0));
printf("session is %d\n", getsid(0));
sleep(10);
setsid(); // 创建会话
printf("change.......");
printf("child process PID is %d\n", getpid());
printf("group id is %d\n", getpgid(0));
printf("session is %d\n", getsid(0));
} else {
// 父进程死了
}
return 0;
}
4.2.守护进程创建:
1. 独立于控制终端[运行在操作系统后台]并且周期执行某种任务
2. 不受用户登录注销影响,通常以d结尾方式 比如vsftpd[d]
int main0000013() {
pid_t pid = fork();
if(pid>0){
exit(0); // 父进程终止
}
__pid_t spid=setsid(); //创建新会话
if(spid == -1){
perror("setsid fail");
exit(-1);
}
__pid_t cpid= chdir("/home/denganzhi/linux");
// 改变工作目录
// why? 如果程序在U盘上运行,那么拔掉U盘,程序会崩溃
if(cpid ==-1){
perror("chdir fail");
exit(-1);
}
umask(0022); //改变文件访问权限掩码,避免fork 之前进程修改umask权限
close(STDIN_FILENO); // 关闭0号文件描述符
// 继承打开文件不会用到,浪费系统资源
// 如果直接关闭,那么文件描述从0开始,不符合习惯
// 可以定向到一个文件,那么还是从3开始文件描述符
int fd=open("/dev/null",O_RDWR);
if(fd == -1){
perror("open error");
exit(-1);
}
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
while(1){
}
return 0;
}
来源:CSDN
作者:小置同学
链接:https://blog.csdn.net/dreams_deng/article/details/104200379