Linux信号机制

目录

一、信号的概念

 二、定时器

1. alarm函数

 2. setitimer函数

3.signal和sigaction函数

三、使用SIGCHLD信号实现回收子进程


一、信号的概念

        概念:信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式 。所有信号的产生及处理全部都是由内核完成的。

信号处理方式:

        1 缺省方式

        2 忽略信号

        3 捕捉信号        

在信号处理中,通常有三种处理方式:

  1. 缺省方式(Default):这是操作系统针对每种信号定义的默认行为。对于大多数信号,缺省行为是终止进程。例如,SIGINT 的缺省行为是终止进程。

  2. 忽略信号(Ignore):这种方式下,进程对收到的信号不做任何响应。这意味着当进程收到指定信号时,不会采取任何动作。这通常用于某些不需要处理的信号,或者是在某些特定情况下临时禁用信号处理器。

  3. 捕捉信号(Catch):这种方式下,进程定义一个信号处理函数,用于处理特定信号。当进程收到指定信号时,会调用这个信号处理函数。这允许程序员自定义对信号的处理方式,例如,收到 SIGINT 时执行特定的操作而不是终止进程。

        在如下代码中使用了第三种方式,即捕捉信号。通过调用 signal(SIGINT, handle),定义了一个信号处理函数 handle,用于处理 SIGINT 信号。当程序收到 SIGINT 信号时,会调用 handle 函数来处理它,而不是按照默认的方式终止进程。

 注意typedef void (*sighandler_t)(int);不能少

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>

typedef void (*sighandler_t)(int);
sighandler_t oldact;


void handle(int sig)
{
	printf("I cath the SIGINT\n");
	signal(SIGINT,oldact);
}

int main()
{
	
	oldact = signal(SIGINT,handle);
	while(1)
	{
		sleep(1);
	}
	return 0;
}

 代码注释:

使用 signal 函数来设置信号处理函数,并捕获 SIGINT 信号(通常由键盘上的Ctrl+C组合键发送)。这段代码的功能是在收到 SIGINT 信号时打印一条消息,并重新设置 SIGINT 信号的处理函数为先前的处理函数。

下面是代码的简要解释:

signal(SIGINT, handle):这行代码设置了 SIGINT 信号的处理函数为 handle。
在收到 SIGINT 信号时,系统将调用 handle 函数来处理该信号。

handle 函数:这是 SIGINT 信号的处理函数。当程序收到 SIGINT 信号时,会调用这个函数,并打印一条消息 "I catch the SIGINT"。
然后,它将信号的处理函数重新设置为 oldact,这样可以恢复先前的信号处理函数。

oldact:这是一个全局变量,用于保存先前 SIGINT 信号的处理函数。

while(1) 循环:这是一个无限循环,程序在这里持续运行,每次循环睡眠1秒钟。

为什么“它将信号的处理函数重新设置为 oldact,这样可以恢复先前的信号处理函数”?

         先前的处理函数,在这里就是系统的默认状态,即CTRL+C可以终止信号。

在这段代码中,oldact 保存了先前 SIGINT 信号的处理函数。
当程序收到 SIGINT 信号时,handle 函数会被调用。
在 handle 函数中,执行了 signal(SIGINT, oldact),这样做的目的是将 SIGINT 信号的处理函数重新设置为先前保存的处理函数 oldact。

这样做的原因是为了确保在 handle 函数执行完毕后,再次收到 SIGINT 信号时,会调用先前的处理函数,而不是再次调用 handle 函数。
这样可以恢复程序在接收 SIGINT 信号时的默认行为,或者是由用户自定义的其他行为。

        通过在调用 signal 函数时将 oldact 作为第二个参数传递进去,可以获取先前的信号处理函数。然后,你可以在需要的时候使用这个指针来重新设置信号处理函数,从而恢复到先前的处理方式。 

在 signal 函数中,oldact 是一个指向先前信号处理函数的指针。signal 函数的原型如下:

void (*signal(int signum, void (*handler)(int)))(int);

它返回了一个指向先前信号处理函数的指针。
这个指针的类型是 sighandler_t,它是一个函数指针类型,它指向一个接受 int 参数并返回 void 的函数。
  1. 当程序启动时,通常会有一些默认的信号处理方式。例如,对于 SIGINT 信号(通常由用户按下 Ctrl+C 发送),默认情况下会终止程序。

  2. 当你调用 signal(SIGINT, handle) 时,你指定了一个自定义的信号处理函数 handle,用于处理 SIGINT 信号。在这之前,如果有其他函数被注册为 SIGINT 信号的处理函数,那么 signal 函数会返回这个先前的处理函数,并将其保存在 oldact 变量中。

  3. 之后,如果你想要恢复先前的处理方式,你可以调用 signal(SIGINT, oldact),这样 SIGINT 信号会再次被先前的处理函数处理,而不是你自定义的 handle 函数。

常用信号:

信号名

含义

默认操作

SIGKILL

该信号用来结束进程,并且不能被捕捉和忽略

终止

SIGSTOP

该信号用于暂停进程,并且不能被捕捉和忽略

