Linux中进程通信之信号

news2024/12/23 20:54:43

信号

信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

关于信号指令的查看:kill -l

例如我们之前使用的kill -9 pid用于杀死一个进程

使用一个死循环

成功发送kill -9指令,杀死该进程

信号通信的框架

        信号的发送(发送信号进程):kill、raise、alarm

        信号的接收(接收信号进程) : pause()、 sleep、 while(1)

        信号的处理(接收信号进程) :signal

相关信号的含义

1.信号的发送(发送信号进程)

kill:

所需头文件:

#include <signal.h>

#include <sys/types.h>

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

参数:          

        函数传入值:pid                     

                正数:要接收信号的进程的进程号                     

                0:信号被发送到所有和pid进程在同一个进程组的进程                      

                 ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)           

        sig:信号   

函数返回值:

                成功  0  

                出错  ‐1  

所以我们可以使用一个封装的函数来使用信号命令

也可以发送信号杀死进程

2.raise: 发信号给自己 == kill(getpid(), sig)

所需头文件:     #include <signal.h>

                        #include <sys/types.h>

函数原型:  int  raise(int sig); 

参数:               

        函数传入值:sig:信号   

函数返回值:                

        成功  0  

        出错  ‐1 

我们先写一个简单的例子

成功对自己发送了信号

再来举个例子

父子进程,对子进程发送暂停信号(SIGTSTP)

程序运行8秒之内,父进程处于s+状态(睡眠),子进程处于T+状态(暂停)

8秒之后,父进程处于R+状态(运行)(因为while(1)循环),子进程仍旧处于T+状态

我们对代码进行修改

父进程S+状态,子进程T+状态

父进程R+状态,子进程Z+(变为僵尸进程),因为父进程想要回收,但是子进程被命令杀死,父进程还处于运行态无法进行回收,子进程变为僵尸进程。

我们在父进程执行循环之前加上阻塞函数等待子进程并回收

8秒之前还是父进程S+状态,子进程T+状态

而在8秒之后,父进程开始运行循环,进入R+(运行态),子进程成功被回收

3.alarm : 发送闹钟信号的函数

alarm 与 raise 函数的比较:

相同点:

                让内核发送信号给当前进程

不同点:

                alarm 只会发送SIGALARM信号

                alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号

所需头文件#include   <unistd.h>

函数原型 unsigned int  alarm(unsigned int seconds)   

参数:          

        seconds:指定秒数 

返回值:            

        成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余      时间,否则返回0。     

        出错:‐1 

举个简单例子

信号的接收

接收信号的进程,要有什么条件:要想使接收的进程能收到信号,这个进程不能结束 :

sleep

pause:进程状态为S(休眠状态)

函数原型 int  pause(void);   

函数返回值  

成功:0,出错:‐1 

3.信号的处理

收到信号的进程,应该怎样处理? 处理的方式:

1.进程的默认处理方式(内核为用户进程设置的默认处理方式)

A:忽略B:终止进程C: 暂停

2.自己的处理方式: 自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式、

所需头文件  #include <signal.h>   

函数原型  void (*signal(int signum, void (*handler)(int)))(int);   

函数传入值              

 signum:指定信号               

        handler                      

                SIG_IGN:忽略该信号。    //ignore                

                SIG_DFL:采用系统默认方式处理信号                 

                自定义的信号处理函数指针   

函数返回值              

        成功:设置之前的信号处理方式                 

        出错:‐1   

signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函 数;这个函数的返回值是一个函数指针。

