终端按键产生信号
ctrl + c
发送2号信号SIGINT,将中止当前进程。SIGINT的“INT”是interrupt的意思;
ctrl + z
发送20号信号SIGTSTP,暂停/停止当前进程。SIGTSTP中的“T”是terminal的意思。进程收到此信号后,将由当前转入后台,可用ps aux找到它。
比如说,我们使用vim编写代码时,需要在vim以外处理一些事情,但又不想退出vim,可用ctrl + z命令让vim进程进入后台,之后再用fg命令重新将vim进程唤回前台,继续编辑;
ctrl + \
发送 3号信号SIGQUIT给前台进程组中的所有进程,终止前台进程并生成 core 文件;
硬件异常产生信号
a. 除0操作
产生8号信号SIGFPE,终止进程并产生core文件。SIGFPE的“F”是float的意思;
b. 非法访问内存
产生11号信号SIGSEGV,段错误;
c. 总线错误
产生7号信号SIGBUS;
kill函数/命令产生信号
从字面上来看,kill是杀死的意思,但这个命令/函数真正的意思是向进程发送信号。真不知道当时设计这个命令/函数的人是怎么想的。
kill命令使用方法:
kill -信号编号/宏 进程号。比如:kill -SIGKILL 1893,表示发送SIGKILL信号给1893号进程,即杀死1893号进程。
kill函数的用法:
函数原型:
int kill(pid_t pid, int sig);
返回值:
成功:返回0;失败:返回-1,并设置errno。
参数说明:
sig:要发送的信号,最好写宏名,而不要写信号编号,因为各个平台信号的编号可能不一样,而宏名都是唯一的;
pid:要发送的目标进程;
pid > 0:发送信号给进程号为pid的进程;
pid = 0:发送信号给与调用kill函数进程属于同一进程组的所有进程;
pid < 0:发送信号给进程组号为|pid|所有进程;
pid = -1:发送给进程有权限发送的系统中所有进程。啥是“有权限发送”?比如你就不能给init进程发信号,因为你没权限。
例程:
循环创建5个进程,并杀死第三个子进程。
1#include <stdio.h>
2#include <sys/types.h>
3#include <unistd.h>
4#include <signal.h>
5
6
7#define N 5
8
9int main()
10{
11 int i = 0;
12 pid_t pid, q;
13
14 for (i = 0; i < N; i++) {
15 /* 循环创建N个子进程 */
16 pid = fork();
17 if (pid == -1)
18 printf("ERROR: fork error!\n");
19 else if (pid == 0)
20 break;
21
22 if (i == 2)
23 q = pid; // 保留第三个子进程的pid
24 }
25
26 if (i < N) {
27 while (1) {
28 printf("I'm %dth child process, pid = %d\n", i, getpid());
29 sleep(1);
30 }
31 } else if (i == N) {
32 sleep(1);
33 printf("I'm parent process\n");
34 kill(q, SIGKILL);
35 while (1);
36 }
37
38 return 0;
39}
raise和abort函数
rase函数:
int raise(int sig); 成功:返回0,失败:返回非0值。
给当前进程发送指定信号。简单一句话:自己给自己发信号。
abort函数:
void abort(void); 无返回值。
给自己发送异常终止信号SIGABRT,终止进程并产生core文件。
alarm函数
函数原型:
unsigned int alarm(unsigned int seconds);
返回值:
返回0或者上次闹钟剩余秒数,无失败情况;
函数作用:
设置定时器,在指定时间之后,内核给调用alarm的函数发送SIGALRM信号。进程收到该信号后,默认动作是终止当前进程。
每个进程有且只有一个定时器,并且与进程的状态无关。也就是说,不管进程处于就绪、运行、挂起(阻塞/暂停)、终止、僵尸、孤儿,alarm定时器一直都在计时。
常见用法:
alarm(5); --> 5秒后发送SIGALRM信号。如果在2秒后调用alarm(2),则alarm(2)返回值是3,并且在2秒后发送SIGALRM信号;
alarm(0); --> 取消定时器,并返回上次调用alarm剩下的秒数。
1#include <stdio.h>
2#include <unistd.h>
3
4int main()
5{
6 unsigned int t = 0;
7 printf("process start..\n");
8 alarm(10);
9 sleep(2);
10 t = alarm(2);
11 printf("time left: %d\n", t);
12 while (1); // 进程停在此处,直到收到定时器信号SIGALRM将进程杀死
13
14 return 0;
15}
setitimer函数
与alarm类似,可设置定时器,但是,alarm函数定时精度只能到秒,而setitimer函数定时可达到微秒级,并且可以周期定时。
函数原型:
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
返回值:
成功:返回0;失败:返回-1,并设置errno。
参数说明:
参数which:指定定时方式:
a. 自然定时:ITIMER_REAL。计算程序运行自然时间,就是程序不管在内核态还是用户态,都计时。到达时间点后,发送SIGARM信号;
b. 用户空间定时:ITIMER_VIRTUAL。只计算进程战胜CPU的时间,即用户空间时间。到达时间点后,发送SIGVTALRM信号;
c. 运行时计时:ITIMER_PROF。计算占用cpu及执行系统调用的时间,也即用户空间时间及内核时间总和。到达时间点后,发送SIGPROF信号。
这三种定时方式,最常用的就是自然定时法,即参数为ITIMER_REAL的定时方式,而后两种方式不常用,有用到时再查手册。
结构体struct itimerval有两个成员,一个是it_value, 另一个是it_interval。他们俩都是可以精确到微秒的,以struct timeval来控制具体时间。it_value控制了多久之后第一次发送信号,之后会循环发送,间隔是it_interval。
参数new_value:设定的定时时间;
参数old_value:上次调用setitimer剩余的时间。
例程:
自己实现alarm函数:
1#include <stdio.h>
2#include <unistd.h>
3#include <sys/time.h>
4#include <stdlib.h>
5
6unsigned int my_alarm(unsigned int sec)
7{
8 struct itimerval new_value, old_value;
9 int ret = -1;
10
11 new_value.it_value.tv_sec = sec;
12 new_value.it_value.tv_usec = 0;
13 new_value.it_interval.tv_sec = 0;
14 new_value.it_interval.tv_usec = 0;
15
16 ret = setitimer(ITIMER_REAL, &new_value, &old_value);
17 if (ret == -1) {
18 printf("ERROR: setitimer error!\n");
19 exit(1);
20 }
21 return old_value.it_value.tv_sec;
22}
23
24int main()
25{
26 unsigned int t = 0;
27 printf("process start..\n");
28 alarm(10);
29 sleep(2);
30 t = alarm(2);
31 printf("time left: %d\n", t);
32 while (1); // 进程停在此处,直到收到定时器信号SIGALRM将进程杀死
33
34 return 0;
35}
---------------
我是良许,世界500强外企 Linux 开发工程师,专业生产 Linux 干货。欢迎关注我的公众号「良许Linux」,回复「1024」获取最新最全的技术资料,回复「入群」进入高手如云技术交流群。