首页 > 系统相关 >Linux下进程间的通信--信号

Linux下进程间的通信--信号

时间:2024-08-24 19:56:57浏览次数:10  
标签:函数 -- pid 通信 发送 信号 Linux 进程 include

信号的概念:

在Linux操作系统中,信号是一种软件中断机制,用于通知进程某个事件已经发生。信号是Linux进程间通信(IPC)的一种简单且快速的方式,它可以用来处理各种异步事件,如用户输入、硬件事件或软件条件。

信号的特点:

进程可以选择阻塞某些信号,阻止这些信号的传递。

每个信号都有一个默认行为,例如终止进程、忽略或停止进程等

信号有不同的优先级,高优先级的信号会打断低优先级的信号处理。

信号可以跨越进程边界,由一个进程发送给另一个进程

信号机制是由操作系统内核实现的,与操作系统的调度和资源管理紧密相关。

信号的来源:

在Linux操作系统中,信号可以由多种来源产生,用于通知进程发生了某些重要事件。

  1. 硬件异常

    • SIGSEGV:尝试访问未分配的内存或无效的内存区域。
    • SIGFPE:发生浮点异常,如除以零。
    • SIGILL:执行了非法指令。
  2. 软件条件

    • SIGINT:通常由用户通过按下Ctrl+C产生,用于中断正在运行的程序。
    • SIGTERM:默认信号,用于请求程序自己终止。
    • SIGALRM:由alarm函数设置的计时器到期时产生。
  3. 用户干预

    • SIGKILL:由kill命令发送,用于立即终止程序,无法被捕获或忽略。
    • SIGSTOP:由kill命令发送,用于停止程序的执行,无法被捕获、忽略或由用户生成。
  4. 系统调用

    • SIGCHLD:子进程结束时,父进程会收到此信号。
    • SIGHUP:当控制终端关闭时,如关闭或拔出调制解调器,相关进程会收到此信号。
  5. 资源限制

    • SIGPIPE:写入一个没有读进程的管道时产生。
    • SIGXCPU:超过CPU时间限制。
    • SIGXFSZ:超过文件大小限制。
  6. I/O操作和设备状态变化

    • SIGURG:有紧急数据可从套接字读取时产生。
    • SIGIO:文件描述符上的I/O操作现在可进行时产生。
  7. 调度器事件

    • 某些实时调度器可能会在特定事件发生时发送信号。
  8. 外部设备或硬件设备

    • 特定硬件设备可能会在检测到特定事件时发送信号

信号的种类:

在Linux系统可以通过 kill -l 命令查看

 常用信号及其编号:

SIGHUP (1):挂起信号,通常在终端关闭时发送给前台进程组。

SIGINT (2):中断信号,通常由用户通过按下Ctrl+C产生。

SIGILL (4):非法指令信号,当进程执行非法指令时发送。

SIGABRT (6):中止信号,由abort()函数调用产生,用于异常终止进程。

SIGBUS (7):总线错误信号,当硬件异常,如内存访问错误时发送。

SIGFPE (8):浮点异常信号,如算术溢出、除以零等。

SIGKILL (9):杀死信号,用于立即终止进程,无法被捕获或忽略。

SIGUSR1 (10):用户定义信号1,用途由用户自定义。

SIGSEGV (11):段错误信号,当访问无效内存段时发送。

SIGUSR2 (12):用户定义信号2,用途由用户自定义。

SIGPIPE (13):管道信号,当写入一个没有读进程的管道时发送。

SIGTERM (15):终止信号,用于请求程序自己终止,可以被捕获或忽略。

SIGCHLD (17):子进程结束信号,当子进程结束时发送给父进程。

SIGCONT (18):继续信号,用于唤醒一个被停止的进程。

SIGSTOP (19):停止信号,用于停止进程的执行,无法被捕获或忽略。

SIGTTIN (21):后台进程试图读终端时发送。

SIGTTOU (22):后台进程试图写终端时发送。

SIGIO (29):I/O信号,当文件描述符上有可进行的I/O操作时发送。

信号处理的流程:

信号处理流程包含以下两个方面:

1. 信号的发送

