Linux进程间通信学习2

news2024/11/15 17:22:59

文章目录

  • 共享内存
  • 信号
    • 信号概述以及种类
    • 信号的处理
    • 信号相关函数(简单)
    • 运用小demo
      • 实现ctrl+c无法终止进程
      • 使用kill函数在程序内部实现一个进程杀死另外一个进程
    • 信号相关函数高级版
    • 运用函数小demo
  • 信号量
    • 信号量相关函数
    • 运用小demo:

共享内存

相比于前三个IPC方式,共享内存有什么不同?
我们可以假设两个人要进行交流,管道和FIFO就好像两人中间有一个水管,一方往里面放,另一方就只能拿;消息队列就好像一个人往箱子里面放纸条,另一个人从箱子里拿出纸条,读取完后再把纸条放回去(消息读完后不会消失,不同于管道);而共享内存就像两人中间有一张桌子,一个人往桌子上写东西,另一个人可以直接看到它写的(桌子对于两个人来说是共用的)。

由名字可知,两个进程可以挂载同一个内存空间,这个内存空间是共享的。
相关函数:

#include <sys/shm.h>
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
 
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
 
//断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
 
//控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

这个参数Key之间我是直接初始化为一个32位的整数,现在我们用一下其他方法,顺便学习一下一个函数——ftok函数。

#include <sys/types.h>
#include <sys/ipc.h>
 
key_t ftok(const char *pathname, int proj_id);

参数:
1.函数的第一个参数是一个路径名,通常是一个存在的文件路径,待会的代码示例中会传入".",这意味着 ftok 函数会使用当前进程的工作目录(即程序运行时所在的目录)作为路径来生成键值,我们只需要知道怎么使用就行了。
2.第二个参数proj_id,它用于进一步区分同一路径下不同对象(如消息队列、信号量、共享内存)的键值。在使用ftok函数时,传入的proj_id值应当是一个非零的整数。简单理解就是这个数字可以被视为一个简单的标识符,用于区分同一路径下不同的对象。
运用函数小demo:
write:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main()
{
	int shmid;
	char *shmaddr;
 
	key_t key;
	key=ftok(".",1);//获取键值
 
	shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建一个共享内存,权限为可读可写,大小为4兆
	if(shmid==-1)//创建/获取共享内存失败
	{
		printf("shmget failed\n");
		exit(-1);
	}	
 
	shmaddr=shmat(shmid,0,0);//挂载共享内存,获取地址
 
	strcpy(shmaddr,"hello!\n");//将字符串复制到共享内存里
 
	sleep(5);/眠五秒
 
	shmdt(shmaddr);//取消挂载/卸载共享内存
	shmctl(shmid,IPC_RMID,0);//删除共享内存
 
	return 0;
}

注意:
1.创建共享内存时,空间大小必须以兆为单位,即1024字节,shmget函数的第二个参数一般传入IPC_CREAT(创建),还需要|上创建的权限(0666表示可读可写,0777表示可读可写可执行)。
2.挂载共享内存shmat的第二和第三个参数通常写0即可,第二个参数写0表示让Linux内核为我们自动安排共享内存,第三个表示挂载/映射的共享内存为可读可写。
3.删除共享内存的第三个参数通常写0,表示不接收删除共享内存的信息等。

read:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main()
{
	int shmid;
	char *shmaddr;
 
	key_t key;
	key=ftok(".",1);//获取键值
 
	shmid=shmget(key,1024*4,IPC_CREAT|0666);//创建一个共享内存,权限为可读可写,大小为4兆
	if(shmid==-1)//创建/获取共享内存失败
	{
		printf("shmget failed\n");
		exit(-1);
	}	
 
	shmaddr=shmat(shmid,0,0);//挂载共享内存,获取地址
 
	strcpy(shmaddr,"hello!\n");//将字符串复制到共享内存里
 
	sleep(5);/眠五秒
 
	shmdt(shmaddr);//取消挂载/卸载共享内存
	shmctl(shmid,IPC_RMID,0);//删除共享内存
 
	return 0;
}

运行结果:
在这里插入图片描述
容易误解事项:
将write函数改写为这样后,就是将sleep函数改迟一点点:
在这里插入图片描述
运行结果还是一样的,只要不删除;另外一个进程就还可以读出共享内容的信息,而不是一定要两个进程一起挂起才可以!


