进程间的通信

发布于:2025-07-04 ⋅ 阅读:(11) ⋅ 点赞:(0)

一、进程间通信的本质与核心问题
 
- 是什么:让两个或多个进程,在数据层面完成交互(比如传递基本数据、发送命令、协同操作、通知事件等 )。
- 难点:进程本身是相互独立的,这使得通信“成本高”(数据要跨进程传递,需额外处理独立性带来的隔离问题 )。
 
二、解决思路:让进程看到“同一份资源”
 
进程间通信,本质是让不同进程能访问到“同一份资源”(通常是特定形式的内存空间 ),关键逻辑:
 
- 资源由谁提供:一般由操作系统提供(而非让某一个进程单独创建,否则会破坏进程独立性,变成“进程私有资源” )。
- 进程如何访问:进程通过 “系统调用接口” ,访问操作系统管理的这份“资源”,实现创建、使用、释放等操作(进程相当于“用户”,借助系统调用和系统交互 )。
 
三、操作系统的通信模块与标准
 
- 通信模块:操作系统会设计 独立的“IPC通信模块” ,常“隶属于文件系统”(把通信资源以类似文件的方式管理 )。
- 行业标准:进程间通信有通用标准,典型的如 System V、posix(不同系统会基于这些标准实现,保证兼容性 )。
 
四、具体通信方式与分类
 
- 基于文件级别的简单方式:比如 管道(把数据传递抽象成“文件读写”逻辑,是较基础的进程通信手段 )。
- System V IPC 经典方式(更灵活、针对复杂场景 ):
- 消息队列:进程间按“消息”为单位传递数据,可实现异步、解耦的通信。
- 共享内存:让多个进程直接访问同一块内存区域,速度快(但需自己处理同步、互斥问题 )
- 信号量:主要用于“同步、互斥”,协调多个进程对共享资源的访问(比如防止同时修改同一份数据 )。
- 其他扩展方式(图中提到的补充 ):还包括互斥量、条件变量、读写锁等(更细分场景下,用于优化同步逻辑、提升效率 )。
 
简单说,进程间通信的核心是“借操作系统的‘共享资源’打破进程独立性”,再通过不同的通信方式(管道、System V 系列、互斥/同步工具等 ),满足“传数据、发命令、协同工作”等需求 。

共享内存

原理

操作系统是需要管理共享内存的,通过内核结构体描述共享内存。

释放共享内存:先去关联,再释放共享内存

步骤:

一创建共享内存

shmget是Linux系统中用于创建或获取共享内存段的系统调用,是System V进程间通信(IPC)机制的核心函数之一。相关信息如下:
 
- 函数原型: int shmget(key_t key, size_t size, int shmflg); 
- 参数说明:
-  key :是一个键值,用于标识共享内存段。若多个进程要共享同一块内存区域,需使用相同的 key 值。当 key 为 0 ( IPC_PRIVATE )时,会建立新的共享内存对象;当 key 为大于0的32位整数时,通常由 ftok 函数生成


-  size :指定新建共享内存的大小,单位为字节,且需为系统页大小(通常是4KB)的整数倍。若只是获取已存在的共享内存,此参数可设为0。
-  shmflg :标识函数的行为及共享内存的权限。常用标志位有 IPC_CREAT (若内核中不存在指定 key 的共享内存,则新建一个)、 IPC_EXCL (与 IPC_CREAT 配合使用,若共享内存已存在则报错)。此外,还需与权限值(如 0600 )进行按位或运算来确定共享内存的存取权限。
- 返回值:成功时返回共享内存的标识符shmid,他只在进程中;出错时返回-1,错误原因存于 errno 中。常见错误代码包括 EINVAL (参数 size 不合理等)、 EEXIST (共享内存已存在但设置了 IPC_EXCL )、 ENOENT (共享内存不存在且未设置 IPC_CREAT )等。

ftok函数

ftok是Linux中用于生成System V IPC(进程间通信)键值(key)的函数,常与shmget等IPC函数配合使用,相关信息如下:
 
函数原型

 key_t ftok(const char *pathname, int proj_id); 
 
参数说明
 
- pathname:(由通信进程创建,由用户指定路径)
指定一个存在的文件路径。函数会根据该文件的inode号(文件在文件系统中的唯一标识)生成key的一部分。
- proj_id:
一个8位的整数(0-255),作为key的另一部分。通常使用一个字符(如'A'),其ASCII码值会被自动转换为整数。
 
返回值
 
成功时返回一个key_t类型的键值(32位整数),可用于标识共享内存、信号量等IPC对象;出错时返回-1(如pathname不存在、无访问权限等)。
 