暂停进程

SIGTSTP

该信号用于暂停进程,用户可键入SUSP字符(

通常是Ctrl-Z)发出这个信号

暂停进程

SIGCONT

该信号让进程进入运行态

继续运行

SIGALRM

该信号用于通知进程定时器时间已到

终止

SIGUSR1/2

该信号保留给用户程序使用

终止

SIGCHLD

是子进程状态改变发给父进程的。

忽略

信号名

含义

默认操作

SIGHUP

该信号在用户终端关闭时产生,通常是发给和该

终端关联的会话内的所有进程

终止

SIGINT

该信号在用户键入INTR字符(Ctrl-C)时产生,内

核发送此信号送到当前终端的所有前台进程

终止

SIGQUIT

该信号和SIGINT类似,但由QUIT字符(通常是

Ctrl-\)来产生

终止

SIGILL

该信号在一个进程企图执行一条非法指令时产生

终止

SIGSEV

该信号在非法访问内存时产生,如野指针、缓

冲区溢出

终止

SIGPIPE

当进程往一个没有读端的管道中写入时产生,代

管道断裂

终止

 二、定时器

1. alarm函数

alarm 函数的原型如下:

        unsigned int alarm(unsigned int seconds);

        它接受一个无符号整数参数 seconds,表示定时器的超时时间,单位是秒。调用 alarm 函数会设置一个定时器,在指定的秒数之后,会产生 SIGALRM 信号。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void alarm_handler(int signum) 
{
    printf("Alarm signal received!\n");

}

int main() 
{
   
    signal(SIGALRM, alarm_handler);
    alarm(5);

    printf("Waiting for alarm...\n");

    while (1) 
    {
   
        sleep(1);
    }

    return 0;
}

   alarm(5) 函数调用设置了一个5秒的定时器。但是,在程序调用 alarm(5) 设置定时器之后,程序会立即继续执行下一条语句,而不会等待5秒钟。这意味着在调用 alarm(5) 之后,立即执行了 printf("Waiting for alarm...\n"); 这行代码,所以会立即打印出 "Waiting for alarm..."。然后,程序会进入 while 循环,在那里它会等待5秒钟,直到定时器超时并产生 SIGALRM 信号。

因此,程序执行的步骤是这样的:

  1. 执行 alarm(5); 设置一代码个5秒的定时器。
  2. 立即执行 printf("Waiting for alarm...\n");,打印 "Waiting for alarm..."。
  3. 进入 while 循环,程序会等待5秒钟。
  4. 定时器超时,产生 SIGALRM 信号,调用 alarm_handler 函数。
  5. 打印 "Alarm signal received!\n"。
  6. 程序继续执行 while 循环中的其他代码。

 2. setitimer函数

        int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

        功能:定时的发送alarm信号
        参数:
        which:   ITIMER_REAL:以逝去时间递减。发送SIGALRM信号、
                  ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号
       new_value:  负责设定 timout 时间                 
       old_value:   存放旧的timeout值,一般指定为NULL
        struct itimerval
        {
                struct timeval it_interval;  // 闹钟触发周期
                struct timeval it_value;    // 闹钟触发时间
        };
        struct timeval
         {
                 time_t      tv_sec;         /* seconds */
                suseconds_t tv_usec;        /* microseconds */
        };
#include<sys/time.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>


void timer_handler(int signum)
{
	printf("Timer expired!\n");
}

int main()
{
	struct itimerval timer;

	struct sigaction act;
	act.sa_handler = timer_handler;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);

	timer.it_value.tv_sec = 5;       // 5秒后定时器启动
	timer.it_value.tv_usec = 0;
	timer.it_interval.tv_sec = 1;    // 间隔1秒
	timer.it_interval.tv_usec = 0;

	sigaction(SIGALRM, &act,NULL);

	if (setitimer(ITIMER_REAL, &timer, NULL) == -1)
	{
		perror("setitimer");
		exit(EXIT_FAILURE);
	}


	while (1)
	{
		sleep(1);
	}

	return 0;
}

定义了一个信号处理函数 timer_handler,用于在定时器到期时打印 "Timer expired!"。

创建了一个 struct itimerval 结构体变量 timer,并设置了定时器的参数:

it_value 成员设置为 5 秒,表示定时器将在 5 秒后启动。
it_interval 成员设置为 1 秒,表示定时器在启动后每隔 1 秒触发一次。
创建了一个 struct sigaction 结构体变量 act,并设置了其中的成员:

sa_handler 成员设置为 timer_handler 函数,表示 SIGALRM 信号的处理函数为 timer_handler。
sa_flags 设置为 0,表示不设置特殊标志。
sa_mask 使用 sigemptyset 函数清空,表示在执行 timer_handler 函数期间不阻塞任何信号。
使用 sigaction 函数将 SIGALRM 信号的处理函数设置为 timer_handler 函数。

使用 setitimer 函数设置实时定时器,并传入 timer 结构体,启动定时器。

进入一个无限循环,程序将持续运行,每隔 1 秒调用 sleep(1) 函数,等待定时器的触发。