共享内存补充
在终端输入指令 ipcs -m 来查看系统中有哪些共享内存

输入 ipcrm -m shmid shmid为共享内存的ID,用于删除共享内存

共享内存有个小缺陷,就是两个人不能同时写,不然数据会混在一起,所以共享内存一般都结合信号量来使用,一个人写的时候另一个人只能看。
————————————————————————————————————

信号

信号概述以及种类

1.信号的名字和编号
每个信号都有一个名字和编号,这些名字都以“SIG"开头,例如"SIGIO","SIGCHLD"等等。

信号定义在 signal.h 头文件中,信号名都定义为正整数。

具体的信号名称可以使用 kill -l 来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kil对于信号0有特殊的应用。
在这里插入图片描述
(使用 kill -l 指令查看信号的名字以及序号)

假设我们写了一个无限循环的程序,想要让它停止运行,我们会按 Ctrl+C 键来终止进程,其实就是向进程发送了第2个信号SIGINT。

还有一种杀死进程的方式,另外打开一个终端,输入 ps -aux |grep 可执行文件名 来查找正在运行的进程ID,再输入 kill -9 进程ID 即可杀死进程,并且在终端打印Killed,kill是发送信号的指令,-9表示发送第9个信号,也就是发送SIGKILL信号给对应ID的进程。以上说的这些指令都需要我们掌握,因为后面的代码示例都是根据这些指令来写的。
运用一下:
在这里插入图片描述

信号的处理

信号的处理有三种方法,分别是:忽略捕捉默认动作

·忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是== SIGKILL ==和 ==SIGSTOP ==)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。

·捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。

·系统默认动作,对于每个信号来说,系统都对应有默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

信号相关函数(简单)

信息处理函数的注册:

#include <signal.h>
 
//它定义了一个名为sighandler_t的新类型(typedef),该类型是一个指向函数的指针,这个函数接受一个整型参数 int,并且没有返回值(void)
typedef void (*sighandler_t)(int)
 
sighandler_t signal(int signum, sighandler_t handler);
//handler就是这个新定义的类型

参数:
signum:就是上面说的用kill -l查看的信号的名字
handler:我们要传入的函数
**
————————————————————————————————————**
信号处理发送函数:

#include <signal.h>
#include <sys/types.h>
 
int kill(pid_t pid, int siq);//第一个参数传入进程ID,第二个参数传入信号的编号

运用小demo

实现ctrl+c无法终止进程

平时我们想要认为的去终止进程,我们都是直接CTRL+C就可以了,假设我们写了一个无限循环的程序,想要让它停止运行,我们会按 Ctrl+C 键来终止进程,其实就是向进程发送了第2个信号SIGINT。
代码:

#include <stdio.h>
#include <signal.h>
 
void handler(int signum)//自定义函数,参数为整形,无返回值
{
	printf("get signum=%d\n",signum);//打印信号编码
	printf("never quit\n");
}
 
int main()
{
	signal(SIGINT,handler);//检测SIGINT信号(Ctrl+C),检测到了进入handler函数
	while(1);
	return 0;
}

本来信号的处理就是系统默认动作,现在相当于我将它改写了,就是捕获这个信号SIGINT,一旦捕获到就执行我们的函数handler,这里就是简单打印了,就没有让进程退出来。

使用kill函数在程序内部实现一个进程杀死另外一个进程

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
 
int main(int argc, char **argv)
{
	int signum;
	int pid;
 
	signum = atoi(argv[1]);//将字符串转换为整数类型
	pid = atoi(argv[2]);
 
	kill(pid,signum);
	printf("send signal ok\n");
	return 0;
}

运行结果:
在这里插入图片描述
就相当于前面用的kill -9 +pid

信号相关函数高级版

信息处理函数的注册:

#include <signal.h>
 
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数:
1.信号编号,要接收哪个信号?
2.是一个指向 struct sigaction 类型的结构体指针,结构体原型为:

struct sigaction {
    void (*sa_handler)(int);           // 指定信号处理函数
    void (*sa_sigaction)(int, siginfo_t *, void *); // 详细信号处理函数
    sigset_t sa_mask;                  // 额外的要阻塞的信号集合
    int sa_flags;                      // 特殊标志
    void (*sa_restorer)(void);         // 未使用,以备将来扩展
};

