当前位置: 首页 > news >正文

轻松Linux-9.进程间通信

来了来了


1.序言

这一章,我们就来看看进程间通信,程序和系统的运作都离不开进程之间的通信。

进程间通信一般是为了以下几点:

数据传输:进程间的数据传输。

资源共享:将指定的资源共享给多个进程。

通知事件:进程工作时,也需要通知其它一个或一组进程,告诉它们发送了什么事件(进程结束时要通知其父进程)。

进程控制:有时我们需要一个进程部分或完全控制另一个进程,拦截其它进程所有陷入和异常的状态,以及时的知道该进程的状态,例如:debug时。

进程间通信主要有这几种:

管道:又分为匿名管道和命名管道。

System V IPC:System V消息队列、System V共享内存、System V信号量。

POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁。

IPC(Inter-Process Communication,进程间通信) 是计算机操作系统中,不同进程之间交换数据或同步操作的机制。它允许独立运行的进程(可能由不同用户或程序创建)协调工作,共享资源或传递信息,从而构建复杂的分布式或并行系统。


2.管道

2.1匿名管道

需要包的头文件
#include <unistd.h>函数功能:创建一个匿名管道
int pipe(int fd[2]);参数:传入一个文件描述符数组,fd[0]表示读端,fd[1]表示写端。成功返回0,失败返回错误代码。
//从键盘输入,再从管道读取。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main()
{int fd[2];char buffer[233];int len;if(pipe(fd) == -1){perror("make pipe");exit(1);}while(fgets(buffer, 233, stdin)){len = strlen(buffer);if(write(fd[1], buffer, len) != len){perror("write pipe");exit(1);}memset(buffer, 0, sizeof(buffer));if((len = read(fd[0], buffer, sizeof(buffer))) == -1){perror("read pipe");exit(1);}if(write(1, buffer, len) == -1){perror("write stdout");exit(1);}}    return 0;
}

若要父子间进程通信,可参看下图↓

再关闭父进程或子进程的读端或写端,就可实现一方负责读,一方负责写。

父子进程的文件描述符如下

    ↓从内核的角度来看就是↓

从这里可以看出,管道其实也被抽象成了文件,内核通过file_operation来调用管道相关的接口。

2.1.1匿名管道的读写以及特点

当管道没有数据可读时(读相关):

未设置O_NONBLOCK:read函数会被阻塞,即进程会被暂停执行,直到有数据可读为止。

设置了O_NONBLOCK:read调用会返回-1,errno的值会被设置为EAGAIN

当管道数据已满时(写相关):

未设置O_NONBLOCK:write同样会被阻塞,直到可以写入为止。

设置了O_NONBLOCK:write调用同样会直接返回-1,并将errno设置为EAGAIN

EAGAIN是:Linux/Unix 系统中的一个错误码(errno),表示资源暂时不可用,但稍后重试可能成功。可以理解为“资源暂时不可用,稍后再试”。

如果管道所有写端的文件描述符被关闭,read函数会返回0。

如果管道所有读端的文件描述符被关闭,write函数会产生SIGPIPE信号,进而可能会使write进程退出。

当要写入的数据量不大于PIPE_BUF时,linux将保证写入原子性
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入原子性。                                            PIPE_BUF通常为4KB512B,取决于系统。

匿名管道一般只能用于有共同祖先(具有亲缘关系,如父子进程)进程的通信,一般是一个进程创建管道,然后调用fork函数,之后父子进程就可以通信了。

管道(Pipe)提供流式服务,意味着它以连续、无固定边界的数据流形式传输数据,而非一次性传输完整的数据块。它具有先进先出的特点,有点像水流(字节流)。可以用两个管道来实现全双工(同时进行写或读)。

管道是半双工的,即只能双方交替进行数据传输,不能同时进行读或写,内核会对管道操作进行同步和互斥,并且一般管道的生命周期同进程一样。

2.2命名管道

匿名管道还是不方便,只能用于有亲缘关系进程间的通信,有没有更强的呢?

有的兄弟,有的。命名管道就是一个解决方案,我们可以创建FIFO文件来完成这份工作,FIFO文件就是命名管道。

Shell或者控制台可以用这串指令创建命名管道
mkfifo filename(文件名)
#include <sys/types.h>  // 提供系统数据类型定义(如mode_t)
#include <sys/stat.h>   // 提供文件模式和权限相关定义(如S_IRUSR、S_IWUSR等)int mkfifo(const char* pathname, mode_t mode);参数:
pathname:有名管道的路径名(如 "/tmp/my_fifo")。
mode:设置管道文件的权限(如 0666 表示所有用户可读写)。成功时返回 0,失败时返回 -1 并设置 errno。删除管道文件要使用unlink()函数:
#include <unistd.h>  // 包含 unlink() 的声明int unlink(const char *pathname);
参数:
pathname:为要删除的文件或符号链接的路径。