信号的发送可以由以下几种方式触发:

  • 用户操作:用户可以通过键盘产生信号,如按下Ctrl+C通常发送SIGINT(中断信号)。
  • 软件生成:程序可以通过kill系统调用或raise函数发送信号给其他进程或自身。
  • 硬件异常:当程序执行非法操作(如除零、内存访问违规)时,硬件会触发信号,如SIGFPE(浮点异常)或SIGSEGV(段错误)。
  • 系统条件:系统在特定条件下也会发送信号,如SIGHUP(挂起信号)可能在控制终端关闭时发送。
  • 定时器超时:使用alarmsetitimertimer等定时器函数设置的定时器超时后,会发送如SIGALRMSIGVTALRM信号。

2. 信号的投递与处理

信号的投递与处理涉及以下几个步骤:

  • 信号队列:当一个信号被发送给进程时,它首先被放入进程的信号队列中。
  • 信号屏蔽:进程可以通过设置信号掩码(使用sigprocmask函数)来阻止某些信号的投递。被屏蔽的信号不会立即投递,直到它们被进程从屏蔽列表中移除。
  • 信号投递:内核负责将信号从队列中取出并投递给进程。如果信号未被屏蔽,内核会根据信号处理函数的设置来处理信号。
  • 默认处理:如果进程没有为信号设置处理函数,或者信号被忽略(使用signalsigaction函数设置为SIG_IGN),内核将执行信号的默认操作,如终止进程或忽略信号。
  • 用户定义的处理:如果进程为信号定义了处理函数,内核在信号到达时调用该函数。处理函数可以执行任何清理或响应操作。
  • 处理完成:信号处理函数执行完毕后,进程恢复到信号到达前的状态,继续执行。如果信号处理函数调用了如exit_Exit等函数,进程将终止。

在 Linux 中对信号的处理方式如下:

 在内核中的用于管理进程的结构为 task_struct ,

1.忽略信号

即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

SIGKILL用于立即终止进程,而SIGSTOP用于停止进程的执行。这两个信号是为了保证系统管理员能够控制所有进程而设计的。

2.捕捉信号

  • 进程可以捕捉信号,并定义自己的信号处理函数。当信号发生时,如果进程为该信号注册了处理函数,内核会暂停进程的执行,转而执行该信号的处理函数。
  • 信号处理函数可以是标准函数,如signal()函数
signal()函数
函数头文件
#include <signal.h>

函数原型
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

函数参数
signum:指定要处理的信号的编号。例如,SIGINT(通常由Ctrl+C产生)。
handler:指向一个函数的指针,该函数将被调用以处理指定的信号。这个函数可以是程序自定义的,也可以是几个标准函数之一:
SIG_DFL:执行信号的默认操作。
SIG_IGN:忽略该信号

函数返回值
signal函数返回之前为该信号设置的处理函数的指针。如果之前没有设置处理函数,或者设置的是默认操作,它将返回SIG_DFL。如果之前设置的是忽略该信号,它将返回SIG_IGN。
如果signal函数调用失败,它将返回SIG_ERR,errno将被设置以指示错误原因
示例代码:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signal_handler(int sig) {
    printf("Received signal %d\n", sig);
    // 执行必要的动作
}

int main() {
    // 设置SIGINT信号的处理函数
    signal(SIGINT, signal_handler);
    while(1) {
        sleep(1);
        printf("Looping...\n");
    }
    return 0;
}

3.执行默认操作

如果进程没有特别处理某个信号,Linux系统会为该信号执行默认操作。默认操作通常是终止进程、忽略信号或停止进程。

  • SIGTERM(信号15):默认操作是终止进程。
  • SIGCHLD(信号17):默认操作是忽略,通常用于子进程结束时通知父进程。
  • SIGCONT(信号18):默认操作是继续执行之前被停止的进程

信号发送操作

在Linux中,kill()raise() 函数是用于发送信号的两个常用方法。它们允许进程之间相互发送信号,或者进程可以向自己发送信号

1.kill()函数

kill() 函数可以向一个进程或进程组发送信号。这是最常用的信号发送方式,尤其是在需要向其他进程发送信号时。

​函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int kill(pid_t pid, int sig);

函数参数
pid:目标进程的进程ID(PID)。
如果pid为正,则信号信号被发送到具有pid指定的ID的进程。
如果pid等于0,则向调用进程的进程组中的每个进程发送sig。
如果pid等于-1,则sig将发送到调用进程有权发送信号的每个进程,进程1 (init)除外
如果pid小于-1,则向进程组中ID为-pid的每个进程发送sig
sig:要发送的信号。可以是任何有效的信号,如SIGINT、SIGTERM等。