上面函数的执行顺序

  1. 程序开始执行
    • 程序进入main函数。
  2. 设置信号处理函数
    • 调用signal(SIGALRM, myfun);,这告诉操作系统当接收到SIGALRM信号时,应该调用myfun函数来处理它。
  3. 打印"before alarm"
    • 执行printf("before alarm\n");,在控制台上输出before alarm
  4. 设置alarm
    • 调用alarm(7);设置一个7秒后的定时器,到时后向进程发送SIGALRM信号。
  5. 打印"after alarm"
    • 立即执行printf("after alarm\n");,在控制台上输出after alarm此时,虽然alarm已经设置,但SIGALRM信号尚未发送,因此myfun函数尚未被调用。
  6. 进入主循环
    • 开始执行while (i < 10)循环。此时,i的初始值为0。
    • 在循环的每次迭代中,i递增,然后调用sleep(1);暂停1秒。
    • 接着,打印当前i的值(例如process 1process 2等)。
  7. SIGALRM信号发送
    • 大约7秒后(从alarm(7);调用开始计时),操作系统向进程发送SIGALRM信号。
    • 由于之前已经通过signal(SIGALRM, myfun);设置了信号处理函数,因此myfun函数被调用。
  8. 执行myfun函数
    • myfun函数内部有一个局部变量i(注意,这与main函数中的i不同),它从0开始,并在循环中递增到9。
    • myfun函数的循环中,每次迭代都会打印出信号编号(SIGALRM的编号,通常为14,但这里使用signum变量表示)和循环计数器的值(局部于myfuni)。
    • 每次打印后,myfun函数中的i递增,并调用sleep(1);暂停1秒。
  9. myfun函数完成
    • myfun函数中的i达到10时,循环结束,myfun函数返回。
  10. 主循环继续
    • myfun函数返回时,主循环(在main函数中)继续执行。由于main函数中的imyfun函数执行期间也在递增,因此主循环将继续执行直到其i也达到10。
  11. 程序结束
    • main函数中的i达到10时,while循环结束,main函数返回0,程序正常结束

使用SIG_IGN函数

使用SIG_DFL函数

信号父子进程之间的通讯

关于下面的函数,几个注意点:

为了避免子进程成为僵尸进程,我们需要对其进行资源回收,但是如果在调用第一个函数signal之后就回收,会导致下面的代码不能按照预期的进行运行

exit()也是一个信号指令,

成功调用signal1函数,目的是使用wait(NULL)进行阻塞,从而达到回收子进程回收资源的目的

子进程回收资源成功

信号灯

信号灯集合(可以包含多个信号灯)IPC对象是一个信号的集合(多个信号量)

semaphore

函数原型:     

        int semget(key_t key, int nsems, int semflg); 

        //创建一个新的信号量或获取一个已经存在的信号量的键值。 

所需头文件:                

 #include <sys/types.h>              

 #include <sys/ipc.h>                

#include <sys/sem.h>

函数参数:               

         key:和信号灯集关联的key值                

        nsems:  信号灯集中包含的信号灯数目                

        semflg:信号灯集的访问权限 

函数返回值:                 

        成功:信号灯集ID                 

        出错:‐1 

函数原型:

        int semctl ( int semid, int semnum,  int cmd,…union semun arg(不是地址)); 

        //控制信号量,删除信号量或初始化信号量 

所需头文件:                   

 #include <sys/types.h>              

 #include <sys/ipc.h>                

#include <sys/sem.h>

函数参数:                   

        semid:信号灯集ID                   

        semnum: 要修改的信号灯编号                   

        cmd :                            

                GETVAL:获取信号灯的值                           

                SETVAL:设置信号灯的值                           

                IPC_RMID:从系统中删除信号灯集合 

函数返回值:                  

        成功:0                  

        出错:‐1      

删除成功,这里删除的是最后一个生成的信号灯。

PV操作

基本概念

  • 信号量(Semaphore):一个整型变量,用于表示资源的数量。信号量的值大于0时,表示可用资源的数量;值为0时,表示没有可用资源;值为负数时,其绝对值表示等待该资源的进程数。
  • P操作(Wait操作):将信号量的值减1,表示请求分配一个资源。如果操作后信号量的值小于0,则表示没有可用资源,进程将被阻塞进入等待队列。
  • V操作(Signal操作):将信号量的值加1,表示释放一个资源。如果操作前信号量的值小于0,则表示有进程在等待该资源,系统将唤醒等待队列中的一个进程。

int semop(int semid ,struct sembuf *sops ,size_t nsops); 

//用户改变信号量的值。也就是使用资源还是释放资源使用权 

包含头文件:              

        include <sys/sem.h>

参数:        

         semid : 信号量的标识码。也就是semget()的返回值          

        sops是一个指向结构体数组的指针。                      

struct   sembuf

{                                        

        unsigned short  sem_num;//信号灯编号;                                        

        short  sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作           

        short sem_flg;0阻塞,1非阻塞                                     

};         

sem_op : 操作信号灯的个数         

//如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果sem_op的值为负 数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用 权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。

 这里我们进行PV操作实现父子进程之间的通信

对代码进行了修改,如下:

#include <stdio.h>  
#include <sys/types.h>  
#include <sys/sem.h>  
#include <sys/ipc.h>  
#include <sys/shm.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <string.h>  
  