生成逻辑
 
ftok将pathname对应的inode号与proj_id按特定规则组合,生成唯一的key。例如:
 
1. 获取pathname的inode号(通过stat系统调用)。
2. 取inode号的低24位与proj_id的8位组合,形成32位的key。
 
注意事项
 
- 文件唯一性:不同文件的inode号不同,若使用同一文件和proj_id,生成的key相同;若文件被删除后重建,inode号可能变化,导致key改变。
- proj_id范围:proj_id必须在0-255之间,否则结果未定义。
- 跨主机问题:同一文件在不同主机上的inode号可能相同,但proj_id需一致才能生成相同key,因此ftok通常仅用于单机IPC场景。
 
应用场景
 
常用于多个进程需要共享同一IPC对象的场景。例如:
1. 服务器进程用ftok生成key,创建共享内存;
2. 客户端进程用相同的pathname和proj_id生成key,通过shmget获取同一共享内存。
 
示例: key = ftok("/tmp/myfile", 'a');   // 若/tmp/myfile存在,生成唯一key。

共享内存的生命周期是随内核的,用户不主动关闭,共享内会一直存在,除非内核重启。

指令ipcs -m,它是 Linux 系统中用于查看共享内存(Shared Memory)相关信息的命令行工具。

ipcrm  是 Linux 系统中用于删除 System V IPC(进程间通信)对象的命令行工具,可删除共享内存、信号量或消息队列。以下是其详细用法:
 
基本语法
ipcrm [选项] [参数]

 
 
常用选项与功能
 
根据删除的 IPC 对象类型,选项分为三类:
 
1. 删除共享内存(Shared Memory)
-  -m shmid :通过共享内存标识符( shmid )删除对象。
- 示例: ipcrm -m 12345 (删除标识符为 12345 的共享内存段)。

2. 删除信号量(Semaphore)
-  -s semid :通过信号量标识符( semid )删除对象。
- 示例: ipcrm -s 67890 。
3. 删除消息队列(Message Queue)
-  -q msqid :通过消息队列标识符( msqid )删除对象。
- 示例: ipcrm -q 101112 。
 
结合  ipcs  命令使用
 
通常先通过  ipcs  查看目标对象的标识符,再用  ipcrm  删除:
 
1. 查看共享内存列表及  shmid :
ipcs -m
 
 
输出示例:

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x5a1a3c89 131073     user       644        4096       2 

        
2. 删除指定  shmid  的共享内存:
ipcrm -m 131073
 
 
注意事项
 
- 权限要求:普通用户只能删除自己创建的 IPC 对象,root 用户可删除任意对象。
- 强制删除:即使有进程正在附加( nattch > 0 ), ipcrm  仍会删除共享内存,可能导致进程访问异常。
- 生命周期影响:删除后,所有附加到该对象的进程在下次操作时会收到错误(如  shmat  返回 -1)。
 
替代方案
 
若需编程删除共享内存可使用  shmctl  函数:

#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// 当 cmd 为 IPC_RMID 时,删除共享内存段
 

共享内存之所以是进程间通信(IPC)中最快的方式,核心原因在于它避免了数据拷贝和内核干预。 

进程共享内存挂接函数
 shmat 是Linux中用于将共享内存段附加到进程地址空间的系统调用,是共享内存通信的核心操作之一。以下是其详细用法和功能解析:
 
一、函数原型与头文件
 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

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


 
 
- 参数说明:
-  shmid :共享内存段的标识符(由 shmget 返回)。
-  shmaddr :指定附加的虚拟地址(通常设为 NULL ,由系统自动分配)。
-  shmflg :标志位,常用值:
-  0 :以只读方式附加(若共享内存创建时允许)。
-  SHM_RDONLY :强制以只读方式附加。
 
二、返回值与错误处理
 
- 成功:返回指向共享内存段的指针(可直接读写)。
- 失败:返回 (void *)-1 ,并设置 errno :
-  EACCES :权限不足(无读权限)。
-  EINVAL : shmid 无效或共享内存已被删除。
-  ENOMEM :进程地址空间不足。
 
三、使用示例(C语言)
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>

#define SHM_SIZE 1024  // 共享内存大小

