进程间通信——信号

本文详细介绍了Linux系统中的信号机制,包括信号的产生、处理方式和常用信号。信号用于处理异步事件,如用户交互、硬件异常或软件条件。常见的信号如SIGINT、SIGQUIT、SIGKILL和SIGSTOP等,有各自的默认行为和处理函数。文章还展示了如何使用kill函数发送信号以及如何注册信号处理函数。此外,通过示例代码演示了如何通过捕获SIGCHLD信号来回收子进程,确保资源的有效管理。
摘要由CSDN通过智能技术生成

信号

信号是软件中断,它提供了一种处理异步事件的方法。每个信号都有名字,这些名字以SIG开头,如:SIGALRM是闹钟信号,由alarm函数设置的定时器超时后将产生此信号。 信号都定义在<signal.h>头文件中,并且都是正整数常量。
在这里插入图片描述
产生信号的条件:

  1. 按键事件信号:如输入Ctrl+C,通常产生中断信号(SIGINT),停止程序。
  2. 硬件异常信号:除数为0,无效内存引用等。这些条件通常是硬件检测到,并且通知linux内核,然后内核产生适当地信号。例如,对执行无效内存引用的进程产生SIGSEGV信号。
  3. 进程调用kill函数可将任意信号发送给另一个进程或进程组。但是注意, 接收信号进程和发送信号进程的所有者必须相同,或发送信号的进程的所有者必须是超级用户。
  4. 用户可用kill命令将信号发给其他进程,该命令是kill函数的接口,通常用此命令结束后台进程。
  5. 当检测到某种软件条件已经发生,并且应将其通知有关进程时也会产生信号,这类信号是软件产生。如,SIGURG(在网络连接上传来带外数据)、SIGPIPE(在管道的读端全部关闭时,进程对管道进行写操作)、SIGALRM(进程设置的定时器超时)。

内核对信号的处理方式:

  1. 默认处理方式,对于大多数信号,系统默认处理方式是终止进程。
  2. 忽略,大多数信号可以这样处理,但是对于SIGKILL和SIGSTOP信号必须处理,不能忽略,因为这两个信号向内核和超级用户提供了使进程终止或停止的可靠方法
  3. 捕捉并注册信号的特殊处理函数,注意不能捕捉SIGKILL和SIGSTOP信号。当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为默认动作,除非调用exec的进程忽略信号。确切地说,exec函数将原来设置为要捕捉的信号都更改为默认动作,其他信号的处理方式保持不变

常用信号:

  1. SIGHUP:当用户退出shell时,由该shell启动的所有进程都会收到这个信号,默认动作是终止进程。
  2. SIGINT:当用户按下<Ctrl+C>组合按键时,用户终端中当前正在运行的进程将收到此信号,默认动作是终止进程。
  3. SIGQUIT:当用户按下<Ctrl+>组合按键时,用户终端中当前正在运行的进程将收到此信号,默认动作是终止进程。
  4. SIGBUS:非法访问内存地址,包括内存对其出错,默认动作为终止进程并产生core文件。
  5. SIGFPE:发生致命的运算错误时发出此信号。包括浮点数运算错误、溢出、除数为0等,默认动作为终止进程并产生core文件。
  6. SIGKILL:无条件终止进程,本信号不能被忽略、捕捉和阻塞、默认动作为终止进程。他向系统管员提供了可以杀死任何进程的方法
  7. SIGSTOP:无条件终止进程,本信号不能被忽略、捕捉和阻塞、默认动作为暂停进程
  8. SIGUSR1:用户定义的信号,即程序员可以在程序中定义并使用该信号,默认动作为终止进程。
  9. SIGUSR2:用户定义的信号,即程序员可以在程序中定义并使用该信号,默认动作为终止进程。
  10. SIGSEGV:提示进程访问了非法内存,默认动作为终止进程并产生core文件
  11. SIGPIPE:向一个读端关闭的管道写数据时产生,默认动作为终止进程
  12. SIGCHLD:子进程状态发生改变,父进程会收到该信号,默认动作为忽略该信号
  13. SIGALRM:定时器超时,超时时间由alarm系统调用设置,默认动作为终止进程。
  14. SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止,通常用来表示程序正常退出。在shell中执行kill命令时默认产生这个信号,默认动作为终止进程。

信号常用函数kill

//包含的头文件
#include <sys/types.h>
#include <signal.h>

//函数原型
int kill(pid_t pid, int sig);  //通过信号杀死进程
参数:
    pid > 0, 发信号给指定进程。
    pid = 0, 发信号给与调用kill函数进程同一组的进程
    pid < -1,取绝对值,发信号给绝对值所对应的进程组的所有进程
    pid = -1,发信号给有权限发送的所有进程,比较危险,可能导致系统崩溃,慎用。
    sig, 信号值
返回值:
    成功,返回0;失败,返回-1

demo1: 循环创建N个子进程,父进程指定杀死任意一个子进程