// 定义信号量的宏,用于读写操作  
#define SEM_READ 0  
#define SEM_WRITE 1  
  
// semun 联合体用于 semctl 函数的 SETVAL 操作  
union semun {  
    int val;       // 用于设置信号量的值  
    struct semid_ds *buf;  
    unsigned short *array;  
};  
  
// P 操作(等待信号量)  
void Poperation(int index, int semid) {  
    struct sembuf sop;  
    sop.sem_num = index;  // 信号量的索引  
    sop.sem_op = -1;      // P 操作,信号量减 1  
    sop.sem_flg = 0;      // 操作标志,0 表示默认  
    if (semop(semid, &sop, 1) == -1) {  
        perror("semop");  // 如果 semop 失败,打印错误信息  
        exit(1);          // 退出程序  
    }  
}  
  
// V 操作(释放信号量)  
void Voperation(int index, int semid) {  
    struct sembuf sop;  
    sop.sem_num = index;  // 信号量的索引  
    sop.sem_op = 1;       // V 操作,信号量加 1  
    sop.sem_flg = 0;      // 操作标志,0 表示默认  
    if (semop(semid, &sop, 1) == -1) {  
        perror("semop");  // 如果 semop 失败,打印错误信息  
        exit(1);          // 退出程序  
    }  
}  
  