!!!通常配置结构体的第二个和第四个参数即可,第四个参数设置成 SA_SIGINFO ,这表示在使用sigaction函数设置信号处理时,希望使用详细的信号处理函数 sa_sigaction 而不是简单的处理函数 sa_handler ,因为我们只设置结构体的第二个和第四个参数,所以自然选择这个配套在一起,第二个参数传入自定义的函数名即可。
3.传入NULL即可

第二个参数我们慢慢刨开来看,第二个参数是一个结构体指针,里面我们一般配置第2,4个即可,第4个已经说了,那么我们可以看出来第2个这个函数原型为:

void handler(int signum, siginfo_t *info, void *context);

参数:
1.信号的编号,由操作系统传入。
2.siginfo_t是一个结构体,它定义在 <signal.h> 头文件中,这个结构体包含了关于信号更详细的信息,使得信号处理函数能够获取有关信号发生背景的更多信息。
在这里插入图片描述
可以看到siginfo结构体包含了很多参数,代码示例我们只需要接受这两个参数,它会将接受到的整型数放在这里,而== si_value== 又是一个结构体,整型数据会存放在 si_value.sival_int 里面。
3.第三个参数用来判断空或者非空(有无收到数据)
**
————————————————————————————————————**
信号处理发送函数:

#include <signal.h>
 
int sigqueue(pid_t pid, int siq, const union sigval value);

参数:
1.传入要接收信号的进程的进程ID
2.要发送的信号的编号
3.第三个参数是个联合体(二选一),原型如下:

union sigval {
    int sival_int;      // 整数值作为附加数据
    void *sival_ptr;    // 指针作为附加数据
};

之所以叫高级版,就是可以实现进程间收发数据,入门版的函数更像是发送某些指令,而高级版可以发送信号的同时发送数据,要么发送整形数,要么发送字符串等。
**
————————————————————————————————————**

运用函数小demo

就简单一个写读数据即可
read:

#include <stdio.h>
#include <signal.h>
 
void handler(int signum, siginfo_t *info, void *context)//用户自定义函数
{
	printf("get signum %d\n",signum);//打印信号编号
 
	if(context != NULL)//如果内容非空
	{
		printf("get data = %d\n",info->si_int);//打印收到的数据
		printf("get data = %d\n",info->si_value.sival_int);//打印收到的数据
	}
}
 
int main()
{
	struct sigaction act;//定义struct sigaction类型的结构体
 
    /*
     * 只需要配置结构体的两个参数,实现最基础的功能
     */
	act.sa_sigaction = handler;//传入自定义函数名
	act.sa_flags = SA_SIGINFO;
 
	sigaction(SIGUSR1,&act,NULL);//检测SIGUSR1信号,参数二传入结构体指针,参数三通常写NULL
    printf("%d\n",getpid());//打印一下进程的ID,方便write函数发送信号和数据
	while(1);//不让程序退出
	return 0;
}

write:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
 
int main(int argc, char **argv)
{
	int signum;
	pid_t pid;
 
    /*
     * 将main函数传入的字符串转换为整形数
     */
	signum = atoi(argv[1]);
	pid = atoi(argv[2]);
 
	union sigval value;//定义联合体
	value.sival_int = 100;//发送整形数100
 
	sigqueue(pid,signum,value);//第三个参数直接传入value即可
 
	return 0;
}

运行结果:
在这里插入图片描述
左边先运行read函数,此时会打印进程的ID号,右边再运行write函数,第一个参数传入信号的编号,SIGUSR1的编号是10,再传入进程ID,这时右边就会打印到收到的数据和信号编号,因为发送的是整型数,所以他会存在两个地方(见proread.c用户自定义函数部分)
————————————————————————————————————

信号量

这个Linux的信号量和FreeRTOS的信号量差不多的,只不过是函数的不同罢了!
可以看一下这个FreeRTOS的信号量一起理解一下:FreeRTOS信号量

信号量相关函数

#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
 
//创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int nsems, int semflg);
 
//对信号量数组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
 
//控制信号量的相关信息
int semctl(int semid, int semnum, int cmd, ...);

参数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

运用小demo:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>      
#include <sys/sem.h>
#include <unistd.h>
 
union semun//联合体的原型
{
    int val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO */
};
 