/*
* 功能:循环创建N个子进程,父进程指定杀死任意一个子进程。
* 注意点:
* 1. 父进程想杀死的子进程不能在父进程调用kill之前就已经退出了
* 2. 父进程杀死子进程之后要做善后处理,也就是回收子进程的进程控制块,防止子进程成为僵尸进程
* 3. 子进程不能被重复杀死
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>

#define   N   5

int main(int argc, char* argv[])
{
    int i = 0, n = -1;
    pid_t pid;
    pid_t * childPID = (pid_t *)malloc(sizeof(pid_t)*N);
    for(i = 0; i < N; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            printf("I'am %dth child process and my id is %d\r\n", i+1, getpid());
            break;
        }
        else if(pid < 0)
        {
            printf("fork failed\r\n");
            exit(1);
        }
        childPID[i] = pid;          //保存子进程的ID到数组中
    }
    if(i < N)
    {
        while(1);  //子进程死循环,不然子进程一退出就成了僵尸,父进程调kill函数是无效的
    }
    if(i == N)
    {
        sleep(1);
        while(1)
        {
            printf("please input you want to kill the child is:");
            scanf("%d", &n);
            if(n<1 || n>N || (childPID[n]==-1))
            {
                printf("you want to kill obj is not exist\r\n");
                break;
            }
            printf("will kill the ID %d child process\r\n", childPID[n-1]);
            kill(childPID[n-1], SIGKILL);//杀死指定的子进程,杀死后子进程成为僵尸进程
            waitpid(childPID[n-1], NULL, 0);  //收尸
            childPID[n-1] = -1;
        }
    }

    return 0;
}

信号常用函数alarm

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

#include <sys/time.h>
int getitimer(int which, struct itimerval *curr_value);
int setitimer(int which, const struct itimerval *new_value,
                struct itimerval *old_value); //闹钟,周期定时功能

struct itimerval {
    struct timeval it_interval; /* Interval for periodic timer */
    struct timeval it_value;    /* Time until next expiration */
};

struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigpending(sigset_t *set);  //获取未决信号集
函数sigemptyset初始化由set指向的信号集,清除其中所有信号;
函数sigfillset初始化由set指向的信号集,使能其中所有信号。
所有应用程序在使用信号集之前,要调用sigemptyset或sigfillset对信号集初始化。
函数sigaddset将一个信号添加到已有的信号集,函数sigdelset则从已有信号集中删除一个信号。
函数sigismember测试一个信号是否在信号集里面。

函数实现:
int sigaddset(sigset_t *set, int signum)
{
    if(SIGBAD(signum))
    {
        errno = EINVAL;
        return -1;
    }
    *set |= 1 << (signum-1);  
    return 0;
}
int sigdelset(sigset_t *set, int signum)
{
    if(SIGBAD(signum))
    {
        errno = EINVAL;
        return -1;
    }
    *set &= ~(1 << (signum-1));
    return 0;
}

int sigdelset(sigset_t *set, int signum)
{
    if(SIGBAD(signum))
    {
        errno = EINVAL;
        return -1;
    }
    return ((*set & (1 << (signum-1))) != 0);
}
信号处理函数:
#include <signal.h>
typedef void (*sighandler_t)(int);  //定义信号处理函数类型
sighandler_t signal(int signum, sighandler_t handler); //用来注册信号处理函数

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);
};
信号处理函数使用方法:
step1:定义信号处理函数
void signal_handler(int arg)
{
	//信号处理代码
}
step2: 注册
int main(int argc, char ** argv)
{
	signal(signum, signal_handler);
	return 0;
}

信号捕捉特性:

  1. 信号捕捉函数执行期间,信号屏蔽字sa_mask生效,捕捉函数执行完后恢复原先的mask值。
  2. 信号捕捉函数执行期间,本信号默认被屏蔽(sa_flags = 0),不能递达。
  3. 信号捕捉函数执行期间,被屏蔽信号多次产生,在捕捉函数执行完成只会处理一次

SIGCHLD产生条件:

  1. 子进程终止
  2. 子进程接收到SIGSTOP信号
  3. 子进程处于暂停态,接收到SIGCONT唤醒
    总之,子进程状态发生改变内核就会产生SIGCHLD信号

demo2:循环创建N个子进程,父进程通过捕捉SIGCHLD信号对子进程进行回收

//通过信号的方式来对子进程进行回收
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

#define   N   10

void signal_handler(int signum)
{
    int status;
    while(wait(&status)>0)  //通过循环解决信号处理期间,多个子进程死亡出现僵尸进程。
    {						
        if(WIFEXITED(status))
        {
            printf("catch child exit\n");
        }
        else if(WIFSIGNALED(status))
        {
            printf("child recived a signal");
        }
    }

    return;
}

int main(int argc, char* argv[])
{
    int i = 0, n = -1;
    pid_t pid;

    //把SIGCHLD信号设置阻塞,防止在注册处理函数之前子进程就退出了,从而导致信号无法处理
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);			  //把SIGCHLD信号添加到集合
    sigprocmask(SIG_BLOCK, &set, NULL);   //解除SIGCHLD信号阻塞

    for(i = 0; i < N; i++)
    {
        pid = fork();
        if(pid == 0)
        {
            printf("I'am %dth child process and my id is %d\r\n", i+1, getpid());
            break;
        }
        else if(pid < 0)
        {
            printf("fork failed\r\n");
            exit(1);
        }
    }

    if(i == N)   //父进程
    {
        struct sigaction act;
        act.sa_handler = signal_handler;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD, &act, NULL);  //注册信号处理函数
        sigprocmask(SIG_UNBLOCK, &set, NULL);	//解除SIGCHLD信号阻塞

        while(1);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值