LinuxC编程——进程间通信(二)(信号、共享内存)

news2024/11/27 0:40:52

目录

  • 一、信号
    • 1.1 概念
    • 1.2 信号的响应方式⭐⭐⭐
    • 1.3 几种常见的信号
    • 1.4 函数
    • 练习
  • 二、共享内存
    • 2.1 共享内存的特点
    • 2.2 共享内存创建步骤⭐⭐
    • 2.3 共享内存创建所需函数

  • 信号主要用来通知进程异步事件的发生。最初信号设计的目的是为了处理错误,它们也用来作为最基本的IPC机制。在Linux中可以识别64种不同的信号。这些信号中的大部分都有了预先定义好的意义,但是至少有两个,SIGUSR1和SIGUSR2可以由应用程序来定义。进程可以显式地用kill 或是killg系统函数来向另一个进程或进程组发信号。此外,内核可以内应不同的事件而产生内部信号。例如,当按下终端键“Ctrl+C" 时,内核便发送一个SIGINT信号到前台的进程。
  • 作为一种IPC机制,信号有一些局限性信号的系统开销太大。发送信号的进程要进行系统调用;内核要中断接收信号的进程,而且要管理它的堆栈,同时还要调用处理程序,之后还要恢复执行被中断的进程。更重要的是,信号的数量非常有限,因为只存在有限的不同的信号,而且信号能传送的信息量十分有限,用户产生的信号不可能发送附加信息及各种
  • Linux是一个多用户、多任务的操作系统,无论是操作系统与一般进程间的通信,还是用户进程间的通信都是必要的。信号是进程间互相通信的方法之一,它用来指出某种事件的发生。
  • 在Linux系统中,针对不同的软/硬件状况,内核程序会发送出不同的信号来通知进程某个事件的发生。但是如何处理这个信号,就要由进程本身来处理。
  • 信号可以由系统内核程序发出,也能由某些进程发送,但是大部分的时候都是由内核程
    序发出的。
  • 当一个信号正在被处理时,所有同样的信号都将暂时搁置,直到这个信号处理完成。

一、信号

1.1 概念

  1. 信号是在软件层次上对中断的一种模拟,是一种异步通信方式
  2. 信号可以直接进行用户空间进程与内核进程之间的交互,内核进程也可以利用信号通知用户进程发生了哪些系统事件。
  3. 如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

1.2 信号的响应方式⭐⭐⭐

  1. 忽略信号:对信号不做任何处理。但是有两个信号不能被忽略STGKILL和SIGSTOP
  2. 捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
  3. 执行缺省操作:linux对每种信号都规定了默认操作。

1.3 几种常见的信号

Linux中可以识别的信号种类有64种,在终端可以用 kill -l 列出:
在这里插入图片描述
需要了解的信号如下:
2)SIGINT:结束进程,对应快捷方式ctrl+c
3)SIGQUIT:退出信号,对应快捷方式ctrl+
9)SIGKILL:结束进程,不能被忽略不能被捕捉
15)SIGTERM:结束终端进程,kill 使用时不加数字默认是此信号
17)SIGCHLD:子进程状态改变时给父进程发的信号
19)SIGSTOP:结束进程,不能被忽略不能被捕捉
20)SIGTSTP:暂停信号,对应快捷方式ctrl+z
26)SIGALRM:闹钟信号,alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程。

1.4 函数

  1. kill
    int kill(pid_t pid, int sig);

    • 功能:信号发送
    • 参数:
      • pid:指定进程
      • sig:要发送的信号
    • 返回值:成功 0;失败 -1
  2. raise
    int raise(int sig);

    • 功能:进程向自己发送信号
    • 参数:sig:信号
    • 返回值:成功 0;失败 -1
  3. pause
    int pause(void);

    • 功能:用于将调用进程挂起(不占用CPU资源),直到收到信号为止。

函数测试:

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    printf("start...\n");
    /*1.kill给指定的进程发送指定的信号*/
    // kill(getpid(),SIGINT);
    /*2.raise给自己发送信号*/
    // raise(2);//给自己发送信号
    //printf("end...\n");
    /*3.pause挂起进程,直到捕捉到信号才会返回*/
    pause(); //不占用cpu
    // while(1); //一直轮寻,占用CPU
    return 0;
}