void p(int semid)//拿钥匙
{
	struct sembuf set;//定义结构体
	set.sem_num = 0;
	set.sem_op = -1;//拿出去-1
	set.sem_flg = SEM_UNDO;
	semop(semid,&set,1);
 
	printf("get key\n");
}

void v(int semid)//放钥匙
{
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op = 1;//放进去+1
    set.sem_flg = SEM_UNDO;
    semop(semid,&set,1);
 
    printf("put back key\n");
}
 
int main(int argc, char **argv)
{
    
	key_t key;
	key = ftok(".",2);
	
    
    int semid;
	semid = semget(key,1,IPC_CREAT|0666);
 
    
	union semun initsem;
	initsem.val = 0;//设置联合体的val为0,就是盒子里面没有信号量,需要“放钥匙”
	semctl(semid,0,SETVAL,initsem);//传入定义的联合体变量
 
    
	int pid=fork();
	if(pid>0)//父进程
	{
	    /*如果是父进程先运行,里面初始化是没有信号量的,会阻塞在这里*/
		p(semid);//拿钥匙(刚开始没有,需要别人放进去才可以拿,没有拿不了)
		printf("father\n");
		v(semid);//放钥匙
	}
	else if(pid == 0)//子进程
	{
		printf("child\n");
		v(semid);//放钥匙(给父进程有的拿)
	}
 
	return 0;
}

运行结果:
在这里插入图片描述

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

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

相关文章

基于微信小程序的宠物服务平台(系统源码+lw+部署文档+讲解等)

文章目录 目录 详细视频演示 系统详细设计截图 微信小程序系统的实现 1.1系统前台功能的实现 2.1微信小程序开发环境搭建 2.2微信开发者工具 2.3程序应用相关技术和知识 2.3.1小程序目录结构以及框架介绍 2.3.2 Java技术 2.3.3 MySQL数据库 2.3.4 SSM框架 源码获…

构建铁路安全防线:EasyCVR视频+AI智能分析赋能铁路上道作业高效监管

一、方案背景 随着我国铁路特别是高速铁路的快速发展&#xff0c;铁路运营里程不断增加&#xff0c;铁路沿线的安全环境对保障铁路运输的安全畅通及人民群众的生命财产安全具有至关重要的作用。铁路沿线安全环境复杂多变&#xff0c;涉及多种风险因素&#xff0c;如人员入侵、…

函数递归超详解!

目录 1.什么是递归调用&#xff1f; 直接调用 间接调用 2.什么是递归&#xff1f; 3.递归举例 3.1求n!的阶乘 3.1.1.非递归法 3.1.2.递归法 3.1.2.1分析和代码实现 3.2顺序打印一个整数的每一位 3.2.1分析和代码实现 4.递归与迭代 4.1举例&#xff1a;斐波那契数列 …

开放式耳机更适合运动的时候使用?开放式耳机推荐指南

开放式耳机确实非常适合运动时使用&#xff0c;原因主要有以下几点。 首先&#xff0c;保持对外界的感知是很重要的一点。在运动的时候&#xff0c;我们需要听到周围的环境声音&#xff0c;比如车辆的行驶声、行人的呼喊等&#xff0c;以便及时做出反应&#xff0c;保证自身安全…

【MySQL】索引概念解析

1.什么是索引&#xff1f; MySQL中的索引是一种数据结构&#xff0c;用于帮助MySQL数据库管理系统快速查询数据。索引的主要目的是提高数据检索的速度&#xff0c;减少数据库系统需要扫描的数据量。 优点&#xff1a; 索引可以极大的提高数据检索效率&#xff0c;降低数据库…

【Nuxt】配置

Nuxt 配置 nuxt.config.ts 里面可以添加相关配置&#xff1a; runtimeConfig 运行时配置。 // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({compatibilityDate: 2024-04-03,devtools: {enabled: true},runtimeConfig: {appKey: …

手拉手模型笔记and一线三角笔记

手拉手模型 基本需要&#xff1a;两个 顶角相等 的 等腰 三角形 共 顶点 反手拉手: 等边 等腰 R t △ 等边\\等腰Rt△ 等边等腰Rt△ 左手拉左手&#xff0c;右手拉右手( − 红线 − \textcolor{red}{-红线-} −红线−): △ A B D ≅ △ A C E ( S A S ) △ABD \cong △ACE(S…