函数返回值
成功 返回0。
失败 返回-1,并设置errno以指示错误原因。

​

2.raise()函数

函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int raise(int sig);

函数参数
sig:要发送给调用进程的信号编号。这是一个整数,表示特定的信号,如SIGINT、SIGTERM等。

函数返回值
成功 返回0。
失败 返回-1,并设置errno以指示错误原因

等待信号操作

在Linux系统中,pause() 函数用于使调用它的进程挂起,直到它接收到一个信号。这是一个简单的阻塞调用,常用于进程初始化后等待异步信号的情况

pause()函数

函数头文件
#include <unistd.h>

函数原型
int pause(void);

函数描述
pause() 函数使调用它的进程挂起,直到它捕获到一个信号。该进程将一直停留在pause()调用处,直到有信号到达并被处理。
该函数没有参数。
它返回时,通常意味着一个信号已经被接收并处理。

函数返回值
pause() 在信号处理函数执行后返回。如果信号处理函数返回,pause() 将返回。
如果因错误而失败,它将返回-1,并设置errno以指示错误原因

总结:

示例代码:

下面程序展示了如何使用信号和fork来创建和协调多个进程。父进程创建两个子进程,然后向它们发送不同的信号。第一个子进程等待并接收SIGUSR1信号,第二个子进程等待并接收SIGUSR2信号。子进程使用pause挂起等待信号,接收到信号后继续执行并退出。父进程等待子进程结束后也退出。这个程序展示了信号在进程间通信中的应用,并且正确地处理了子进程的结束,使用了wait(NULL)来避免子进程成为僵尸进程。

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

typedef void (*sighandler_t)(int);
void sighandler(int signum)
{
        printf("sighandle:%s\n",strsignal(signum));
}
int main()
{
        sighandler_t sig = signal(SIGUSR1,sighandler);
        if(sig==SIG_ERR)
        {
                perror("signal sig");
                exit(EXIT_FAILURE);
        }
        sighandler_t sigt  = signal(SIGUSR2,SIG_DFL);
        if(sigt==SIG_ERR)
        {
                perror("signal sigt");
                exit(EXIT_FAILURE);
        }
        pid_t pid = fork();
        if (pid == -1)
        {
                perror("fork failed");
                return 1;
        }
        else if(pid == 0)
        {
                printf("<1>chilb process pid :%d\n",getpid());
                pause();
                printf("<1>chilb process end\n");
                exit(EXIT_SUCCESS);
        }
        else
        {
                pid_t Apid = fork();
                if(Apid==-1)
                {
                        perror("fork1:");
                        exit(EXIT_FAILURE);
                }
                else if(Apid==0)
                {
                        printf("<2>chilb process Apid :%d\n",getpid());
                        pause();
                        printf("<2>chilb process end\n");
                        exit(EXIT_SUCCESS);
                }
                else
                {
                        printf("parent process pid :%d\n",getpid());
                        sleep(1);
                        int result = kill(pid,SIGUSR1);
                        if(result==-1)
                        {
                                perror("kill pid");
                                exit(EXIT_FAILURE);
                        }
                        int ret = kill(Apid,SIGUSR2);
                        if(ret==-1)
                        {
                                perror("kill Apid");
                                exit(EXIT_FAILURE);
                        }
                        printf("parent process end\n");
                }
        }
        wait(NULL);
        return 0;
}

代码解读:

定义sighandler_t类型,它是一个指向函数的指针,该函数接受一个整数参数(信号编号)并且没有返回值。

定义sighandler函数,它是一个信号处理函数,当接收到信号时被调用。它使用strsignal函数将信号编号转换为信号名称的字符串,并打印出来。

main函数中,首先设置SIGUSR1信号的处理函数为sighandler。如果设置失败,打印错误信息并退出。

SIGUSR2信号的处理函数设置为默认操作(SIG_DFL)。如果设置失败,打印错误信息并退出。

 创建第一个子进程。如果fork失败,打印错误信息并返回1。

 如果当前是子进程(pid == 0),打印子进程的PID,然后调用pause挂起等待信号。接收到信号后,打印结束信息并退出。