在还没有信号触发时,为了保证进程不立即结束,可以用while或者pause,但是while会一直轮询,占用cpu资源,而pause会将进程挂起,不占用CPU资源。

  1. signal
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    • 功能:信号处理函数
    • 参数:
      • signum:要处理的信号
      • handler:信号处理方式
        • SIG_IGN:忽略信号
        • SIG_DFL:执行默认操作
        • handler:捕捉信号(捕捉到信号要处理的函数)
          • void handler(int sig){} //函数名可以自定义
    • 返回值:成功:设置之前的信号处理方式;失败:-1

函数测试:

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

void handler(int sig)
{
    if (sig == SIGINT)
        printf("You pressed ctrl + c!\n");
    else if (sig == SIGTSTP)
        printf("You pressed ctrl + z!\n");
    else if (sig == SIGQUIT)
        printf("You pressed ctrl + \\!\n");
    else
        printf("end...\n");
}

int main(int argc, char const *argv[])
{
    /*1.忽略信号*/
    // signal(2,SIG_IGN); //ctrl+c被忽略,终端用ctrl+\退出

    /*2.缺省(默认)操作*/
    // signal(SIGINT,SIG_DFL);

    /*3.捕捉信号*/
    signal(2, handler);       //捕捉结束进程信号(ctrl+C)
    signal(SIGTSTP, handler); //捕捉暂停信号(ctrl+Z)
    signal(SIGQUIT,handler);  //捕捉推出信号(ctrl+Q)

    //挂起进程,直到捕捉到信号才会返回
    pause();
    return 0;
}
  1. alarm
    unsigned int alarm(unsigned int seconds)

    • 功能:在进程中设置一个定时器
    • 参数:seconds:定时时间,单位为秒
    • 返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。

    注意📢:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。

函数测试:

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

int main(int argc, char const *argv[])
{
    printf("start...\n");
    // alarm(5); //alarm不阻塞,当时间到达后结束进程
    
    printf("%d\n",alarm(5));//第一次调用alarm返回值为0
                            //alarm不阻塞,当时间到达后结束进程
    printf("end...\n");
    
    sleep(3);
    printf("%d\n",alarm(8)); // 前面定时5秒。用sleep函数消耗1秒,alarm返回5-3=2
    printf("second end...\n");
    alarm(0); //当秒数为0时,定时被取消
    
    pause(); //pause挂起进程(用while也可以),直到捕捉到信号才会返回

    return 0;
}

练习

用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let’s gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。—>父子进程,父:司机 子:售票员

/*
练习一:
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。
*/
/*
分析:各个进程要捕捉和忽略的信号
售票员(子进程):
    捕捉:SIGINT,SIGQUIT,SIGUSR1
    忽略:SIGTSTP
司机(父进程):
    捕捉:SIGTSTP,SIGUSR1,SIGUSR2
    忽略:SIGINT,SIGQUIT
*/
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>

pid_t pid; //pid设置为为全局变量

void saller(int arg)
{
    if(arg == SIGINT)
        kill(getppid(),SIGUSR1);
    if(arg == SIGQUIT)
        kill(getppid(),SIGUSR2);
    if(arg == SIGUSR1)
    {
        printf("售票员:please get off the bus\n");
        exit(0);
    }
}
void driver(int arg)
{
    if(arg == SIGUSR1)
        printf("司机:let's gogogo\n");
    if(arg == SIGUSR2)
        printf("司机:stop the bus\n");
    if(arg == SIGTSTP)
    {
        kill(pid,SIGUSR1);//要注意pid要为全局变量
        wait(NULL);//阻塞回收子进程资源
        exit(0);
    }
}