int main() {
    int shmid;
    char *shmptr;
    
    // 1. 获取已存在的共享内存段(假设key为0x1234)
    shmid = shmget(0x1234, 0, 0666);
    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }
    
    // 2. 附加共享内存到进程地址空间
    shmptr = shmat(shmid, NULL, 0);
    if (shmptr == (void *)-1) {
        perror("shmat failed");
        exit(1);
    }
    
    // 3. 读写共享内存(示例:读取字符串)
    printf("Shared memory content: %s\n", shmptr);
    
    // 4. 修改共享内存(需有写权限)
    strcpy(shmptr, "Hello from process 2!");
    printf("Modified content: %s\n", shmptr);
    
    // 5. 分离共享内存(非删除,仅断开进程关联)
    if (shmdt(shmptr) == -1) {
        perror("shmdt failed");
        exit(1);
    }
    
    return 0;
}


 
 
四、关键机制与注意事项
 
1. 地址空间映射:
-  shmat 将内核中的共享内存段映射到进程的虚拟地址空间,进程可直接通过指针操作内存,无需内核参与数据拷贝(这也是共享内存高效的原因)。
2. 权限与附加状态:
- 附加权限由共享内存的 perms 控制(如 0666 表示所有者、组、其他用户均可读写)。
- 可通过 ipcs -m 查看当前附加到共享内存的进程数( nattch 列)。
3. 与 shmdt 的配合:
- 进程终止时会自动分离共享内存,但建议主动调用 shmdt 释放资源。
- 分离不代表删除共享内存段,需通过 shmctl 的 IPC_RMID 命令删除。
4. 同步问题:
- 共享内存本身不提供同步机制,需配合信号量、互斥锁等实现进程间读写同步,避免数据竞争。
 
五、与其他系统调用的关联
 
graph TD
A[shmget(创建/获取共享内存)] --> B[shmat(附加到进程)]
B --> C[进程通过指针读写内存]
C --> D[shmdt(分离内存)]
D --> E[shmctl(删除内存段)]
 

进程共享内存分离函数

shmdt 是Linux中用于分离共享内存段与进程地址空间的系统调用,是共享内存通信的收尾操作之一。以下是其详细用法和功能解析:
 
一、函数原型与头文件
 

#include <sys/types.h>
#include <sys/shm.h>int shmdt(const void *shmaddr);

- 参数说明:
-  shmaddr :指向已附加的共享内存段的指针(即 shmat 返回的地址)。
 
二、返回值与错误处理
 
- 成功:返回 0 。
- 失败:返回 -1 ,并设置 errno :
-  EINVAL : shmaddr 不是有效的共享内存指针(未通过 shmat 附加)。
 
三、使用示例(C语言)
 

#include <stdio.h>
#include <sys/shm.h>

int main() {
    int shmid;
    char *shmptr;
    
    // 1. 获取共享内存段(假设已存在)
    shmid = shmget(0x1234, 0, 0666);
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }
    
    // 2. 附加共享内存
    shmptr = shmat(shmid, NULL, 0);
    if (shmptr == (void *)-1) {
        perror("shmat failed");
        return 1;
    }
    
    // 3. 读写共享内存...
    
    // 4. 分离共享内存
    if (shmdt(shmptr) == -1) {
        perror("shmdt failed");
        return 1;
    }
    printf("Shared memory detached successfully.\n");
    
    return 0;
}
 


 
四、核心机制与作用
 
1. 解除地址映射:
-  shmdt 的本质是将共享内存段从进程的虚拟地址空间中卸载,断开指针与内核共享内存的关联。
- 分离后,进程无法再通过原指针访问共享内存,避免野指针操作。
2. 计数管理:
- 共享内存段维护一个 nattch 计数器(记录当前附加的进程数), shmdt 会将该计数减1。
- 当 nattch 减为0时,若共享内存已被标记为删除(通过 shmctl 的 IPC_RMID ),则内核会真正释放其物理内存。
 
五、与其他操作的关联
 
graph TD
A[shmget创建共享内存] --> B[shmat附加到进程]
B --> C[进程读写内存]
C --> D[shmdt分离内存]
D --> E{是否删除共享内存?}
E -- 是 --> F[shmctl删除]
E -- 否 --> G[共享内存保留,等待其他进程访问]

 
 
六、注意事项
 
1. 分离≠删除:
-  shmdt 仅断开进程与共享内存的关联,共享内存段本身仍存在于系统中(需通过 shmctl 删除)。
- 可通过 ipcs -m 查看共享内存的 nattch 计数,确认当前附加的进程数。
2. 进程终止的自动处理:
- 进程退出时(如 exit 或异常崩溃),系统会自动调用 shmdt 分离所有已附加的共享内存,无需手动处理。
- 但主动调用 shmdt 是良好的编程习惯,可及时释放资源。
3. 同步与资源释放:
- 若多个进程共享同一内存段,某进程调用 shmdt 不影响其他进程的访问,仅减少 nattch 计数。
- 建议在分离前确保数据已同步(如写入完成),避免其他进程读取到不完整数据。