如果当前是父进程,再创建第二个子进程。如果fork失败,打印错误信息并退出。

如果当前是第二个子进程(Apid == 0),打印子进程的PID,然后调用pause挂起等待信号。接收到信号后,打印结束信息并退出。

如果当前是父进程,等待1秒,然后向第一个子进程发送SIGUSR1信号,向第二个子进程发送SIGUSR2信号。如果发送信号失败,打印错误信息并退出。

父进程打印结束信息。

父进程调用wait(NULL)等待任一子进程结束。

结语:

无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力

 

 

标签:函数,--,pid,通信,发送,信号,Linux,进程,include
From: https://blog.csdn.net/2301_79695216/article/details/141473872

相关文章

  • 基于SSM的校园二手交易平台【附源码+文档】
    ......
  • 毕业设计基于SSM/Springboot的商城项目
    一、前言博主介绍:提供有偿定制化修改疑难代码问题,也可以私信,包括问题定位,代码运行,后台留言答疑[承接Java毕设]。API接口访问http://127.0.0.1:8081/shop/swagger-ui.html管理员访问http://127.0.0.1:8081/shop/admin/login普通用户访问http://127.0.0.1:8081/sh......
  • 业务逻辑漏洞
    什么是业务逻辑?       实际上,我们口口声声的业务逻辑,是只用代码实现的真实业务的规则映射。注意“规则”这个词,简单说,一个业务中,存在什么逻辑,可以通过在纸上画出不同业务对象之间的联系和约束,并将这些联系和约束一条条列出来,形成一个列表,而这列表中的每一条,就是一条......
  • 怎么实现用frp搭建一个自己的内网穿透服务
    使用frp搭建一个自己的内网穿透服务包括以下几个步骤:配置frp服务器(服务端)和frp客户端。Frp是什么:frp(FastReverseProxy)是一款高性能的反向代理应用,广泛用于内网穿透、跨网络访问等场景。以下是frp的一些常见应用场景:1.内网服务的外网访问frp可以将内网中的Web......
  • Linux 命令管道介绍
    今天给伙伴们分享一下Linux命令管道,希望看了有所收获。我是公众号「想吃西红柿」「云原生运维实战派」作者,对云原生运维感兴趣,也保持时刻学习,后续会分享工作中用到的运维技术,在运维的路上得到支持和共同进步!如果伙伴们看了文档觉得有用,欢迎大家关注我的公众号,获取相关文......
  • 开发指南056-定时任务
    业务场景中定时任务很常见。平台实现定时任务的原则如下:1、定时任务的定义在业务库(没必要集中到核心库,另外定时任务的服务要访问业务库)。2、定时任务的服务为独立微服务。平台的定时任务基于:    <dependency>      <groupId>org.quartz-scheduler</gr......
  • c语言解决所有认识的排序(默认升序)
    库函数(不讲武德法)intcmp(constvoid*a,constvoid*b){   return*(int*)a-*(int*)b;} 调用qsort(nums,numsize,sizeof(int),cmp;元素什么类型自己改一下就行了。可以对a,b进行操作降序或者偶奇排序。选择排序voida(int*a,intn){  for(inti=0;i<n;......
  • c++贪心、模拟超详细讲解
    一、贪心算法基础1.1定义与原理定义:贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。原理:贪心算法通过局部最优选择来构造全局最优解。在每一步,算法都做出一个看起来最优的决策,期望通过局部最优达到全局......
  • c++SPFA细剖
    SPFA(ShortestPathFasterAlgorithm),作为一种高效的最短路算法,是Bellman-Ford算法的改进版本,结合了Dijkstra算法和Bellman-Ford算法的优点。下面我将从十个大的方面对SPFA算法进行详细剖析,每个大点下再分若干小点进行阐述。一、算法背景与起源1.1算法起源SPFA算法由西安交......
  • 能精准捕捉股价波峰波谷的 Findpeaks
    作者:老余捞鱼原创不易,转载请标明出处及原作者。写在前面的话:    在AI对金融产品进行价值分析中,检测波峰波谷具有至关重要的应用意义。投资者可以直接观察股票价格走势图,通过肉眼识别波峰和波谷的位置。这种方法简单易行,但主观性较强,可能受到投资者个人经验和情绪......