int main(int argc, char const *argv[])
{
    pid = fork();//创建父子进程
    if(pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if(pid == 0) //售票员(子进程)
    {
        //子进程要捕捉的信号
        signal(SIGINT,saller);
        signal(SIGQUIT,saller);
        signal(SIGUSR1,saller);

        //子进程要忽略的信号
        signal(SIGTSTP,SIG_IGN);
    }
    else //司机(父进程)
    {
        //父进程要捕捉的信号
        signal(SIGTSTP,driver);
        signal(SIGUSR1,driver);
        signal(SIGUSR2,driver);

        //父进程要忽略的信号
        signal(SIGINT,SIG_IGN);
        signal(SIGQUIT,SIG_IGN);
    }
    while(1)
        pause();//这样比只有一个while占用CPU更少
    return 0;
}

📢要明白:最后的while(1) pause(); 的用意→如果只用一个pause(),验证一个信号后程序立即结束;如果只用while(1)进行阻塞,虽然可以起到从终端循环输入信号并打印出结果,但是cpu占用资源较高;而使用while(1)和pause()则解决以上弊端,因为这样程序运行起来后,进while,在pause处程序挂起,不再占用CPU,直到产生信号解除pause阻塞进入下一次while。
pause()作用:用于将调用进程挂起,直到收到信号为止

二、共享内存

2.1 共享内存的特点

  1. 共享内存是一种最为 高效 的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
  2. 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间
  3. 进程可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
  4. 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

2.2 共享内存创建步骤⭐⭐

  1. 创建key值(ftok)
  2. 创建或打开共享内存(shmget)
  3. 映射共享内存到用户空间(shmat)
  4. 取消映射(shmdt)
  5. 删除共享内存(shmctl)

2.3 共享内存创建所需函数

  1. ftok
    key_t ftok(const char *pathname, int proj_id);

    • 功能:产生一个独一无二的key值
    • 参数:
      • pathname:已经存在的可访问文件的名字
      • proj_id:一个字符(因为只用低8位)
    • 返回值:成功:key值;失败:-1
      在这里插入图片描述
      运行结果:
      在这里插入图片描述
      前两位是ftok第二个字符参数的,ASCII码值的十六进制表示形式
      在这里插入图片描述
      中间这两位大多情况是01
      在这里插入图片描述
      查看文件inode👇
      在这里插入图片描述
      将文件的inode转换为十六进制👇,可以看到其后四位确实是key值十六进制的后四位
      在这里插入图片描述
  2. shmget
    int shmget(key_t key, size_t size, int shmflg);

    • 功能:创建或打开共享内存
    • 参数:
      • key 键值
      • size 共享内存的大小
      • shmflg IPC_CREAT|IPC_EXCL(判错)|0666
    • 返回值:成功 shmid;出错 -1

    例:
    在这里插入图片描述
    在这里插入图片描述
    查看系统共享内存:ipcs -m
    在这里插入图片描述
    删除共享内存:ipcrm -m 对应的shmid
    在这里插入图片描述

  3. shmat
    void *shmat(int shmid,const void *shmaddr,int shmflg);

    • 功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
    • 参数:
      • shmid 共享内存的id号
      • shmaddr 一般为NULL,表示由系统自动完成映射;如果不为NULL,那么由用户指定
      • shmflg:
        • SHM_RDONLY就是对该共享内存只进行读操作
        • 0 可读可写
    • 返回值:
      • 成功:完成映射后的地址,
      • 失败:-1的地址

    用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)
    常用下面:

char *p = shmat(shmid,NULL,0);
if(p==(void *)-1)
{
     perror("shmat err");
     return -1;
}
  1. shmdt
    int shmdt(const void *shmaddr);

    • 功能:取消映射
    • 参数:要取消的地址
    • 返回值:成功:0 ;失败:-1
  2. shmctl
    int shmctl(int shmid,int cmd,struct shmid_ds *buf);

    • 功能:(删除共享内存),对共享内存进行各种操作
    • 参数:
      • shmid :共享内存的id号
      • cmd
        • IPC_STAT 获得shmid属性信息,存放在第三参数
        • IPC_SET 设置shmid属性信息,要设置的属性放在第三参数
        • IPC_RMID:删除共享内存,此时第三个参数为NULL即可
    • 返回:成功0 ;失败-1

    用法:shmctl(shmid,IPC_RMID,NULL);

例:创建共享内存,通过共享内存完成读写

/*
    共享内存创建及读写
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    /*1.创建key值*/
    key_t key;
    key = ftok("./6kill.c", 'a'); //第二个参数是任意字符
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("%#x\n", key); //'#'会在打印结果中添加前缀0x

    /*2.创建或打开共享内存*/
    int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid <= 0) //=0不使用
    {
        //若已创建,则用shmget打开,重新给shmid赋值
        if (errno == 17)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid:%d\n", shmid);
    /*3.映射共享内存到用户空间 */
    char *p = shmat(shmid, NULL, 0);
    if (p == (void *)-1)
    {
        perror("shmat err");
        return -1;
    }
    //读写操作
    read(0, p, 32);
    write(1, p, 32);
    //printf("buf:%s\n",p);

    /*4.撤销映射*/
    shmdt(p);
    /*5.删除共享内存*/
    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}

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

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