创建好管道后可以使用open()函数来操作,就行文件操作一样。

命名管道与匿名管道的区别:创建、删除和打开略有区别,除此之外几乎没有不同。

2.2.1命名管道的打开规则

如果当前为读打开FIFO文件:

设置O_NONBLOCK:open()函数会阻塞,直到有进程以写打开。

未设置O_NONBLOCK:直接返回成功。但后续read()会返回-1,并设置errno为EAGAIN。

如果当前为写打开FIFO文件:

设置O_NONBLOCK:open()函数会阻塞,直到有进程以读打开。

未设置O_NONBLOCK:直接失败返回-1,errno设置为ENXIO。

目的:确保数据生产者(写端)和数据消费者(读端)同时存在,避免无效操作。


3.System V共享内存

System V共享内存是最快的IPC方式,只要将内存映射到目标进程的地址空间内,之后的数据传输就不再需要进入内核的系统调用,即不再涉及内核。

↓内核中的数据结构↓

/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct shmid_ds {struct ipc_perm		shm_perm;	/* operation perms */int			shm_segsz;	/* size of segment (bytes) */__kernel_time_t		shm_atime;	/* last attach time */__kernel_time_t		shm_dtime;	/* last detach time */__kernel_time_t		shm_ctime;	/* last change time */__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */unsigned short		shm_nattch;	/* no. of current attaches */unsigned short 		shm_unused;	/* compatibility */void 			*shm_unused2;	/* ditto - used by DIPC */void			*shm_unused3;	/* unused */
};

System V共享内存所需函数:

定义System V IPC(进程间通信)相关的结构体和常量(如key_t类型、IPC_CREAT、IPC_EXCL等标志)。
#include <sys/ipc.h>包含共享内存函数的声明及共享内存状态结构体(如struct shmid_ds)的定义。
#include <sys/shm.h>定义基本数据类型(如key_t、size_t),这些类型在共享内存操作中用于标识键值和内存大小。
#include <sys/types.h>提供ftok函数(用于生成唯一的键值key_t)
#include <unistd.h>//用于创建共享内存
int shmget(key_t key, size_t size, int shmflg);
参数:key:这个共享内存段名字。size:共享内存大小。shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
-------------------------------------------------------------------------------------
//将共享内存映射到进程地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:shmid: 共享内存标识。shmaddr:指定连接的地址。shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY。
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1。
注:
1.如果shmaddr为NULL,有系统选择地址。
2.shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
3.shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)。
4.shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。
-------------------------------------------------------------------------------------
//让当前进程与共享内存脱离,但不等于删除共享内存片段
int shmdt(const void *shmaddr);
参数:shmaddr: 由shmat所返回的指针。
返回值:成功返回0;失败返回-1。
-------------------------------------------------------------------------------------
//控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:shmid:由shmget返回的共享内存标识码。cmd:将要采取的动作(有三个可取值)。buf:指向一个保存着共享内存的模式状态和访问权限的数据结构。
返回值:成功返回0;失败返回-1。cmd参数的取值:
IPC_STAT: 获取共享内存段的状态信息,将内核中的struct shmid_ds数据复制到用户空间的buf指针指向的结构体中。(需要当前进程有共享内存的读权限)
IPC_SET: 修改共享内存的属性(如权限、所有者ID等)。仅允许修改struct shmid_ds中的以下字段:shm_perm.uid(所有者用户ID)shm_perm.gid(所有者组ID)shm_perm.mode(权限位,仅低9位有效)。需要当前进程的用户ID是共享内存的创建者或所有者。即,改变其他进程(所属其他用户或用户组)的访问权限。
IPC_RMID: 删除共享内存段。内核会标记该段为“待删除”,但实际释放内存需等待所有附加进程分离(通过shmdt)后异步完成。
权限需求与IPC_SET一样。

System V消息队列这里不作详细介绍,可以看看其他佬的博文。


4.System V信号量

System V信号量是一种用于进程间同步与互斥的机制,也属于System V IPC。它通过管理公共资源的访问权限,来协调多个进程对同一份临界区资源的操作,避免数据竞争和不一致的问题。