删除进程共享内存的函数


 shmctl 是Linux中用于控制共享内存段属性的系统调用,可实现获取状态、修改权限、删除共享内存等操作。以下是其详细功能解析:
 
一、函数原型与头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);

- 参数说明:
-  shmid :共享内存段的标识符(由 shmget 返回)。
-  cmd :操作命令(核心命令见下方列表)。
-  buf :指向 shmid_ds 结构体的指针,用于传入/返回属性信息。
 
二、核心操作命令(cmd)
 
1. 获取共享内存状态( IPC_STAT )
 
- 作用:将共享内存段的属性复制到 buf 指向的结构体中。
- 结构体关键字段:

struct shmid_ds {
    struct ipc_perm shm_perm;  // 权限信息
    size_t shm_segsz;         // 内存段大小(字节)
    pid_t shm_lpid;           // 最后一次操作的进程PID
    pid_t shm_cpid;           // 创建者PID
    unsigned short shm_nattch; // 当前附加的进程数
    // 其他字段...
};


2. 修改共享内存权限( IPC_SET )
 
- 作用:将 buf 中 shm_perm 字段的权限信息(如 mode )应用到共享内存段。
- 示例:

 

buf->shm_perm.mode = 0660;  // 设置读写权限为所有者和组可读写
shmctl(shmid, IPC_SET, buf);


3. 删除共享内存段( IPC_RMID )
 
- 作用:标记共享内存段为“待删除”,当所有进程分离( shm_nattch 为0)时,内核释放其物理内存。
- 注意:
- 已附加的进程仍可访问,直到调用 shmdt 分离。
- 可通过 ipcs -m 查看状态为 dest 的共享内存段。
 
三、返回值与错误处理
 
- 成功:返回 0 。
- 失败:返回 -1 ,常见 errno :
-  EINVAL : shmid 无效,或 cmd 不支持。
-  EPERM :无权限执行操作(如非所有者或root用户修改权限)。
 
四、使用示例(C语言)
 
1. 获取共享内存信息并删除
 

#include <stdio.h>
#include <sys/shm.h>

int main() {
    int shmid;
    struct shmid_ds buf;
    
    // 1. 获取已存在的共享内存段
    shmid = shmget(0x1234, 0, 0666);
    if (shmid == -1) {
        perror("shmget failed");
        return 1;
    }
    
    // 2. 获取共享内存状态
    if (shmctl(shmid, IPC_STAT, &buf) == -1) {
        perror("shmctl IPC_STAT failed");
        return 1;
    }
    printf("Shared memory size: %zu bytes\n", buf.shm_segsz);
    printf("Attached processes: %hu\n", buf.shm_nattch);
    
    // 3. 删除共享内存段(标记为待删除)
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID failed");
        return 1;
    }
    printf("Shared memory marked for deletion.\n");
    
    return 0;
}


 
 2. 修改共享内存权限
 

struct shmid_ds buf;
// 先通过IPC_STAT获取当前属性
shmctl(shmid, IPC_STAT, &buf);
// 修改权限为所有者读写,其他用户只读
buf.shm_perm.mode = 0644;
shmctl(shmid, IPC_SET, &buf);

五、与其他系统调用的配合
 
graph TD
A[shmget创建] --> B[shmat附加]
B --> C[进程读写]
C --> D{是否需要管理?}
D -- 是 --> E[shmctl修改/删除]
D -- 否 --> F[shmdt分离]
E --> F
F --> G{是否删除?}
G -- 是 --> H[shmctl IPC_RMID]

 
 
六、注意事项
 
1. 删除的延迟性:
- 调用 IPC_RMID 后,共享内存段仍存在,直到所有进程分离。若需立即删除,需确保所有进程已调用 shmdt 。
2. 权限与所有权:
- 仅共享内存所有者、创建者或root用户可执行 IPC_SET 和 IPC_RMID 操作,否则会因 EPERM 错误失败。
3. 系统资源查看:
- 可通过命令行工具 ipcs -m 查看共享内存段列表, ipcrm -m shmid 等价于 shmctl(shmid, IPC_RMID, NULL) 。

 
七、总结
 
 shmctl 是共享内存管理的核心接口,负责完成从属性查询到资源释放的全生命周期操作。合理使用 IPC_STAT 、 IPC_SET 和 IPC_RMID ,可确保多进程环境下共享内存的安全与高效使用。


网站公告

今日签到

点亮在社区的每一天
去签到