Linux网络编程学习(五) ----- 信号(第四章)

时间:2023-12-21 15:04:14

1、基本概念

进程阻塞:

进程执行条件得不到满足,就自动放弃CPU资源而进入休眠状态,以等待条件满足,当条件满足时,系统就将控制权还给该进程进行未完成的操作

共享资源:

进程间协调使用的系统资源

锁定:

当某个进程使用共享资源时,可能需要防止别的进程对该资源的使用。Linux提供一些方法保证共享资源被某个进程使用时,其他进程不能使用,就称为共享资源的锁定

2、信号

信号的作用是通知一个或多个进程异步事件的发生,也可以用来处理某种严重的错误,可以从内核发往进程,也可以从一个进程发往另一个进程,

例如:用户在后台启动了一个程序,现在想要终止此进程的执行,就可以使用Kill命令将SIGTERM信号发送给这个进程,SIGTERM将终止该进程的执行

例如:执行exit()时,就向子进程的父进程发送SIGCHLD信号(子进程结束信号),若此时父进程在执行wait(),则父进程会被唤醒,若不是在执行wait(),则此父进程不会捕捉到SIGCHLD信号,该信号就不起作用,子进程进入过渡状态(如果父进程忽略SIGCHLD,子进程就会结束而不会进入过渡状态)

在大多数情况下,当进程收到一个信号时,会被正常终止,相当于进程执行了一个临时加入的exit()调用。

信号SIGQUIT、SIGILL、SIGTRAP、SIGFPE会导致一个非正常终止,它们将发生核心转贮,也就是把进程的内存映像写入进程当前目录的core文件中,core文件以二进制形式记录了终止时程序中全部变量值、硬件寄存器值和内核中的控制信息,非正常终止进程的退出状态除了其低端第7位置位外,其他与通过信号正常终止时一样

3、信号的复位

当一个信号的信号处理函数执行时,该进程又收到了同样的信号,那么该信号将会被存储而不会中断信号处理函数,直到信号处理函数执行完后重新调用相应处理函数,但是如果接收到了其他类型的信号,那么该信号处理函数将会被中断

例如:

#include <signal.h>
int interrupt()
{
printf(“Interrupt called\n”);
sleep(3);
printf(“Interrupt Func Ended.\n”);
}
main()
{
signal(SIGINT,interrupt);
printf(“Interrupt set for SIGINT\n”);
sleep(10);
printf(“Program NORMAL ended.\n”);
return;
}

  执行结果就是

Interrupt set for SIGINT
<ctrl+c>
Interrupt called
<ctrl+c>
Func Ended
Interrupt called
Func Ended
Program NORMAL ended.

  而代码:

#include <signal.h>
int interrupt()
{
printf(“Interrupt called\n”);
sleep(3);
printf(“Interrupt Func Ended.\n”);
}
int catchquit()
{
printf(“Quit called\n”);
sleep(3);
printf(“Quit ended.\n”);
}
main()
{
signal(SIGINT,interrupt);
signal(SIGQUIT,catchquit);
printf(“Interrupt set for SIGINT\n”);
sleep(10);
printf(“Program NORMAL ended.\n”);
return;
}

  执行的结果是

Interrupt set for SIGINT
<ctrl+c>
Interrupt called
<ctrl+\>
Quit called
Quit ended.
Interrupt Func Ended.
Program NORMAL ended.

  还有就是同一种信号不能累积,如:

Interrupt set for SIGINT
<ctrl+c>
Interrupt called
<ctrl+c><ctrl+c><ctrl+c>
Func Ended
Interrupt called
Func Ended
Program NORMAL ended.

  如果两个信号同时产生,系统不保证进程接收的顺序,因此,信号处理的顺序就不可控

4、进程间发送信号

一个进程可以通过signal()调用来处理其他进程发送来的信号,同时也可以通过kill(pid_t pid,int sig)来向其他进程发送信号。pid指定了信号发送的对象进程。可以是进程标识符,也可以是以下的值

1)pid = 0,  则信号被发送到当前进程所在的进程组的所有进程

2)pid = -1, 则信号按进程标识符从高到低的顺序发送给全部的进程

3)pid < -1,则信号被发送给标识符为pid 绝对值的进程组里的所有进程

信号发送的限制:普通用户进程只能向具有与其相同的用户标识符的进程发送信号,也就是说一个用户的进程不能向另一个用户的进程发送信号,只有root用户的进程才能给任何进程发送信号。

由于Kill需要知道目标进程标识符,所以一般在父子进程之间进行信号发送

举个例子:两个进程,通过发送SIGUSER1实现同步,两个进程都处于死循环中,接收对方信号前,处于暂停等待中,也就是pause(),使得程序暂停一直到信号达到,然后进程输出信息,并用kill发送一个信号给对方,若用户按了中断键,两个进程终止

#include <signal.h>
int ntimes = 0;
main()
{
int pid,ppid;
int p_action(), c_action();
/* 设定父进程的SIGUSR1 */
signal(SIGUSR1,p_action);
switch(pid = fork())
{
case -1: /*fork 失败*/
perror("synchro");
exit(1);
case 0: /*子进程模块*/
/* 设定子进程的SIGUSR1 */
signal(SIGUSR1,c_action);
/* 获得父进程的标识符 */
ppid = getppid();
for(;;)
{
sleep(1);
kill(ppid,SIGUSR1);
pause();
}
/*死循环*/
break;
default: /*父进程模块*/
for (;;)
{
pause();
sleep(1);
kill(pid,SIGUSR1);
}
/*死循环*/
}
}
int p_action()
{
printf("Patent caught signal #%d\n",++ntimes);
}
int c_action()
{
printf("Child caught signal #%d\n",++ntimes);
}

  运行结果就是

Patent caught signal #1
Child caught signal #1
Patent caught signal #2
Child caught signal #2
Patent caught signal #3
Child caught signal #3
Patent caught signal #4
Child caught signal #4
<ctrl+c>