信号量的本质是:一个非负整数的计数器,用于记录可用资源的数量,我们可以通过P、V操作来对信号量进行操作,实现资源的申请和释放。

一些相关概念:

1.多个执行流(进程)看到的公共资源,称之为共享资源。

2.被保护起来的资源,叫做临界资源。

3.常见的保护方式:同步和互斥。多个执行流,访问临界资源的时候,且有一定的顺序性,为同步。任意时刻,只能有一个执行流访问临界资源,叫做互斥。

4.系统中的某些资源一次只允许一个执行流访问,叫做互斥资源。

5.在程序中涉及到临界资源部分的代码,叫做临界区,代码也可以分为:临界区和非临界区。

所以对共享资源进行保护,实则是在限制访问临界资源的执行流。

信号量:

一元信号量:一个非0即1的计数器,用于互斥锁(用于确保只有一个进程可以进行操作)。

计数信号量:值可以为任何非负整数,用于管理多份同类资源(例如数据库连接池)。

关键操作--P、V操作(P\V操作是原子的,确保多进程并发时的正确性和唯一性):

P操作:用于申请资源,信号量计数器-1。如果计数器<=0,则会阻塞,直到计数器>0才会执行。

V操作:用于释放操作,信号量计数器+1。唤醒一个等待的进程。

核心函数:

#include <sys/sem.h>//创建或获取信号量集
int semget(key_t key, int nsems, int semflg);
参数:key:唯一标识信号量集的键值(通常用ftok生成)。nsems:集中信号量的数量。semflg:权限标志(如0666)和控制标志(如IPC_CREAT、IPC_EXCL)。
返回值:成功返回信号量集标识符(semid),失败返回-1。
-------------------------------------------------------------------------------------
//控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
参数:semid:信号量集标识符。semnum:信号量编号(信号量集中的索引)。cmd:控制命令(如SETVAL设置初始值、IPC_RMID删除信号量集、GETVAL获取值)。...:可选参数(如union semun用于SETVAL)。
返回值:成功返回0或特定值,失败返回-1。
-------------------------------------------------------------------------------------
//执行P/V操作
int semop(int semid, struct sembuf *sops, size_t nsops);
参数:semid:信号量集标识符。sops:指向struct sembuf数组的指针,定义操作类型(P/V)和信号量编号。nsops:操作数量(通常为1)。
返回值:成功返回0,失败返回-1。
-------------------------------------------------------------------------------------
定义P/V操作的结构体,是 System V 信号量操作的核心结构体,定义在 <sys/sem.h> 头文件中。
struct sembuf {unsigned short sem_num; // 信号量编号short sem_op;          // 操作值(P:-1,V:+1)short sem_flg;         // 操作标志(如SEM_UNDO)
};↓需要自己定义↓
用于semctl的SETVAL等命令:
union semun {int val;                // 信号量初始值(SETVAL)struct semid_ds *buf;   // 信号量集属性(IPC_STAT/IPC_SET)unsigned short *array;  // 信号量值数组(GETALL/SETALL)
};

想要深入了解System V更多的信息,可以去查阅相关资料或问问ai。


5.内核中的IPC结构

bro参考的Linux内核版本是linux-5.0-rc3struct ipc_ids {int in_use;unsigned short seq;struct rw_semaphore rwsem;struct idr ipcs_idr;int max_idx;
#ifdef CONFIG_CHECKPOINT_RESTOREint next_id;
#endifstruct rhashtable key_ht;
};
......
/* used by in-kernel data structures */
struct kern_ipc_perm {spinlock_t	lock;bool		deleted;int		id;key_t		key;kuid_t		uid;kgid_t		gid;kuid_t		cuid;kgid_t		cgid;umode_t		mode;unsigned long	seq;void		*security;struct rhash_head khtnode;struct rcu_head rcu;refcount_t refcount;
} ____cacheline_aligned_in_smp __randomize_layout;
......
/* one msq_queue structure for each present queue on the system */
struct msg_queue {struct kern_ipc_perm q_perm;time64_t q_stime;		/* last msgsnd time */time64_t q_rtime;		/* last msgrcv time */time64_t q_ctime;		/* last change time */unsigned long q_cbytes;		/* current number of bytes on queue */unsigned long q_qnum;		/* number of messages in queue */unsigned long q_qbytes;		/* max number of bytes on queue */struct pid *q_lspid;		/* pid of last msgsnd */struct pid *q_lrpid;		/* last receive pid */struct list_head q_messages;struct list_head q_receivers;struct list_head q_senders;
} __randomize_layout;
......
/* One queue for each sleeping process in the system. */
struct sem_queue {struct list_head	list;	 /* queue of pending operations */struct task_struct	*sleeper; /* this process */struct sem_undo		*undo;	 /* undo structure */struct pid		*pid;	 /* process id of requesting process */int			status;	 /* completion status of operation */struct sembuf		*sops;	 /* array of pending operations */struct sembuf		*blocking; /* the operation that blocked */int			nsops;	 /* number of operations */bool			alter;	 /* does *sops alter the array? */bool                    dupsop;	 /* sops on more than one sem_num */
};
......
struct shmid_kernel /* private to the kernel */
{struct kern_ipc_perm	shm_perm;struct file		*shm_file;unsigned long		shm_nattch;unsigned long		shm_segsz;time64_t		shm_atim;time64_t		shm_dtim;time64_t		shm_ctim;struct pid		*shm_cprid;struct pid		*shm_lprid;struct user_struct	*mlock_user;/* The task created the shm object.  NULL if the task is dead. */struct task_struct	*shm_creator;struct list_head	shm_clist;	/* list by creator */
} __randomize_layout;