一次多波束和浅地层处理的经历—信标机出问题?

最近处理多波束和浅地层时&#xff0c;一个从来没有过的问题出现了。 多波束数据(.pds)是由PDS2000采集的&#xff0c;使用设备型号为T50P。浅地层数据(.raw)是有SESWIN采集的&#xff0c;使用设备型号为SES2000 Standard。 1、多波束处理 多波束数据采用CARIS11.3处理的。船…

返校季热度持续发酵,赛盈分销浅谈下半年选品趋势!

小孩学习用的东西&#xff0c;那可真是省不了一点&#xff0c;该买的&#xff0c;家长还是会买。 特别是在每年的7-9月份正是海外返校季高消费的时候&#xff0c;这也是卖家少有的能在淡季里血赚的机会了。 都说早起的鸟儿有虫吃&#xff0c;一些朋友提前行动的&#xff0c;已经…

day17 Java流程控制——用户交互Scanner

day17 Java流程控制——用户交互Scanner 目录 day17 Java流程控制——用户交互Scanner1. 什么是Scanner对象&#xff1f;2. 实操 1. 什么是Scanner对象&#xff1f; Scanner对象是Java编程语言中的一个类&#xff0c;存在于java.util包中。它用于获取输入&#xff0c;可以是各…

数据库的安装初始化及管理

1. 官网下载或者 wget [rootmysql ~] # ls anaconda-ks.cfg initserver.sh mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar mysql-community-client-8.0.33-1.el7.x86_64.rpm mysql-community-client-plugins-8.0.33-1.el7.x86_64.rpm mysql-community-common-8.0.33-1.el7.…

大数据-57 Kafka 高级特性 消息发送相关01-基本流程与原理剖析

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

反转链表~

一&#xff1a;初始化 public class ListNode {public int val;public ListNode next;public ListNode(int val,ListNode next){this.val val;this.next next;}Overridepublic String toString(){StringBuilder sb new StringBuilder(64);sb.append("[");ListNod…

【系统架构设计师】二十四、安全架构设计理论与实践①

目录 一、安全架构概述 1.1 信息安全面临的威胁 1.1.1 安全威胁分类 1.1.2 常见的安全威胁 1.2 安全架构的定义和范围 二、安全模型 2.1 状态机模型 2.2 Bell-LaPadula模型 2.3 Biba模型 2.4 Clark-Wilson模型 2.5 Chinese Wall 模型 往期推荐 一、安全架构概述 1…

vue3+vue-simple-uploader +SpringBoot实现大文件分块上传

效果图 一、安装所需依赖包 npm install vue-simple-uploadernext --savenpm install spark-md5 --save二、main.ts 注册组件 import { createApp } from vue import uploader from vue-simple-uploader import vue-simple-uploader/dist/style.css import App from ./App.vu…

Java ArrayList源码阅读笔记(基于JDK17)

Java ArrayList源码阅读笔记&#xff08;基于JDK17&#xff09; 虽然不喜欢看源码&#xff0c;但是据说会让人变强啊&#xff0c;看别的大佬的代码也许才知道怎么处理自己的一坨吧&#xff0c;因此冒着秃顶的风险还是来看看吧。。。 第一遍先简单看看吧&#xff0c;搞不清楚的…

双阈值最大最小值筛选

问题&#xff1a; 如下图所示的问题&#xff0c;给定最小阈值、最大阈值以及一段数据队列&#xff0c;对数据队列中超过阈值部分的极值进行保存&#xff0c;即从队列中得到P1-P6 计算规则 规则类似状态机 首先定义last_type标志位&#xff1a; { 上一时刻大于 m a x _ t h…

win7安装mysql-installer-community-8.0.11.0

1、安装Microsoft Visual C 2019 Redistributable Package (x64) 官网下载地址&#xff1a;https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?viewmsvc-160#latest-microsoft-visual-c-redistributable-version 通过百度网盘分享的文件&#xff1…

VBA_MF系列技术资料1-680

MF系列VBA技术资料1-680 WORD 目录下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/17TrFO37OgnjiwvACvMna_A?pwdbr3g 提取码&#xff1a;br3g 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff…

大数据-63 Kafka 高级特性 分区 副本机制 宕机恢复 Leader选举

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…