团购网站设计,wordpress换轮播图的,网站开发合同中英文,电子商务网站建设需要学什么软件Linux系统编程 day07 信号 1. 信号的介绍以及信号的机制2. 信号相关函数2.1 signal2.2 kill2.3 abort和raise2.4 alarm2.5 setitimer 3. 信号集4. 信号捕捉函数6. SIGCHLD信号7. SIGUSR1与SIGUSR2 1. 信号的介绍以及信号的机制
信号是信息的载体#xff0c;在Linux/Unix环境下… Linux系统编程 day07 信号 1. 信号的介绍以及信号的机制2. 信号相关函数2.1 signal2.2 kill2.3 abort和raise2.4 alarm2.5 setitimer 3. 信号集4. 信号捕捉函数6. SIGCHLD信号7. SIGUSR1与SIGUSR2 1. 信号的介绍以及信号的机制
信号是信息的载体在Linux/Unix环境下信号是一种很重要的通信手段。信号通信的方式简单但是不能携带大量的信息信号是满足某个特定的条件才会产生的。
在Linux中进程A给进程B发送信号进程B收到信号之前执行自己的代码收到信号后不管执行到应用程序的什么位置都要暂停运行去处理信号处理完毕后再继续执行。信号是在软件的层面上实现的中断早期被称为软中断。每一个进程收到的信号都是由内核负责发送的。信号的发送过程如下 信号的状态有三种分别是产生、未决、递达。信号的产生由以下的几种方式
按键产生如Ctrl CSIGINT、Ctrl Z SIGSTOP、Ctrl \ SIGTERM。系统调用产生如调用kill 、raise、abort。软件条件产生如定时器alarm。硬件异常产生如非法访问内存段错误、除0浮点数例外、内存对齐出错总线错误。命令产生如kill命令。
信号的未决状态时是信号产生和信号递达之间的状态主要是由于阻塞导致了该状态后面我们会用sigpromask去对某个信号集进行阻塞等操作。信号的递达是信号递达并且到达该进程。
进程收到了信号之后就会对信号进行相应的处理。信号处理默认动作有以下几种方式 处理方式处理结果Term默认行为是终止进程Ign默认行为是忽略信号Core默认行为是终止进程并生成core文件Stop默认行为是暂停进程Cont默认行为是如果进程当前状态为暂停则继续运行进程
当然除了默认处理之外我们还可以捕捉信号调用用户的自定义处理函数。由于信号的发送是需要由用户态切换到内核态处理也是需要进入内核态所以用信号这种实现进程间通信的手段会导致信号有很强的延时性。虽然有延时但是这个时间对于用户来说是非常短的用户基本上察觉不到。
进程的相关信息存储在PCB中在PCB中有阻塞信号集和未决信号集。阻塞信号集的作用是对信号进行阻塞若一个信号被阻塞之后如果收到了该信号就会留在未决信号集中不会被处理。未决信号集是用于存储需要处理的信号这些信号因为某些原因不能抵达在解决屏蔽之前信号都是一直处于未决状态。当信号被处理这信号会从未决信号集中消失。
信号有四要素分别是信号的编号、信号的名称、产生信号的事件和默认的处理动作。其中信号编号为1~31之间的信号为常规信号也称为普通信号或者标准信号。34~64之间的信号称为实时信号与驱动编程和硬件相关。下面是信号的四要素表 信号编号表 从上面可以看到这些信号的编号在不同的操作系统架构中是不一样的因此我们使用信号的时候应该使用信号的名称。在上面的所有信号之中特别需要注意SIGKILL和SIGSTOP信号是不能被捕获和阻塞或者是忽略的。
在系统编程中我们常用的信号有SIGINT、SIGQUIT、SIGKILL、SIGSEGV、SIGUSR1、SIGUSR2、SIGPIPE、SIGALRM、SIGTERM、SIGCHLD、SIGSTOP、SIGCONT。
这些信号的含义如下
常用信号含义SIGINT按键中断SIGQUIT退出SIGKILL杀死SIGSEGV内存溢出非法操作内存等SIGUSR1用户使用自定义信号SIGUSR2用户使用自定义信号SIGPIPE管道破裂SIGALRM定时器信号SIGTERM终止信号SIGCHLD子进程死亡、暂停、继续时给父进程发送的信号SIGSTOP暂停进程SIGCONT继续运行暂停的进程
2. 信号相关函数
2.1 signal
该函数用于注册信号捕捉函数函数的原型如下
#include signal.h// 信号处理函数类型
typedef void (*sighandler_t)(int);// 作用: 注册信号捕捉函数
// 返回值: 以前信号的处理函数指针
// 参数: signum: 信号编号
// handler: 信号处理函数
sighandler_t signal(int signum, sighandler_t handler);该函数的使用示例如下
// signal函数测试--注册信号处理函数
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h// 信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);
}int main()
{// 注册信号处理函数signal(SIGINT, sighandler);sleep(200);return 0;
}
需要注意的是signal函数的行为在不同的UNIX版本之间是不同的在不同的Linux版本之间也是不同的应该避免是用它后续会用sigaction函数取代。
2.2 kill
kill函数用于给指定的进程发送指定的信号。该函数的原型为
#include sys/types.h
#include signal.h// 作用: 给指定进程发送指定信号
// 返回值: 成功返回0失败返回-1并设置errno
// 参数: pid: 进程号
// pid 0: 发送信号给指定进程。
// pid 0: 发送信号给调用kill进程属于同一进程组的
// 所有进程。
// pid -1: 发送给pid绝对值对应的进程组。
// pid -1: 发送给进程有权限发送的系统中所有进程。
// sig: 信号
int kill(pid_t pid, int sig);函数中的sig信号参数不推荐使用数字应该使用宏名因为不同的操作系统信号编号可能不一样但名称不一样。
在Linux中每一个进程都属于一个进程组进程组是一个或者多个进程的集合他们是相互关联的共同完成一个实体任务每一个进程都有一个进程组长默认进程组ID与进程组组长ID相同。
关于kill的示例代码如下
// kill函数测试--发送信号
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h// 信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);kill(getpid(), SIGKILL);
}int main()
{// 注册信号处理函数signal(SIGINT, sighandler);while(1){sleep(1);kill(getpid(), SIGINT);}return 0;
}
2.3 abort和raise
这两个函数是进程自己给自己发送信号其中abort用于给自己发送异常终止信号SIGABRT并会产生core文件而raise函数是用于给自己发送指定信号。这两个函数的原型如下
#include signal.h// 作用: 给当前进程发送指定信号自己给自己发
// 返回值: 成功返回0失败返回非0值
// 参数: sig: 信号编号
int raise(int sig);#include stdlib.h// 作用: 给自己发送异常终止信号SIGABRT并产生core文件
void abort(void);这两个函数的功能都可以使用kill函数实现。这两个函数的示例代码如下
raise函数测试
// raise函数测试 -- 给自己发送指定信号
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h// 信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);raise(SIGKILL);
}int main()
{// 注册信号处理函数signal(SIGINT, sighandler);while(1){raise(SIGINT);}return 0;
}abort函数测试
// abort函数测试 -- 给自己发送异常终止信号
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h// 注册信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);raise(SIGSTOP);
}int main()
{// 注册信号处理函数signal(SIGABRT, sighandler);abort();return 0;
}
2.4 alarm
该函数主要是用于设置定时器。函数的原型如下
#include unistd.h// 作用: 设置定时器闹钟在指定seconds之后内核会给当前进程发送
// SIGALRM信号。进程收到该信号之后默认动作为终止。每一个进程
// 都有且只有唯一的一个定时器。
// 返回值: 返回0或者剩余的秒数无失败的返回
// 参数: seconds: 秒数
unsigned int alarm(unsigned int seconds);该函数的如果把参数设置为0就表示取消定时器也就是alarm(0)。alarm函数使用的是自然定时与进程的状态无关无论进程处于什么状态alarm都计时。该函数的示例代码如下
// alarm函数测试--设置定时器
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.hint main()
{// 设置定时器int ret alarm(5);printf(ret [%d]\n, ret);sleep(2);ret alarm(4);printf(ret [%d]\n, ret);sleep(1);ret alarm(4);printf(ret [%d]\n, ret);while(1){sleep(1000);}return 0;
}
2.5 setitimer
setitimer函数也是用于设置定时器的可以代替alarm函数。该函数的精确到微妙可以实现周期定时。函数的原型如下
#include sys/time.h// 作用: 设置定时器精度为微妙可以实现周期定时。
// 返回值: 成功返回0失败返回-1并设置errno。
// 参数: which: 定时方式。
// new_value: 负责设定timeout时间
// old_value: 存放旧的timeout时间一般指定为NULL。
int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);// 参数的结构体
struct itimerval {struct timeval it_interval; // 闹钟触发周期struct timeval it_value; // 闹钟第一次触发时间
};struct timeval {time_t tv_sec; // 秒suseconds_t tv_usec; // 微妙
};其中定时方式主要有以下几种
定时方式宏名信号含义自然定时ITIMER_REALSIGALRM计算自然时间虚拟空间计时用户空间ITIMER_VIRTUALSIGVTALRM只计算占用CPU的时间运行时计时用户 内核ITIMER_PROFSIGPROF计算占用CPU以及执行系统调用的时间
该函数的使用示例如下
// setitimer函数测试
#include stdio.h
#include stdlib.h
#include sys/time.h
#include unistd.h
#include signal.h// 信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);
}int main()
{// 注册信号处理函数signal(SIGALRM, sighandler);// 设置周期时间struct itimerval tm;tm.it_interval.tv_sec 1;tm.it_interval.tv_usec 0;tm.it_value.tv_sec 3;tm.it_value.tv_usec 0;setitimer(ITIMER_REAL, tm, NULL);while(1){sleep(1);}return 0;
}
3. 信号集
信号集主要是两个信号集。第一个是未决信号集该信号集存放了当前进程要阻塞的信号。第二个是阻塞信号集该信号集存放了当前进程中还处于未决状态的信号。这两个集合都存储在PCB中。
在一个进程运行的时候如果该进程收到了一个信号A则这个信号首先会被保存到未决信号集中对应的信号编号的存储位置将该位置置为1此时该信号处于未决状态。当要处理这个信号的时候会去检查阻塞信号集中对应的信号编号的存储位置上是否为0若为1表示需要阻塞该信号则信号不会被处理若为0则表示该信号不被阻塞。当未决信号集的信号没有被阻塞被处理之后未决信号集的信号编号对应的存储位置会被重置为0。
信号集在Linux中使用sigset_t进行定义。该类型是一个结构体结构体定义如下
#ifndef ____sigset_t_defined
#define ____sigset_t_defined#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;#endif由于信号集属于内核的一块区域所以用户不能直接操作内核空间因此我们需要使用信号集相关的一些接口函数完成对信号集的相关操作。
接下来看一些信号集的相关函数信号集相关函数原型如下
#include signal.h// 作用: 将某个信号集清0
// 返回值: 成功返回0失败返回-1并设置errno
// 参数: set: 信号集
int sigemptyset(sigset_t *set);// 作用: 将某个信号集置1
// 返回值: 成功返回0失败返回-1并设置errno
// 参数: set: 信号集
int sigfillset(sigset_t *set);// 作用: 将某个信号加入到信号集中
// 返回值: 成功返回0失败返回-1并设置errno
// 参数: set: 信号集
// signum: 信号编号
int sigaddset(sigset_t *set, int signum);// 作用: 将某个信号清出信号集
// 返回值: 成功返回0失败返回-1并设置errno
// 参数: set: 信号集
// signum: 信号编号
int sigdelset(sigset_t *set, int signum);// 作用: 判断某个信号是否在信号集中
// 返回值: 存在返回1不存在返回0失败返回-1并设置errno
// 参数: set: 信号集
// signum: 信号编号
int sigismember(const sigset_t *set, int signum);
#include signal.h// 作用: 用于屏蔽信号解除屏蔽信号
// 返回值: 成功返回0失败返回-1并设置error
// 参数: how: 参数取值决定函数的作用how的取值有以下:
// SIG_BLOCK: 设置屏蔽信号set为需要屏蔽的信号
// SIG_UNBLOCK: 设置解除屏蔽信号set表示要解除屏蔽信号
// SIG_SETMASK: set用于代替原始屏蔽集的新屏蔽集
// set: 需要设置的信号集
// oldset: 在设置操作之前的信号集
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);#include signal.h// 作用: 读取当前进程的未决信号集
// 返回值: 成功返回0失败返回-1并设置errno
// 作用: set: 信号集
int sigpending(sigset_t *set);接下来使用一个例子来说明以下信号集相关函数的使用该例子的作用是将当前进程未决信号集中的常用信号集打印出来。
// 信号集相关函数测试
#include stdio.h
#include stdlib.h
#include sys/time.h
#include signal.h
#include unistd.h// 信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);
}int main()
{// 注册信号处理函数signal(SIGINT, sighandler);signal(SIGQUIT, sighandler);// 定义信号集sigset_t set; // 设置的信号集sigset_t pending; // 未决信号集sigset_t oldset; // 保存之前的信号集// 初始化信号集sigemptyset(set);sigemptyset(oldset);sigemptyset(pending);// 将SIGINT和SIGQUIT加入到信号集中sigaddset(set, SIGINT);sigaddset(set, SIGQUIT);// 将SIGINT和SIGQUIT加入到阻塞信号集中// sigprocmask(SIG_BLOCK, set, NULL);sigprocmask(SIG_BLOCK, set, oldset);int j 1;while(1){// 获取未决信号集sigpending(pending);for(int i 1; i 32; i ){// 判断某个信号是否位于信号集中if(sigismember(pending, i)){printf(1);}else {printf(0);}}printf(\n);if(j % 10 0){// 解除阻塞// sigprocmask(SIG_UNBLOCK, set, NULL);sigprocmask(SIG_UNBLOCK, set, oldset);}else{// 阻塞sigprocmask(SIG_BLOCK, set, NULL);}sleep(1);}return 0;
}
程序的运行结果如下 4. 信号捕捉函数
这里的信号处理函数是说的sigaction该函数的作用和signal一样。不同的signal函数在不同的Unix或者Linux下其行为可能是不一样的而sigaction的行为都是一样的想要做一个跨平台的程序一般使用sigaction函数。该函数的原型如下
#include signal.h// 作用: 注册一个信号处理函数
// 返回值: 成功返回0失败返回-1并设置errno
// 参数: signum: 捕捉的信号
// act: 新的处理方式
// oldact: 旧的处理方式
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);// struct sigaction 定义如下
struct sigaction {// 信号处理函数void (*sa_handler)(int); // 信号处理函数一般不用这个void (*sa_sigaction)(int, siginfo_t *, void *);// 处理期间需要阻塞的信号sigset_t sa_mask;// 默认标识通常设置为0int sa_flags;// 已丢弃不用void (*sa_restorer)(void);
};
在上面结构体的sa_handler表示指定信号捕捉后处理的函数名也可以赋值为SIG_IGN表示忽略SIG_DFL表示执行默认动作。sa_mask用于指定在信号处理函数执行期间需要被屏蔽的信号特别是当某个信号被处理的时候它自身会被自动放入到进程的信号掩码因此在信号处理函数执行期间这个信号不会再度发生。需要注意的是sa_mask只在处理函数调用期间屏蔽生效是一个临时性的设置。另外需要知道的是信号是不支持排队的。也就是在一个信号处理函数执行期间该函数接收到了很多个该信号则信号处理函数处理完该信号之后只执行一次处理。
内核再执行信号处理函数的过程如下 接下来给一个关于使用sigaction函数的例子。
// sigaction函数测试信号捕获
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h// 信号处理函数
void sighandler(int signo)
{printf(signo [%d]\n, signo);sleep(5);
}int main()
{// 注册信号处理函数struct sigaction act;act.sa_handler sighandler; // 信号处理函数sigemptyset(act.sa_mask);sigaddset(act.sa_mask, SIGQUIT); // 信号执行期间需要阻塞的信号act.sa_flags 0;sigaction(SIGINT, act, NULL); // 注册信号处理函数signal(SIGQUIT, sighandler);while(1){sleep(1);}return 0;
}
6. SIGCHLD信号
SIGCHLD信号主要是由子进程在一定条件下触发并由内核给父进程发送的信号该信号的产生有三种情况
子进程结束的时候。子进程收到SIGSTOP信号。子进程停止时收到SIGCONT信号。
子进程退出之后内核会给父进程发送SIGCHLD信号因此我们可以用这个信号完成对子进程的回收。这样避免父进程一直阻塞等待而不能执行其它操作。在回收子进程的时候我们需要考虑到有可能还没有完成信号注册的时候子进程就可能退出了也有可能在SIGCHLD处理期间SIGCHLD再次产生了很多次若不进行多次回收则会出现僵尸进程。
避免第一个问题的方法是在注册处理函数之前先对SIGCHLD信号进行阻塞第二个问题的解决是使用循环进行多次回收。因此我们可以写出来以下用SIGCHLD函数完成子进程回收的例子。关于信号产生的条件可以自行验证。
// 使用SIGCHLD回收子进程的资源
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h
#include sys/wait.h// 注册信号处理函数
void sighandler(int signo)
{while(1){pid_t wpid waitpid(-1, NULL, WNOHANG);if(wpid 0){printf(Child pid [%d] is dead\n, wpid);}else if(wpid 0){printf(Child is living\n);break;}else if(wpid -1){printf(No child is living\n);break;}}
}int main()
{// 在注册SIGCHLD之前阻塞SIGCHLD信号sigset_t set;sigemptyset(set);sigaddset(set, SIGCHLD);sigprocmask(SIG_BLOCK, set, NULL);// 创建子进程int i;for(i 0; i 3; i ){pid_t pid fork();if(pid 0){perror(fork error);return -1;}else if(pid 0){printf(Child: pid [%d], fpid [%d]\n, getpid(), getppid());break;}}if(i 0){printf(I am child [%d]\n, getpid());sleep(2);}else if(i 1){printf(I am child [%d]\n, getpid());sleep(1);}else if(i 2){printf(I am child [%d]\n, getpid());sleep(2);}else if(i 3){sleep(4);// 注册信号处理函数struct sigaction act;act.sa_handler sighandler;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGCHLD, act, NULL);// 取消阻塞sigprocmask(SIG_UNBLOCK, set, NULL);}return 0;
}
7. SIGUSR1与SIGUSR2
在系统编程的时候我们不能随便乱使用kill函数去对某个进行发送一个系统的信号因此操作系统提供用户两个信号使用。接下来我们使用这个两个信号完成两个进程交替数数。示例代码如下
// 使用SIGUSR实现交替数数
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.hint num 0;
int flag;// 信号处理函数
void sigusr1handler(int signo)
{printf(USR1: [%d]\n, num);num 2;flag 0;sleep(1);
}void sigusr2handler(int signo)
{printf(USR2: [%d]\n, num);num 2;flag 0;sleep(1);
}int main()
{// 创建子进程pid_t pid fork();if(pid 0){perror(fork error);return -1;}else if(pid 0){// 注册信号处理函数struct sigaction act;act.sa_handler sigusr2handler;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGUSR2, act, NULL);num 2;flag 0;while(1){if(flag 0){kill(getppid(), SIGUSR1);flag 1;}}}else {// 注册信号处理函数struct sigaction act;act.sa_handler sigusr1handler;sigemptyset(act.sa_mask);act.sa_flags 0;sigaction(SIGUSR1, act, NULL);num 1;flag 1;while(1){if(flag 0){kill(pid, SIGUSR2);flag 1;}}}return 0;
}
运行结果为 在上面的代码之中我们分别通过信号处理函数来控制while循环中的if语句是否执行。两个进程分别互相发送信号就可以完成进程的交替输出。由于flag变量和num变量需要在信号处理函数中能够访问所以定义成了全局变量。具体的运行过程可以通过分析程序结构理解理解。