总的来说,这段代码实现了一个定时器,在程序启动 5 秒后启动,并且每隔 1 秒触发一次定时器,在定时器触发时打印一条消息。

3.signal和sigaction函数

signal函数: 

typedef void (*sighandler_t)(int);
sighandler_t  signal(int signum, sighandler_t handler);
功能:捕捉信号执行自定义函数
返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR
参数:
 signo 要设置的信号类型
 handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;  

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。


sigaction函数: 

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
参数:
signum:处理的信号
act,oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。

sigaction结构体成员定义如下:
sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似
sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域

三、使用SIGCHLD信号实现回收子进程

SIGCHLD的产生条件

        1子进程终止时

        2子进程接收到SIGSTOP信号停止时

        3子进程处在停止态,接受到SIGCONT后唤醒时

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<signal.h>
#include<string.h>
#include<errno.h>

void handle(int sig)
{
	wait(NULL);
	printf("GET sig = %d\n",sig);
}
int main()
{

	pid_t pid;
	struct sigaction act;
	act.sa_handler = handle;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);
	pid = fork();

	if(pid > 0)
	{
		//wait(NULL);
		sigaction(SIGCHLD,&act,NULL);
		while(1)
		{
			printf("this is father process\n");
			sleep(1);
		}
	}
	else if(pid == 0)
	{
		sleep(5);
		exit(0);
	}
	else
	{
		perror("fork");
		return 0;
	}
}
这段代码创建了一个父子进程,并使用 wait 函数等待子进程结束。
在父进程中,设置了 SIGCHLD 信号的处理函数为 handle 函数,用于处理子进程终止的信号。
具体来说,父进程会周期性地打印一条消息,而子进程在启动后会等待 5 秒后自行退出。

下面是这段代码的执行逻辑:

程序开始执行,父进程调用 fork 创建了一个子进程。

父进程中,如果 fork 成功(返回大于 0 的值),则进入了一个无限循环,不断地打印 "this is father process",并在其中使用 sigaction 函数设置了 SIGCHLD 信号的处理函数为 handle 函数。

子进程中,如果 fork 成功(返回 0),则进入了 if (pid == 0) 的分支,子进程会休眠 5 秒后自行退出。

如果 fork 出错(返回小于 0 的值),则父进程中输出错误信息并退出。

当子进程结束时,父进程会收到 SIGCHLD 信号,进而调用 handle 函数来处理。在 handle 函数中,调用 wait 函数等待子进程结束,并打印 "GET sig = xx" 的信息,其中 xx 是接收到的信号值。

总的来说,这段代码通过信号处理机制实现了在父进程中对子进程的终止进行处理。

wait(NULL)

当一个父进程调用 `wait(NULL)` 时,它会发生以下事情:

1. **阻塞父进程**:如果没有任何子进程已经结束,`wait(NULL)` 会阻塞父进程,直到至少有一个子进程结束。
2. **回收子进程**:一旦有子进程结束,`wait(NULL)` 会回收该子进程的资源。这意味着操作系统会清理与该子进程相关的所有资源,比如内存和进程控制块。
3. **不指定子进程**:由于 `wait(NULL)` 不指定等待特定的子进程,它适用于等待任何一个子进程。如果需要等待特定的子进程,可以使用 `waitpid(pid, &status, options)` 函数,其中 `pid` 是特定子进程的进程号。
linux wait(null)函数_wait(null)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_44652882/article/details/134363936

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux信号机制是进程间通信的一种方式。它通过向进程发送信号来通知进程发生了某些事件,例如用户按下了某个键,或者内核发现了一个错误等等。进程可以选择忽略信号,或者处理信号并执行一些特定的操作。 为了进行Linux信号机制的实验,可以按照以下步骤进行: 1. 编写一个简单的C程序,用于演示信号的发送和接收。程序可以使用signal()函数来处理信号。在该程序中,可以使用kill()函数向指定进程发送信号,或者使用raise()函数向当前进程发送信号。 2. 运行该程序,并使用ps命令查看进程的PID。然后,可以使用kill命令向该进程发送信号,例如: ``` kill -SIGINT <PID> ``` 这将向指定的进程发送一个SIGINT信号,该信号通常用于中断进程。 3. 在程序中添加信号处理函数,以便在收到信号时执行特定的操作。例如,可以使用sigaction()函数来注册信号处理程序,如下所示: ``` struct sigaction sigact; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigact.sa_handler = handle_signal; sigaction(SIGINT, &sigact, NULL); ``` 这将注册一个名为handle_signal()的函数,用于处理SIGINT信号。 4. 可以编写多个程序,并使用信号来实现进程间通信。例如,一个进程可以向另一个进程发送信号,以通知它执行某些操作。 5. 最后,可以尝试使用其他类型的信号,例如SIGTERM、SIGKILL等,以了解它们的不同之处。 总之,Linux信号机制是一个非常强大的工具,可以用于实现进程间通信、错误处理等。通过实验,我们可以更深入地了解信号的工作原理,并学会如何使用它们来编写更高效的程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值