《进击的巨人》


文章转载自:

http://DJjtB7bb.zLhbg.cn
http://HTYyXkT9.zLhbg.cn
http://iu3uyJxb.zLhbg.cn
http://y6ldwg8c.zLhbg.cn
http://lfzrZ9A8.zLhbg.cn
http://Dolbi7CC.zLhbg.cn
http://XYR6KKSy.zLhbg.cn
http://C6C6alnn.zLhbg.cn
http://G4kmJkO3.zLhbg.cn
http://9fL1o4Bv.zLhbg.cn
http://dItPayQb.zLhbg.cn
http://4K9kkRJM.zLhbg.cn
http://DTb1cmUK.zLhbg.cn
http://AMcDTJkL.zLhbg.cn
http://dfjxRvyS.zLhbg.cn
http://K0e4Xlu4.zLhbg.cn
http://ZRCyy0uO.zLhbg.cn
http://q0aGQZe2.zLhbg.cn
http://l1c0lCrU.zLhbg.cn
http://LhOaSHM9.zLhbg.cn
http://IF15QNun.zLhbg.cn
http://iD0Wolye.zLhbg.cn
http://4BESYKu8.zLhbg.cn
http://piks18zh.zLhbg.cn
http://hlH1MMFY.zLhbg.cn
http://Wfvb70h5.zLhbg.cn
http://8rpfEOUd.zLhbg.cn
http://9xbohLmq.zLhbg.cn
http://DFFm52ZH.zLhbg.cn
http://TTCGfJ7w.zLhbg.cn
http://www.dtcms.com/a/372877.html

相关文章:

  • 20250908的学习笔记
  • Golang 与 gRPC
  • shareId 的产生与传递链路
  • Go语言实战案例-开发一个JSON格式校验工具
  • AI技术架构与GEO算法原理如何重塑搜索引擎可见性
  • 【AI测试前沿】谷歌Fuzzing安全测试Go语言指南
  • 佰力博检测与您探讨薄膜样品如何测介电常数?
  • jsBridge接入流程
  • TFS-2018《On the convergence of the sparse possibilistic c-means algorithm》
  • ArrayList中的源码解析
  • 详细解析SparkStreaming和Kafka集成的两种方式的区别和优劣
  • 大数据Spark(六十三):RDD-Resilient Distributed Dataset
  • 云原生TodoList Demo 项目,验证云原生核心特性
  • C语言爬虫开发:常见错误与优化方案
  • Linux 应急响应实操 Checklist
  • 【PCIe EP 设备入门学习专栏 -- 8.2.3 Local Bus Controller (LBC) 详细介绍】
  • 将基于 Oracle JDK 17 开发的 Spring Boot 3.2.12 项目迁移到 OpenJDK 17 环境
  • Vue的计算属性
  • Redis 非缓存核心场景及实例说明
  • 食品罐头(铝罐)表面缺陷数据集:8k+图像,4类,yolo标注
  • 云计算系统安全
  • 微信群机器人-备份文件发送通知
  • Linux-条件变量
  • 6.python——字符串
  • 懒汉式——LazyMan(任务队列应用)
  • Nginx 实战系列(四)—— Nginx反向代理与负载均衡实战指南
  • Nginx 反向代理 + Tomcat 集群:负载均衡配置步骤与核心原理
  • 【Linux】匿名管道和进程池
  • PWA:打造媲美 Native Apps 的 Web 应用体验
  • # 小程序 Web 登录流程完整解析