相关文章

【EI/SCOPUS检索】第二届能源与动力工程国际学术会议(EPE 2023)

第二届能源与动力工程国际学术会议&#xff08;EPE 2023&#xff09; 2023 2nd International Conference on Energy and Power Engineering 能源是人类社会发展的重要推动力量。如何安全、清洁、高效地存储、转化和利用能源&#xff0c;实现人类可持续发展&#xff0c;一直…

比例方向阀控制多功能放大器

适用于控制无电位置反馈的三位四通比例方向阀&#xff0c;两路独立工作的比例放大器&#xff0c;可组合成并联工作方式&#xff0c;0到10V输入接口&#xff0c;可切换为0(4)到20mA输入&#xff0c;工作电压24VDC&#xff0c;允许工作温度范围0~45℃&#xff0c;放大器只有在断电…

spring security实践-全套代码

贴一套完整代码 电脑文件都被加密了&#xff0c;无法上传git&#xff0c;留一套在此&#xff0c;日后方便。 整个学习过程参考的spring security 1. 项目目录结构 2.初始化数据库 CREATE TABLE sys_user (id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 主键,user_name VAR…

你确定你的 REST API 真的符合 REST 规范?

RESTful API 的存在是 web 开发历史上的一个里程碑。在本文中&#xff0c;我将和你探讨几种节省 REST API 开发时间的方法&#xff0c;并给出相关的 Node.js 示例。 什么是 RESTful API 首先&#xff0c;想问一个问题&#xff0c;你的项目里真的有真正的 RESTful API 吗&…

【构造】CF1734 E

Problem - 1734E - Codeforces 题意&#xff1a; 思路&#xff1a; Code&#xff1a; #include <bits/stdc.h>#define int long longusing i64 long long;constexpr int N 1e3 10; constexpr int mod 1e9 7;int n; int a[N], ans[N][N];void solve() {std::cin &g…

动力节点零基础Java学习教程,从入门到进阶,轻松掌握Java开发技能

Java是一门应用非常广泛的编程语言&#xff0c;对于零基础自学Java来说&#xff0c;Java的学习过程可能会有一些困难&#xff0c;但只要掌握了相关的基础知识和技能&#xff0c;不断地实践和总结&#xff0c;就能真正掌握Java编程。 动力节点老杜的Java经典之作&#xff0c;哔站…

Jenkins构建自由风格项目发布jar到服务器

前面的文章有介绍 docker安装jenkins 和 dockerjenkins发布spring项目&#xff1b;这里就不做过多的介绍&#xff0c;直接说明构建步骤。 1、选择构建一个自由风格的项目 2、 选择丢弃旧的构建 3、配置Git信息 4、构建触发器 和 构建环境可以直接跳过 5、直接来到Build Step…

技术广度必备——高并发设计之分布式锁的实现方式

文章目录 问题背景前言实现基于MySQL实现唯一索引乐观锁悲观锁 基于Redis基于Zookeeper原理使用Curator框架实现ZK分布式锁缺点 问题背景 研究有哪几种方案可以实现分布式锁&#xff0c;技术选型的场景下能用到。 前言 本文参考过的文章有分布式锁的几种实现方式方式大致分为3种…

蓝帽杯 取证2022

网站取证 网站取证_1 下载附件 并解压 得到了一个文件以及一个压缩包 解压压缩包 用火绒查病毒 发现后门 打开文件路径之后 发现了一句话木马 解出flag 网站取证_2 让找数据库链接的明文密码 打开www文件找找 查看数据库配置文件/application/database.php&#xff08;CodeI…

Cuda和cuDNN安装