int main() {  
    key_t key;           // 用于生成唯一的标识符  
    int semid, shmid;    // 信号量和共享内存段的标识符  
    char *shmaddr;       // 指向共享内存段的指针  
    pid_t pid;           // 进程标识符  
  
    // 使用 ftok 生成唯一的 key  
    key = ftok("123", 65);  
    if (key == (key_t)-1) {  
        perror("ftok");  
        exit(1);  
    }  
  
    // 创建信号量集  
    semid = semget(key, 2, IPC_CREAT | 0755);  
    if (semid < 0) {  
        perror("semget");  
        return -2;  
    }  
  
    // 初始化信号量  
    union semun myun;  
    myun.val = 0;  
    semctl(semid, SEM_READ, SETVAL, myun);  // 初始化读信号量为 0  
    myun.val = 1;  
    semctl(semid, SEM_WRITE, SETVAL, myun); // 初始化写信号量为 1  
  
    // 创建共享内存段  
    shmid = shmget(key, 1024, IPC_CREAT | 0755);  
    if (shmid < 0) {  
        perror("shmget");  
        return -3;  
    }  
  
    // 创建子进程  
    pid = fork();  
    if (pid == 0) { // 子进程  
        while (1) {  
            // 附加共享内存段  
            shmaddr = (char *)shmat(shmid, NULL, 0);  
            if (shmaddr == (char *)-1) {  
                perror("shmat");  
                exit(1);  
            }  
  
            // 等待读信号量  
            Poperation(SEM_READ, semid);  
  
            // 读取并打印共享内存内容  
            printf("Child: get shared memory is %s\n", shmaddr);  
  
            // 释放写信号量  
            Voperation(SEM_WRITE, semid);  
  
            // 分离共享内存段  
            shmdt(shmaddr);  
  
            // 暂停一秒,避免过快循环  
            sleep(1);  
        }  
    } 
      else if (pid > 0) { // 父进程  
    char input[32];  
    while (1) {  
        // 获取用户输入  
        printf("Parent: please input to shared memory (q to quit): ");  
        if (fgets(input, sizeof(input), stdin) == NULL) {  
            perror("fgets");  
            break; // 如果读取输入失败,则退出循环  
        }  
  
        // 检查是否输入了退出命令  
        if (input[0] == 'q' && input[1] == '\n') {  
            break; // 退出循环  
        }  
  
        // 附加共享内存段  
        shmaddr = (char *)shmat(shmid, NULL, 0);  
        if (shmaddr == (char *)-1) {  
            perror("shmat");  
            exit(1);  
        }  
  
        // 等待写信号量  
        Poperation(SEM_WRITE, semid);  
  
        // 将输入的数据写入共享内存  
        // 注意:这里直接写入可能不安全,因为fgets包含换行符,并且没有检查缓冲区溢出  
        // 这里为了简化,我们直接写入,但在实际应用中应该更谨慎  
        strncpy(shmaddr, input, sizeof(input) - 1); // 减去1以排除可能的换行符  
  
        // 释放读信号量,允许子进程读取  
        Voperation(SEM_READ, semid);  
  
        // 分离共享内存段  
        shmdt(shmaddr);  
  
        // 可以在这里添加其他逻辑,如处理错误或进行其他任务  
    }  
  
    // 父进程退出前,可以清理资源(可选)  
    // 但在这个例子中,由于子进程将无限循环,除非父进程被外部方式终止,  
    // 否则通常不会执行到这里的清理代码  
  
    // 注意:在实际应用中,你可能需要等待子进程结束,或者发送信号来优雅地终止它  
    // 例如,使用 waitpid 或 kill 函数  
} else {  
    // 如果 fork 失败  
    perror("fork");  
    exit(1);  
}  
  
// 清理信号量和共享内存(这部分代码在实际应用中可能需要放在更合适的位置)  
// 例如,在父进程确定子进程已经终止之后  
// 但在这个例子中,由于子进程是无限循环的,所以这里不执行清理  
// ...  
  
return 0;  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1961010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

对于AI大模型发展态势的几点认识

本期内容从AI大模型产业的视角出发&#xff0c;全面审视该产业的发展现状&#xff0c;深入剖析其成长轨迹和未来趋势&#xff0c;旨在为人工智能产业的参与者提供一个全面的视角&#xff0c;更好地理解AI大模型产业的复杂性、动态性和潜力&#xff0c;以及如何在这个快速变化的…

Ruoyi 快速开发平台

Ruoyi 快速开发平台 一、官网二、准备工作2.1 环境要求2.2 必要配置 三、运行系统3.1 后端运行3.2 前端安装及运行 四、自定义开发4.1 新增业务模块4.2 代码生成4.2.1 创建菜单4.2.2 后端代码4.2.3 前端代码 一、官网 链接: 前后端分离版本 回到目录 二、准备工作 2.1 环境要…

UDP服务器端bind失败问题

本人使用microchip芯片开发&#xff0c;使用UDP虚拟机通讯&#xff0c;经常提示bind失败&#xff0c;返回-1&#xff0c;尝试了以前UDP作为客户端使用时正常&#xff0c;故硬件链路没问题。 一、可能有几个原因&#xff1a; 端口实际上被占用&#xff1a;最明显的原因是端口真…

基于入侵野草算法的KNN分类优化matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 入侵野草算法 4.2 K近邻分类器&#xff08;KNN&#xff09; 4.3 基于IWO的KNN分类优化 5.完整程序 1.程序功能描述 基于入侵野草算法的KNN分类优化。其中&#xff0c;入侵野草算法是一…

GEE APP:利用谷歌地球引擎实现更有效的草原管理:决策支持应用视角

简介 草原占地球表面和农田的很大一部分,对人类福祉和畜牧业至关重要。由于牧区基础设施不发达、通信不畅,牧民和草原管理部门在有效控制牧民放牧行为和草原利用方面面临挑战。要解决这一问题,促进草原的可持续利用并保护其生态系统服务,就需要基于云的放牧管理和决策支持…

C++初阶大总结

目录 一.命名空间 1.命名空间定义 2.命名空间使用 二.C++输入&输出 三.缺省参数 四. 函数重载 五.引用 1.常引用 2.传值、传引用效率比较 3.引用和指针的区别 4.引用和指针的不同点: 小知识点: 六.内联函数 七.auto关键字(C++11) 1.auto的使用细则 八.基于…

24暑假算法刷题 | Day23 | LeetCode 39. 组合总和,40. 组合总和 II,131. 分割回文串

目录 39. 组合总和题目描述题解 40. 组合总和 II题目描述题解 131. 分割回文串题目描述题解 39. 组合总和 点此跳转题目链接 题目描述 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有…

Dolphinscheduler 3.2.1bug记录

问题1&#xff1a;分页只展示首页 解决方案&#xff1a; [Bug][API] list paging missing totalpage by Gallardot Pull Request #15619 apache/dolphinscheduler GitHub 问题2:Hive 数据源连接失败 解决方案&#xff1a;修改源码&#xff1a;HiveDataSourceProcessor.cla…

A Survey on Multimodal Large Language Models(from gpt-4o)

目录 A Survey on Multimodal Large Language Models1. INTRODUCTION2. ARCHITECTURE2.1 Modality encoder2.2 Pre-trained LLM2.3 Modality interface 3. TRAINING STRATEGY AND DATA3.1 Pre-training3.1.1 Training Detail3.1.2 Data 3.2 Instruction-tuning3.2.1 Introducti…

Linux下文件编译器-GCC/G++

前言 本文介绍了c/c的编译过程以及gcc/g的时使用 一.c/c翻译的本质&#xff1a;将高级语言翻译成二进制 1&#xff09;程序翻译过程&#xff1a; &#xff08;1&#xff09;预处理&#xff08;头文件展开、宏替换、去注释、条件编译&#xff09;还是C语言代码 ​ …

hash表如何形成,hash函数如何计算,什么是hash冲突 如何解决 ,Golang map的底层原理及扩容机制

散列表 散列表&#xff08;hash表&#xff09;:根据给定的关键字来计算出关键字在表中的地址的数据结构。也就是说&#xff0c;散列表建立了关键字和 存储地址之间的一种直接映射关系。 问题&#xff1a;如何建立映射管血 散列函数:一个把查找表中的关键字映射成该关键字对应…

平移、旋转、缩放和媒体

一、平移 1.1translate&#xff08;&#xff09;函数 做转换工作可以用translate()函数&#xff0c;这个函数可以改变坐标系。通过改变默认的坐标系&#xff0c;我们可以创建不同的转换方式&#xff0c;包括平移、旋转和缩放。 1.2平移位置案例 案例代码如图1 图1 保存运行如…

【大一公共课】C语言+python语言入门对比+思维导图

C 和 Python 入门教程对比 一、引言 C 语言和 Python 语言都是在编程领域中广泛使用的语言&#xff0c;但它们在语法、应用场景和学习难度上有很大的不同。本教程将对 C 和 Python 的入门知识进行对比&#xff0c;帮助您更好地理解和选择适合自己的编程语言。 二、C 语言入门 …

python爬取某财富网

过程&#xff1a; 点击底部的第3页&#xff0c;第5页&#xff0c;网页刷新了&#xff0c;但是顶部的url地址没有变。那么就是 动态加载&#xff0c; 就是 XHR. 直接请求api. 实验代码如下: import requestsheaders {"User-Agent": "Mozilla/5.0 (Windows NT…

LLM大模型:2024工业AI大模型发展分析

一、大模型为工业智能化发展带来新机遇 1.1. 大模型开启人工智能应用新时代 大模型引领人工智能技术创新和应用。 自 1956 年达特茅斯会议&#xff08;Dartmouth Conference&#xff09;提出人工智能的概念以来&#xff0c;人工智能技术经历了多个发展高峰和低谷。在这一长期的…

《深入浅出WPF》学习笔记一.解析WPF程序

《深入浅出WPF》学习笔记一.解析WPF程序 visual studio帮助我们做了那些事情 引用文件 输出文件类型 按照最原始的方式&#xff0c;我们需要手动打开编译器命令行&#xff0c;使用命令引用类库将代码编译成目标文件。 visual studio会根据我们选择的项目模板&#xff0c;自动…

Java学习Day19:基础篇9

包 final 权限修饰符 代码块 静态代码块在Java中是一个重要的特性&#xff0c;它主要用于类的初始化操作&#xff0c;并且随着类的加载而执行&#xff0c;且只执行一次。静态代码块的实践应用广泛&#xff0c;以下是几个主要的应用场景&#xff1a; 1. 初始化静态变量 静态代…

芋道源码yudao-cloud 二开日记(Editor富文本本地图片上传报错问题)

&#xff1a; 于是找到富文本的组件代码Editor.vue&#xff0c;检查一下上传的接口地址和token有没有传&#xff0c;如下图&#xff1a; 都没有问题&#xff0c;但还是报错&#xff0c;所以试试自定义上传的方法&#xff1a; // 导入上传文件的接口 import * as FileApi from …

数字图像处理 --- 二维离散余弦变换(python实战)

二维离散余弦变换(python实战) &#xff08;注&#xff1a;文中所讨论的离散余弦变换都是DCT-II&#xff09; 在上一篇文章中&#xff0c;我详细介绍了一维离散余弦变换的基本原理&#xff0c;并且以可视化的方式展示了一维DCT中用于分析输入信号的一系列基础余弦波。 在这篇文…

【简单图解 最强计网八股】HTTP 和 HTTPS 的区别

HTTP&#xff08;HyperText Transfer Protocol 超文本传输协议&#xff09; HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff0c;超文本传输安全协议&#xff09; 通过 传输内容加密 和 身份认证 保证了传输过程的安全性 协议传输内容加密身份认证响应效率端口号…