一、进程之间的通信。--- 信号
1、在linux下,有哪些信号?
gec@ubuntu:/mnt/hgfs/GZ2057/09 系统编程/02/homework/3$ kill -l (kill:发送信号命令 -l: 列出全部信号)
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
例如:
19) SIGSTOP
19 -> 信号值
SIGSTOP -> 信号的名字
2、信号名字的本质是什么?
其实一个信号名字是一个宏定义来的,信号值就是这个宏定义背后的意义。
宏定义被定义在一个头文件中,头文件路径: /usr/include/asm-generic/signal.h -> 信号的专属头文件
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
3、常用信号?
2) SIGINT -> 中断信号,其实就是我们平时用得很多的"ctrl + C"
9) SIGKILL -> 停止信号,代表这个进程被强制杀死,直接退出。
10) SIGUSR1
12) SIGUSR2 -> 提供给用户使用的两个信号,没有什么特殊的作用。
18) SIGCONT -> 继续信号
19) SIGSTOP -> 暂停信号
4、在linux下,这些信号究竟是由谁来发出?
1)由系统来发出。
14) SIGALRM -> 当程序中调用alarm()时,如果到点了,系统就会自动发出SIGALRM这个信号。
17) SIGCHLD -> 当子进程退出时,系统自动发出这个信号给父进程。
2)由用户发送。
如果是由用户来发送,则需要学习 kill / killall 这两个命令。
5、用户如何发送信号给进程?
方法一:使用kill命令。
1)通过ps -ef找到需要发送信号的目标程序。
gec@ubuntu:~$ ps -ef
PID PPID
gec 68986 7670 89 19:38 pts/17 00:00:08 ./project
2)查看到目标程序的PID号。
68986
3)直接使用kill命令发送信号给进程即可。
gec@ubuntu:~$ kill -9 68986
方法二: 使用killall命令发送。
1)通过"ps -ef"查到进程的名字是什么
gec@ubuntu:~$ ps -ef
gec 69017 7670 88 19:42 pts/17 00:00:02 ./project
2)直接使用killall命令进程名字发送信号。
gec@ubuntu:~$ killall -9 project (只要整个linux下有名字为project的进程,都会被杀死)
二、学习信号的函数接口?
1、如何发送信号给另外一个进程? -> kill() -> man 2 kill
功能: kill - send signal to a process
发送信号给进程
头文件:#include <sys/types.h>
#include <signal.h>
原型:
int kill(pid_t pid, int sig);
pid:目标进程的PID号。
sig:需要发送的信号的信号值。
返回值:
成功:0
失败:-1
2、 如何捕捉信号? -> signal() -> man 2 signal
1)什么是捕捉信号
就是这个进程一定要先说清楚,将来收到什么信号,就做什么事情。
2)以下的这句话是什么意思?
typedef void (*sighandler_t)(int);
1.先去掉typedef -> void (*sighandler_t)(int);
2.剩余结果肯定是数据类型 + 变量名
数据类型: void (*)(int);
变量名: sighandler_t
3.就是这个变量替代了这个数据类型。
sighandler_t 就是 void (*)(int) 的新别名。
3)什么是信号的处理函数呢?
将来你收到什么信号,想做的事情,就放在这个信号处理函数中
4)信号处理函数中的int类型的形参是什么来的?
就是你捕捉到的信号值。
功能: signal - ANSI C signal handling
//设置信号的处理函数
头文件:#include <signal.h>
原型:
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum:需要捕捉的信号
handler: 信号处理的函数的地址 信号处理函数必须长: void fun(int a)
返回值:
成功:信号处理函数的地址
失败:SIG_ERR
举例子。
1. 将来我收到"走,吃饭去"这个信号,我就说"好"
void fun(int a)
{
"好"
}
signal("走,吃饭去",fun);
2. 将来我收到了SIGUSR1这个信号,我就打印"helloworld";
void my_fun(int a)
{
printf("helloworld!\n");
}
signal(SIGUSR1,my_fun);
例子1: 子进程发送SIGUSR1给父进程,父进程收到这个信号之后,就打印helloworld。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
void fun(int a)
{
printf("helloworld!\n");
}
int main(int argc,char *argv[])
{
pid_t x;
x = fork();
if(x > 0)
{
//1. 先捕捉
signal(SIGUSR1,fun);
//2. 原地等待
wait(NULL); -> 阻塞
exit(0);
}
if(x == 0)
{
//2. 先睡一会,确保父进程先捕捉
sleep(3);
//3. 发送信号给父进程
printf("I send signal to parent!\n");
kill(getppid(),SIGUSR1);
exit(0);
}
return 0;
}
结果:
I send signal to parent!
helloworld!
3、挂起进程,直到收到一个信号为止。 -> pause() -> man 2 pause()
一般都是与signal连用。
signal(SIGUSR1,fun); -> 将来我收到SIGUSR1,我就执行这个fun函数。
pause(); -> 原地等待,目的就是为了等到SIGUSR1的到达。
功能:
pause - wait for signal
//等待信号的到达
头文件:#include <unistd.h>
原型:
int pause(void);
参数:无
返回值:
当你捕捉到一个信号就会返回-1。
没有信号就会一直等待。
4、自己给自己发送信号。 -> raise() -> man 3 raise
功能: raise - send a signal to the caller
//发送一个信号给发送者
头文件:#include <signal.h>
原型:int raise(int sig);
参数:
sig: 你想给自己发送的信号值
返回值:
成功:0
失败:非0
示例:
#include <signal.h>
#include <stdio.h>
void fun(int a)
{
printf("helloworld!\n");
}
int main(int argc,char *argv[])
{
//1. 先捕捉信号
signal(SIGUSR1,fun);
//2. 自己给自己发送一个信号
raise(SIGUSR1);
return 0;
}
三、研究signal函数的第二个参数。
现实例子: signal用于捕捉信号的。
假设将来收到"去睡觉"这个信号,我就说"好,我准备关灯回房间" -> 默认动作。
假设将来收到"去睡觉"这个信号,我理都不理你。 -> 忽略。
假设将来收到"去睡觉"这个信号,我就说"我刚刚煮菜没有放盐吗" -> 自定义动作。 -> 信号处理函数
1、 自定义动作。
the address of a programmer-defined function (a "signal handler").
//信号处理函数的地址
信号处理函数: void fun(int sig)
void fun(int sig)
{
"我刚刚煮菜没有放盐吗"
}
signal("去睡觉",fun); -> 第二个填信号处理函数的地址。
2、 忽略。
SIG_IGN -> signal ignore
If the disposition is set to SIG_IGN, then the signal is ignored.
//如果设置SIG_IGN,那么这个信号就会被忽略。
signal("去睡觉",SIG_IGN); -> 第二个填SIG_IGN。
待会收到"去睡觉"这个信号,没有任何反应。
3、 默认动作。
SIG_DFL -> signal default
If the disposition is set to SIG_DFL, then the default action associated with the signal (see signal(7)) occurs.
//如果设置了SIG_DFL,信号默认动作就会被发生。
四、linux系统信号集概念?
1、什么是信号集?
信号集是一个集合来的,而每一个成员都是一个信号的来,通过将信号加入到信号集中,再设置阻塞状态给信号集,那么信号集中所有的信号都会变成阻塞的属性。
2、信号响应、信号阻塞、信号忽略分别是什么意思?
信号响应: 收到信号之后,马上会响应信号的动作。
信号忽略: 收到信号之后,直接丢弃这个信号。
信号阻塞: 进程在阻塞某一个信号的前提,收到这个信号,不会马上响应,而是等到解除阻塞之后,才会响应这个信号。
(这个信号因为阻塞没有被响应,那么这个信号就会被放在挂起队列上)
五、信号集处理函数?
1、信号集如何定义?
信号集其实就是一个变量,数据类型: sigset_t
定义一个信号集: sigset_t set;
2、清空信号集? -> 将所有的信号都剔除到集合之外。 -> sigemptyset() -> man 3 sigemptyset
头文件:#include <signal.h>
原型:int sigemptyset(sigset_t *set);
参数:
set: 需要清空的信号集的地址。
返回值:
成功:0
失败:-1
3、将linux下所有的信号都加入到信号集。 -> sigfillset() -> man 3 sigfillset
头文件:#include <signal.h>
原型:int sigfillset(sigset_t *set);
参数:
set: 需要添加所有信号的信号集的地址。
返回值:
成功:0
失败:-1
4、添加某一个信号到信号集中。 -> sigaddset() -> man 3 sigaddset
头文件:#include <signal.h>
原型:int sigaddset(sigset_t *set, int signum);
参数:
set: 需要添加信号的信号集的地址。
signum: 需要添加的信号值。
返回值:
成功:0
失败:-1
5、删除信号集中的某一个信号。 -> sigdelset() -> man 3 sigdelset
头文件: #include <signal.h>
原型:int sigdelset(sigset_t *set, int signum);
参数:
set: 需要删除信号的信号集的地址。
signum: 需要删除的信号值。
返回值:
成功:0
失败:-1
6、测试某一个信号是不是在集合中。 -> sigismember() -> man 3 sigismember
头文件: #include <signal.h>
原型: int sigismember(const sigset_t *set, int signum);
参数:
set: 需要判断信号在不在集合中的信号集的地址。
signum: 需要判断的信号值。
返回值:
signum在set中: 1
signum不在set中: 0
出错: -1
例子1: 写一个程序,先清空信号集,再把SIGUSR1加入到集合中,判断该信号是不是在集合中。
#include <signal.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int ret;
//1. 定义信号集。
sigset_t set;
//2. 清空信号集。
ret = sigemptyset(&set);
printf("ret = %d\n",ret);
//3. 再将SIGUSR1加入到信号集中。
ret = sigaddset(&set,SIGUSR1);
printf("ret = %d\n",ret);
//4. 判断一下这个该信号在不在集合中。
ret = sigismember(&set,SIGUSR1);
if(ret == 1)
{
printf("is member!\n");
}
else if(ret == 0)
{
printf("isn't member!\n");
}
else{
printf("error!\n");
}
return 0;
}
7、如何设置信号集为阻塞的状态? -> sigprocmask() -> man 2 sigprocmask
功能:examine and change blocked signals
//导致信号集变成阻塞状态
头文件:
#include <signal.h>
原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数:
how:
SIG_BLOCK -> 设置阻塞的属性
SIG_UNBLOCK -> 设置解除阻塞的属性
set: 需要设置属性的信号集的地址
oldset: 保留之前状态的指针,如果不关心之前的状态,置为NULL。
返回值:
成功:0
失败:-1
练习1: 先让本来进程产生一个子进程。
父进程将SIGUSR1加入到信号集中,判断信号在不在信号集中,再设置信号集为阻塞状态,该状态持续10S(也就是说,那么在10S内,对SIGUSR1都是阻塞的),10S后,解除阻塞,看看会不会响应该信号?
子进程在10S内发送一个SIGUSR1给父进程。
结果 -> 信号发送之后,是马上响应 -> 信号响应。
信号发送之后,10S内,10S后都不响应 -> 信号忽略。
信号发送之后,10S内不会响应,10S后就会响应 -> 信号阻塞。 -> 对
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>
void my_fun(int sig)
{
printf("catch sig = %d\n",sig);
}
int main(int argc,char *argv[])
{
//1. 产生一个新的进程。
pid_t x;
x = fork();
//2. 父子进程做的事情不一样,通过返回值去判断。
if(x > 0)
{
int ret,i;
//3. 捕捉信号
signal(SIGUSR1,my_fun);
//4. 初始化信号集。
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGUSR1);
ret = sigismember(&set,SIGUSR1);
if(ret == 0)
{
printf("not is member!\n");
}
//5. 设置为阻塞状态。
sigprocmask(SIG_BLOCK,&set,NULL);
//6. 倒数10S
for(i=10;i>0;i--)
{
sleep(1);
printf("block state: %d\n",i);
}
//7. 10S后,解除阻塞。
sigprocmask(SIG_UNBLOCK,&set,NULL);
//8. 主动回收子进程的资源
wait(NULL);
//9. 父进程退出
exit(0);
}
if(x == 0)
{
sleep(3); //确保父进程已经准备好了
kill(getppid(),SIGUSR1);
printf("I send signal to parent!\n");
exit(0);
}
return 0;
}
结果:
block state: 10
block state: 9
I send signal to parent!
block state: 8
block state: 7
block state: 6
block state: 5
block state: 4
block state: 3
block state: 2
block state: 1
catch sig = 10 -> 要等到解除阻塞才会响应。