Cuda和cuDNN安装 1Cuda下载与安装1.1查看适合cuda的版本1.2下载cuda toolkit1.3cuda安装步骤1.4配置环境变量1.5验证 2cuDNN下载与安装2.1cuDNN下载2.2cuDNN配置2.3配置环境变量2.4验证 3安装PyTorch-GPU3.1打开Anaconda Prompt![在这里插入图片描述](https://img-blog.csdnimg…

ARM--day2(cpsr、spsr、数据搬移指令、移位操作指令、位运算操作指令、算数运算指令、比较指令、跳转指令)

.text .global _gcd _gcd:mov r0,#9mov r1,#15b loop loop:cmp r0,r1beq stopsubhi r0,r1bhi loopsubcc r1,r0bcc loopstop:b stop.end用for循环实现1~100之间和5050 .text .global _gcd _gcd:mov r0,#0x0mov r1,#0x1mov r2,#0x64b loop loop:cmp r1,r2bhi stopadd r0,r0,r1ad…

卷王特斯拉又全网降价了,卷死车企们

哈喽,大家好,今天媒介盒子小编又来跟大家分享软文推广的干货知识了,本篇分享的主要内容是&#xff1a;特斯无孔不入的营销手段。 1、特斯拉Model Y降价 车企要打架 自2023 年 8 月 14 日起&#xff0c;Model Y 长续航版起售价从 31.39 万元调整为 29.99 万元&#xff0c;Mode…

React+Typescript清理项目环境

上文 创建一个 ReactTypescript 项目 我们创建出了一个 React配合Ts开发的项目环境 那么 本文 我们先将环境清理感觉 方便后续开发 我们先来聊一下React的一个目录结构 跟我们之前开发的React项目还是有一些区别 public 主要是存放一些静态资源文件 例如 html 图片 icon之类的 …

走进 Linux

一、开关机 开机&#xff1a; 开机会启动许多程序。他们在windows叫做“服务”(service),在Linux就叫做“守护进程”&#xff08;daemon&#xff09;开机成功后&#xff0c;它会显示一个文本登录界面&#xff0c; 这个界面就是我们经常看到的登录界面&#xff0c;在这个登录界…

【有奖体验】COS插件体验,四重好礼等你拿!

对象存储 COS 为降低用户接入门槛&#xff0c;集成了多款 COS 插件&#xff0c;开放供用户使用&#xff0c;包含搭建网站、图床、论坛等多个热门业务场景的插件&#xff0c;让使用更便捷&#xff01;对象存储 COS 准备了多重好礼&#xff0c;欢迎广大同学们踊跃体验 COS 插件&a…

U盘安装CentOS7系统出现dracut timeout的解决办法

文章目录 业务场景操作步骤U盘装CentOS7系统确定U盘盘符修改启动命令系统配置 总结 业务场景 我们在某市实施交通信控平台项目&#xff0c;我们申请了一台服务器&#xff0c;用于平台安装由于机房机器只有内网&#xff0c;不连互联网&#xff0c;我们无法安装所需要的软件&…

FreeRTOS(任务通知)

资料来源于硬件家园&#xff1a;资料汇总 - FreeRTOS实时操作系统课程(多任务管理) 目录 一、任务通知的概念 1、概念 2、发送通知给任务的方式 3、任务通知使用限制 二、任务通知的运行机制 三、任务通知的API函数 1、任务通知的数据结构 2、常用的API函数 3、函数x…

Java多线程编程中的线程死锁

Java多线程编程中的线程死锁 ​ 在多线程编程中&#xff0c;线程死锁是一种常见的问题&#xff0c;它发生在两个或多个线程互相等待对方释放资源的情况下&#xff0c;导致程序无法继续执行。本文将介绍线程死锁的概念、产生原因、示例以及如何预防和解决线程死锁问题。 线程死…

LeNet中文翻译

Gradient-Based Learning Applied to Document Recognition 基于梯度的学习应用于文档识别 摘要 使用反向传播算法训练的多层神经网络构成了成功的基于梯度的学习技术的最佳示例。给定适当的网络架构&#xff0c;基于梯度的学习算法可用于合成复杂的决策表面&#xff0c;该决策…

【C语言实战项目】通讯录

一.了解项目功能 在本次实战项目中我们的目标是实现一个通讯录: 该通讯录可以用来存储1000个人的信息 每个人的信息包括&#xff1a;姓名、年龄、性别、住址、电话 通讯录提供功能有&#xff1a; 添加联系人信息删除指定联系人信息查找指定联系人信息修改指定联系人信息显示所有…