6258 字
31 分钟
进程间通信:XSI【Unix编程】

XSI IPC(X/Open System Interface Inter-Process Communication)是Unix系统中一套传统的进程间通信机制,包括消息队列(Message Queues)信号量(Semaphores)共享内存(Shared Memory) 三种核心组件。这套IPC机制遵循X/Open标准,提供了进程间数据交换和同步的标准化方法,其中消息队列允许进程通过发送和接收消息进行通信,信号量用于进程间的同步和资源访问控制,而共享内存则允许多个进程共享同一块内存区域以实现高效的数据共享。XSI IPC通过系统调用接口(如msgget、semget、shmget等)进行创建和管理。

核心组件功能描述主要系统调用特点
消息队列 (Message Queues)允许进程间通过发送和接收消息进行通信msgget, msgsnd, msgrcv, msgctl- 支持异步通信
- 消息具有类型标识
- 可设置权限和生命周期
信号量 (Semaphores)用于进程间同步和资源访问控制semget, semop, semctl- 提供原子操作
- 支持PV操作
- 可实现互斥和同步
共享内存 (Shared Memory)多个进程共享同一块内存区域shmget, shmat, shmdt, shmctl- 通信效率最高
- 需要额外同步机制
- 可实现大容量数据共享

键值标识符#

“键”,在XSI IPC的上下文中通常指的是IPC对象的唯一标识符。每种IPC机制(如消息队列、信号量、共享内存)都有自己生成或使用的键值,用于标识和访问特定的IPC对象。这些键由ftok函数生成,并用于创建或访问IPC资源。

函数ftok#

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

作用
ftok 用于将路径名和项目标识符转换为 System V IPC 键。生成的键可以用于 msggetsemgetshmget 等系统调用,以便访问消息队列、信号量或共享内存等 System V IPC 对象。

参数

  • const char *pathname:指向一个存在的、可访问的文件路径名。该文件用于生成唯一的键。
  • int proj_id:项目标识符,其最低有效 8 位会被使用(必须非零)。通常使用一个 ASCII 字符作为项目标识符。

返回值

  • 成功时返回生成的 key_t 类型的键值。
  • 失败时返回 -1,并设置 errno 指示错误。

消息队列#

XSI IPC消息队列是一种基于System V接口的进程间通信机制,允许进程通过异步消息传递进行数据交换。与其他IPC机制不同,消息队列支持消息的类型标识,使接收进程能够按需选择性地读取消息。消息队列具有持久性,消息保存在内核中,直到被接收,即使发送进程终止,消息仍然存在。它提供了灵活的通信方式,适用于多进程环境中的复杂消息处理场景,允许进程在无需同步的情况下发送和接收数据。

msgget#

int msgget(key_t key, int msgflg);

作用
msgget 用于获取与 key 参数值关联的 System V 消息队列标识符。

参数

  • key_t key:指定消息队列的键值。如果 key 等于 IPC_PRIVATE 或者不存在具有给定 key 的消息队列,并且在 msgflg 中指定了 IPC_CREAT,则会创建一个新的消息队列。
  • int msgflg:指定创建和访问消息队列的权限
选项说明
IPC_CREAT如果指定的 key 对应的消息队列不存在,则创建一个新的消息队列。
IPC_EXCLIPC_CREAT 一起使用,如果指定的 key 对应的消息队列已存在,则失败。
返回值
  • 成功时返回消息队列标识符(非负整数)。
  • 失败时返回 -1,并设置 errno 指示错误类型。

msgsnd#

int msgsnd(int msqid, const void msgp[.msgsz], size_t msgsz, int msgflg);

作用
msgsnd 用于向 System V 消息队列发送消息。调用进程必须具有对该消息队列的写权限才能发送消息。

参数

  • int msqid:消息队列的标识符。
  • const void *msgp:指向消息结构的指针,该结构通常包含一个长整型消息类型字段 (mtype) 和一个字符数组 (mtext) 用于存放消息数据。
  • size_t msgsz:要发送的消息数据 (mtext) 的大小(以字节为单位)。
  • int msgflg:控制函数行为的标志。如果设置 IPC_NOWAIT,当队列满时函数不会阻塞而是立即返回错误。
struct msgbuf {
long mtype; /* 消息类型,必须大于 0 */
char mtext[1]; /* 消息数据 */
};

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 指示错误类型。

msgrcv#

ssize_t msgrcv(int msqid, void msgp[.msgsz], size_t msgsz, long msgtyp, int msgflg);

作用
msgrcv 用于从 System V 消息队列接收消息。调用进程必须具有对该消息队列的读权限才能接收消息。

参数

  • int msqid:消息队列的标识符。
  • void *msgp:指向用于存储接收消息的缓冲区指针。
  • size_t msgsz:缓冲区中消息数据 (mtext) 部分的最大大小(以字节为单位)。
  • long msgtyp:指定要接收的消息类型。根据其值和 msgflg 的设置有不同的行为:
    • 0:接收队列中的第一条消息。
    • 正数:接收队列中类型为 msgtyp 的第一条消息(除非设置了 MSG_EXCEPT,则接收类型不等于 msgtyp 的第一条消息)。
    • 负数:接收队列中类型小于或等于 abs(msgtyp) 的最小类型的第一个消息。
  • int msgflg:控制函数行为的标志,可以是以下值的按位或组合:
标志说明
IPC_NOWAIT如果没有符合条件的消息,则不阻塞,立即返回错误 ENOMSG
MSG_COPY非破坏性地获取队列中指定位置的消息副本(从 Linux 3.8 开始支持)。
MSG_EXCEPTmsgtyp 大于 0 一起使用,接收第一个与 msgtyp 不同类型的消息。
MSG_NOERROR如果消息文本长度大于 msgsz,则截断消息文本而不是返回错误。

返回值

  • 成功时返回实际复制到 mtext 数组中的字节数。
  • 失败时返回 -1,并设置 errno 指示错误类型。

函数msgctl#

int msgctl(int msqid, int op, struct msqid_ds *buf);

作用
msgctl 用于对指定的 System V 消息队列执行控制操作。操作类型由参数 op 指定。

参数

  • int msqid:消息队列的标识符。
  • int op:要执行的操作类型。
  • struct msqid_ds *buf:指向 msqid_ds 结构的指针,用于某些操作中传递或接收数据。

op 参数选项

op 值说明
IPC_STAT将与 msqid 相关联的内核数据结构中的信息复制到 buf 指向的 msqid_ds 结构中。调用进程必须对消息队列具有读权限。
IPC_SETbuf 指向的 msqid_ds 结构中的某些成员的值写入与此消息队列关联的内核数据结构,并更新其 msg_ctime 成员。需要适当的权限。
IPC_RMID立即删除消息队列,并唤醒所有正在等待的读写进程(返回错误,errno 设置为 EIDRM)。调用进程必须具有适当权限,或其有效用户 ID 必须是消息队列的创建者或所有者。

返回值

  • 对于 IPC_STATIPC_SETIPC_RMID 操作,成功时返回 0。
  • 对于 IPC_INFOMSG_INFO 操作,成功时返回内核内部记录所有消息队列信息的数组中使用的最高条目索引。
  • 对于 MSG_STATMSG_STAT_ANY 操作,成功时返回其索引在 msqid 中指定的队列的标识符。
  • 失败时返回 -1,并设置 errno 指示错误类型。

示例1(简单的消息收发)#

这段代码包含两个主要部分:发送和接收消息的程序。两个程序使用消息队列进行进程间通信。

proto.h

#pragma once
#define KEY_PATH "/etc/passwd"
#define KEYPROJ 'g'
#define NAME_SIZE 128
struct msg_st {
long mtype;
char name[NAME_SIZE];
int math;
int chinese;
};

msg.rcv.c

#include "stdio.h"
#include "stdlib.h"
#include "sys/msg.h"
#include "proto.h"
int main(int argc, char *argv[]) {
struct msg_st rcv_buf;
key_t key;
key = ftok(KEY_PATH, KEYPROJ);
if (key < 0) {
perror("ftok()");
exit(1);
}
int msgid = msgget(key, IPC_CREAT | 0600);
if (msgid < 0) {
perror("msgget()");
exit(1);
}
while (1) {
if (msgrcv(msgid, &rcv_buf, sizeof(rcv_buf) - sizeof(long), 0, 0) < 0) {
perror("msgrcv()");
exit(1);
}
printf("NAME=%s,MATH=%d,CHINESE=%d\n", rcv_buf.name, rcv_buf.math,
rcv_buf.chinese);
}
if (msgctl(msgid, IPC_RMID, NULL) < 0) {
perror("msgctl()");
exit(1);
}
return 0;
}

msg_snd.c

#include "stdio.h"
#include "stdlib.h"
#include "sys/msg.h"
#include "proto.h"
int main(int argc, char *argv[]) {
key_t key;
key = ftok(KEY_PATH, KEYPROJ);
if (key < 0) {
perror("ftok()");
exit(1);
}
int msgid = msgget(key, 0);
if (msgid < 0) {
perror("msgget()");
exit(1);
}
struct msg_st msg = {
.mtype = 1,
.chinese = 10,
.math = 20,
.name = "123",
};
if (msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0) < 0) {
perror("msgsnd()");
exit(1);
}
puts("send ok");
return 0;
}

示例2(myftp)#

个基于 System V 消息队列实现的简单客户端-服务器模型程序,用于通过消息传递机制在进程间传输文件内容。服务器端(server.c)负责接收客户端请求的文件路径,打开并读取文件内容,然后通过消息队列将数据分块发送给客户端;客户端(client.c)则发送文件路径请求,并接收来自服务器的文件数据并输出到标准输出。 server.c

#include "errno.h"
#include "fcntl.h"
#include "proto.h"
#include "stdio.h"
#include "stdlib.h"
#include "sys/msg.h"
#include <signal.h>
#include <unistd.h>
struct server_fsm_st {
int state;
msg_data_t msg_data;
msg_eof_t msg_eof;
msg_path_t msg_path;
int fd;
};
enum {
STATE_IDEL,
STATE_FL_R,
STATE_EOF,
STATE_SND,
};
int msgid;
void signal_handler(int signum) {
if (msgctl(msgid, IPC_RMID, NULL) < 0) {
perror("msgctl()");
exit(1);
}
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGINT, signal_handler);
struct server_fsm_st fsm;
fsm.state = STATE_IDEL;
fsm.msg_data.mtype = MSG_DATA;
fsm.msg_eof.mtype = MSG_EOF;
fsm.msg_path.mtype = MSG_PATH;
key_t key;
key = ftok(KEY_PATH, KEYPROJ);
if (key < 0) {
perror("ftok()");
exit(1);
}
msgid = msgget(key, IPC_CREAT | 0600);
if (msgid < 0) {
perror("msgget()");
exit(1);
}
while (1) {
switch (fsm.state) {
case STATE_IDEL:
printf("IDEL\n");
if (msgrcv(msgid, &fsm.msg_path, sizeof(msg_path_t) - sizeof(long),
MSG_PATH, 0) < 0) {
perror("msgrcv()");
exit(1);
}
fsm.fd = open(fsm.msg_path.pathname, O_RDONLY);
if (fsm.fd < 0) {
msg_error_t err;
err.mtype = MSG_ERROR;
err.err = errno;
if (msgsnd(msgid, &err, sizeof(msg_error_t) - sizeof(long), 0) < 0) {
perror("msgsnd()");
}
break;
}
fsm.state = STATE_FL_R;
break;
case STATE_FL_R:
printf("STATE_FL_R\n");
fsm.msg_data.len = read(fsm.fd, fsm.msg_data.data, DATASIZE);
if (fsm.msg_data.len == 0) {
fsm.state = STATE_EOF;
break;
}
fsm.state = STATE_SND;
break;
case STATE_SND:
printf("STATE_SND\n");
if (msgsnd(msgid, &fsm.msg_data, sizeof(msg_data_t) - sizeof(long), 0) <
0) {
perror("msgsnd()");
}
fsm.state = STATE_FL_R;
break;
case STATE_EOF:
printf("STATE_EOF\n");
if (msgsnd(msgid, &fsm.msg_eof, sizeof(msg_eof_t), 0) < 0) {
perror("msgsnd()");
}
fsm.state = STATE_IDEL;
break;
default:
fprintf(stderr, "error");
exit(1);
break;
}
}
if (msgctl(msgid, IPC_RMID, NULL) < 0) {
perror("msgctl()");
exit(1);
}
return 0;
}

client.c

#include "proto.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/msg.h"
#include "unistd.h"
int main(int argc, char *argv[]) {
key_t key;
key = ftok(KEY_PATH, KEYPROJ);
if (key < 0) {
perror("ftok()");
exit(1);
}
int msgid = msgget(key, 0);
if (msgid < 0) {
perror("msgget()");
exit(1);
}
msg_path_t path;
path.mtype = MSG_PATH;
strcpy(path.pathname, argv[1]);
msgsnd(msgid, &path, sizeof(path) - sizeof(long), 0);
union msg_s2c_un msg;
while (1) {
msgrcv(msgid, &msg, sizeof(union msg_s2c_un) - sizeof(long), 0, 0);
if (msg.mtype == MSG_DATA) {
}
switch (msg.mtype) {
case MSG_DATA:
write(STDOUT_FILENO, msg.datamsg.data, msg.datamsg.len);
break;
case MSG_EOF:
exit(0);
break;
case MSG_ERROR:
printf("errno = %d: %s\n", msg.errno_msg.err,
strerror(msg.errno_msg.err));
exit(1);
break;
default:
fprintf(stderr, "error,know type:%d\n", msg.mtype);
exit(1);
break;
}
}
puts("send ok");
return 0;
}

proto.h

#pragma once
#define KEY_PATH "/etc/passwd"
#define KEYPROJ 'g'
#define NAME_SIZE 128
#define DATASIZE 128
enum { MSG_PATH = 1, MSG_DATA, MSG_EOF, MSG_ERROR };
typedef struct msg_path_st {
long mtype;
char pathname[NAME_SIZE];
} msg_path_t;
typedef struct msg_data_st {
long mtype;
char data[DATASIZE];
int len;
} msg_data_t;
typedef struct msg_eof_st {
long mtype;
} msg_eof_t;
typedef struct msg_error_st {
long mtype;
int err;
} msg_error_t;
union msg_s2c_un {
long mtype;
msg_data_t datamsg;
msg_eof_t eotmsg;
msg_error_t errno_msg;
};

信号量数组#

信号量是一种用于进程间同步的计数器机制,用于对共享资源的访问控制。进程要使用某个共享资源时,需要检查相关信号量:如果信号量值为正,表示资源可用,进程将信号量值减1以表明占用了一个资源单位;如果信号量值为0,进程则会进入休眠状态,等待资源释放。当进程释放资源时,信号量值增加1,并唤醒任何正在等待的进程。
二元信号量是一种常见的信号量形式,其值仅为0或1,用于控制单个资源的访问。信号量的操作必须是原子性的,以确保正确的同步行为,通常由内核实现。这种机制使得多个进程能够有效地共享资源,防止竞争和冲突。

semget#

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

作用
semget 用于获取与 key 参数值关联的 System V 信号量集标识符。

参数

  • key_t key:指定信号量集的键值。如果 key 等于 IPC_PRIVATE 或者不存在具有给定 key 的信号量集,并且在 semflg 中指定了 IPC_CREAT,则会创建一个新的信号量集。使用IPC_PRIVATE,常用于具有亲缘关系的进程通信。
  • int nsems:指定要创建的信号量数量。如果信号量集已经存在,则该参数可以为 0。
  • int semflg:指定创建和访问信号量集的权限。
选项说明
IPC_CREAT如果指定的 key 对应的信号量集不存在,则创建一个新的信号量集。
IPC_EXCLIPC_CREAT 一起使用,如果指定的 key 对应的信号量集已存在,则失败。

返回值

  • 成功时返回信号量集标识符(非负整数)。
  • 失败时返回 -1,并设置 errno 指示错误类型。

semctl#

int semctl(int semid, int semnum, int op, ...);

作用
semctl 用于对System V信号量集执行控制操作。它可以对由 semid 标识的整个信号量集执行操作,也可以对其中第 semnum 个信号量执行操作(信号量从0开始编号)。

参数

  • int semid:信号量集的标识符。
  • int semnum:信号量集中的信号量编号(从0开始)。对于某些操作,此参数会被忽略。
  • int op:要执行的操作。
选项说明
IPC_STAT将与 semid 关联的内核数据结构中的信息复制到 arg.buf 指向的 semid_ds 结构中。调用进程必须对信号量集具有读权限。
IPC_SETarg.buf 指向的 semid_ds 结构中某些成员的值写入与该信号量集关联的内核数据结构,并更新其 sem_ctime 成员。调用进程的有效UID必须与信号量集的所有者或创建者匹配,或者调用者必须具有特权。
IPC_RMID立即删除信号量集,并唤醒所有在 semop 调用中阻塞在该信号量集上的进程(返回错误并将 errno 设置为 EIDRM)。调用进程的有效用户ID必须与信号量集的创建者或所有者匹配,或调用者必须具有特权。
IPC_INFO (Linux特定)返回关于系统范围信号量限制和参数的信息到 arg.__buf 指向的结构中。
SEM_INFO (Linux特定)返回类似于 IPC_INFO 的信息,但包含关于系统资源消耗的信息。
SEM_STAT (Linux特定)返回类似于 IPC_STATsemid_ds 结构。
SEM_STAT_ANY (Linux特定, Linux 4.17起)返回类似于 SEM_STATsemid_ds 结构,但不检查 sem_perm.mode 的读权限。
GETALL将信号量集中所有信号量的 semval(即当前值)返回到 arg.array 中。调用进程必须对信号量集具有读权限。
GETNCNT返回信号量集中第 semnum 个信号量的 semncnt 值(即等待该信号量值增加的进程数)。调用进程必须对信号量集具有读权限。
GETPID返回信号量集中第 semnum 个信号量的 sempid 值(即最后对该信号量执行操作的进程PID)。调用进程必须对信号量集具有读权限。
GETVAL返回信号量集中第 semnum 个信号量的 semval(即信号量值)。调用进程必须对信号量集具有读权限。
GETZCNT返回信号量集中第 semnum 个信号量的 semzcnt 值(即等待该信号量值变为0的进程数)。调用进程必须对信号量集具有读权限。
SETALL使用 arg.array 设置信号量集中所有信号量的 semval 值,并更新与该集关联的 semid_ds 结构的 sem_ctime 成员。调用进程必须对信号量集具有更改(写)权限。
SETVAL将信号量集中第 semnum 个信号量的 semval 值设置为 arg.val,并更新与该集关联的 semid_ds 结构的 sem_ctime 成员。调用进程必须对信号量集具有更改权限。

第四个参数根据 op 的不同而不同,类型为 union semun,调用程序必须按如下方式定义此union:

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 (Linux-specific) */
};

返回值

  • 成功时,semctl() 的返回值取决于 op
    • GETNCNT: 返回 semncnt 的值。
    • GETPID: 返回 sempid 的值。
    • GETVAL: 返回 semval 的值。
    • GETZCNT: 返回 semzcnt 的值。
    • IPC_INFO: 返回内核内部数组中记录所有信号量集信息的最高使用条目的索引。
    • SEM_INFO: 同 IPC_INFO
    • SEM_STAT: 返回 semid 中指定索引的信号量集的标识符。
    • SEM_STAT_ANY: 同 SEM_STAT
    • 所有其他 op 值成功时返回 0。
  • 失败时返回 -1,并设置 errno 指示错误类型。

错误

  • EACCES: 调用进程对信号量集没有所需的权限。
  • EFAULT: arg.bufarg.array 指向的地址不可访问。
  • EIDRM: 信号量集已被删除。
  • EINVAL: opsemid 的值无效。
  • EPERM: 调用进程没有权限执行 IPC_SETIPC_RMID 操作。
  • ERANGE: SETALLSETVAL 操作中设置的 semval 值超出范围。

semop#

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

作用
semop 用于对 System V 信号量集中的选定信号量执行操作。它将对由 semid 指定的信号量集中的信号量执行 nsops 个操作,这些操作由 sops 数组中的 sembuf 结构体定义。

参数

  • int semid:指定信号量集的标识符。
  • struct sembuf *sops:指向 sembuf 结构体数组的指针,每个结构体定义一个对单个信号量的操作。
  • size_t nsops:指定 sops 数组中操作的数量。

sembuf 结构体

sembuf 结构体包含以下成员:

成员类型说明
sem_numunsigned short信号量在信号量集中的编号
sem_opshort信号量操作
sem_flgshort操作标志

sem_flg 选项

选项说明
IPC_NOWAIT如果操作不能立即执行,则 semop 调用不会阻塞,而是立即返回并设置 errnoEAGAIN
SEM_UNDO如果指定了此标志,当进程终止时,该操作将被自动撤销。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 指示错误类型。

示例#

改写文件锁实现对文件并发访问。 代码通过使用XSI信号量机制保护了对共享文件的并发访问,避免多个进程同时修改导致的数据竞争问题。

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#define PROCESS_COUNT 20
#define FILE_PATH "/tmp/out"
#define BUFFER_SIZE 1024
static int semaphore_id;
// P 操作:申请一个资源(减1)
static void lock_semaphore() {
struct sembuf operation;
operation.sem_num = 0;
operation.sem_op = -1; // 请求资源
operation.sem_flg = 0;
while (semop(semaphore_id, &operation, 1) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
else {
perror("semop() - lock");
exit(EXIT_FAILURE);
}
}
}
// V 操作:释放一个资源(加1)
static void unlock_semaphore() {
struct sembuf operation;
operation.sem_num = 0;
operation.sem_op = 1; // 释放资源
operation.sem_flg = 0;
while (semop(semaphore_id, &operation, 1) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
else {
perror("semop() - unlock");
exit(EXIT_FAILURE);
}
}
}
// 子进程执行的任务函数
static void process_task() {
FILE *file = fopen(FILE_PATH, "r+");
if (!file) {
perror("fopen()");
exit(EXIT_FAILURE);
}
char buffer[BUFFER_SIZE];
lock_semaphore(); // 加锁访问共享资源
// 读取文件内容
if (fgets(buffer, sizeof(buffer), file) == NULL) {
fprintf(stderr, "Failed to read from file.\n");
fclose(file);
unlock_semaphore();
exit(EXIT_FAILURE);
}
int number = atoi(buffer);
sleep(1); // 模拟处理时间
// 回到文件开头并写入新值
fseek(file, 0, SEEK_SET);
fprintf(file, "%d\n", number + 1);
fflush(file);
unlock_semaphore(); // 解锁
fclose(file);
}
int main() {
pid_t pid;
// 创建信号量集合,包含一个信号量
semaphore_id = semget(IPC_PRIVATE, 1, 0666);
if (semaphore_id == -1) {
perror("semget()");
exit(EXIT_FAILURE);
}
// 初始化信号量初始值为1(实现互斥锁)
if (semctl(semaphore_id, 0, SETVAL, 1) == -1) {
perror("semctl()");
exit(EXIT_FAILURE);
}
// 创建多个子进程
for (int i = 0; i < PROCESS_COUNT; i++) {
pid = fork();
if (pid < 0) {
perror("fork()");
exit(EXIT_FAILURE);
}
if (pid == 0) {
process_task(); // 子进程执行任务
exit(EXIT_SUCCESS);
}
}
// 父进程等待所有子进程结束
for (int i = 0; i < PROCESS_COUNT; i++) {
wait(NULL);
}
// 删除信号量
semctl(semaphore_id, 0, IPC_RMID);
return 0;
}

共享内存#

shmget#

int shmget(key_t key, size_t size, int shmflg);

作用
shmget 用于获取与 key 参数值关联的 System V 共享内存段标识符。

参数

  • key_t key:指定共享内存段的键值。如果 key 等于 IPC_PRIVATE 或者不存在具有给定 key 的共享内存段,并且在 shmflg 中指定了 IPC_CREAT,则会创建一个新的共享内存段。
  • size_t size:指定要创建的共享内存段的大小,单位为字节。新创建的共享内存段的大小将向上取整为系统页面大小的倍数。
  • int shmflg:指定创建和访问共享内存段的权限。
选项说明
IPC_CREAT创建一个新的共享内存段。如果不使用此标志,则 shmget() 将查找与 key 关联的共享内存段并检查用户是否有权限访问该段。
IPC_EXCLIPC_CREAT 一起使用,以确保此调用创建共享内存段。如果共享内存段已存在,则调用失败。
SHM_HUGETLB使用“大”页面分配共享内存段(自 Linux 2.6 起)。有关更多信息,请参见 Linux 内核源文件 Documentation/admin-guide/mm/hugetlbpage.rst
SHM_HUGE_2MBSHM_HUGETLB 结合使用,选择 2MB 的大页面大小(自 Linux 3.8 起)。
SHM_HUGE_1GBSHM_HUGETLB 结合使用,选择 1GB 的大页面大小(自 Linux 3.8 起)。
SHM_NORESERVE不为此段保留交换空间(自 Linux 2.6.15 起)。当不保留交换空间时,如果物理内存不足,写入时可能会收到 SIGSEGV 信号。

此外,shmflg 的最低有效 9 位指定了授予所有者、组和其他用户的权限。这些位的格式和含义与 openmode 参数相同。系统不使用执行权限。

返回值

  • 成功时返回有效的共享内存标识符(非负整数)。
  • 失败时返回 -1,并设置 errno 指示错误类型。

shmat#

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

作用
shmat 用于将由 shmid 标识的 System V 共享内存段附加到调用进程的地址空间中。

参数

  • int shmid:要附加的共享内存段的标识符。
  • const void *_Nullable shmaddr:指定附加共享内存段的地址。
  • int shmflg:指定附加操作的标志。

shmflg 选项

选项说明
SHM_RND如果 shmaddr 不为 NULL 且指定了此标志,则附加地址为 shmaddr 向下舍入到最近的 SHMLBA 倍数的地址。
SHM_EXEC(Linux 特有; 自 Linux 2.6.9 起) 允许执行段的内容。调用者必须对段具有执行权限。
SHM_RDONLY以只读方式附加段。进程必须对段具有读权限。如果未指定此标志,则段以读写方式附加,进程必须具有读写权限。
SHM_REMAP(Linux 特有) 指定段的映射应替换 shmaddr 开始且持续段大小范围内的任何现有映射。在此情况下,shmaddr 不得为 NULL。

返回值

  • 成功时返回附加的共享内存段的地址。
  • 失败时返回 (void *) -1,并设置 errno 指示错误类型。

shmdt#

int shmdt(const void *shmaddr);

作用
shmdt 用于从调用进程的地址空间中分离位于 shmaddr 指定地址的共享内存段。要分离的段必须当前已使用与 shmaddr 相等的地址附加。

参数

  • const void *shmaddr:指向要分离的共享内存段的地址。

返回值

  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 指示错误类型。

示例#

使用XSI 共享内存来替代上节中存储映射IO函数mmap()实现共享内存。 下面是一个具有亲缘关系的进程共享内容示例。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#define BUFSIZE 1024
#define SHMSIZE 1024
static int shmid;
int main() {
key_t key;
pid_t pid;
shmid = shmget(IPC_PRIVATE, SHMSIZE, IPC_CREAT | 0600);
pid = fork();
if (pid < 0) {
perror("fork()");
exit(1);
}
if (pid == 0) {
char *addr;
addr = shmat(shmid, NULL, 0);
if (addr == (char *)-1) {
perror("shmat()");
exit(1);
}
char str[] = "Hello";
memcpy(addr, str, sizeof(str));
shmdt(addr);
} else {
wait(NULL);
char *addr;
addr = shmat(shmid, NULL, 0);
printf("%s\n", addr);
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);
}
return 0;
}

补充#

命令ipcs#

Terminal window
ipcs [options]

作用
ipcs 命令用来显示 System V 进程间通信(IPC)设施的信息。默认情况下,它会显示所有三种资源的信息:共享内存段、消息队列和信号量数组。

Terminal window
~ ipcs
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x51262404 65574 capacake 600 152 1
------ Semaphore Arrays --------
key semid owner perms nsems
0x51262403 163903 capacake 600 1

命令ipcrm#

Terminal window
ipcrm [options]

作用
ipcrm 命令用于删除一个或多个 System V 进程间通信(IPC)对象。这些对象包括共享内存段、消息队列和信号量数组。要删除一个 IPC 对象,调用进程的有效用户 ID 必须匹配该对象的创建者或所有者,或者调用进程必须具有特权(通常是 root 用户)。或者,使用 -f 选项可以强制删除,即使调用进程不是所有者或创建者(需要特权)。

选项说明
-a, —all [shm] [msg] [sem]删除所有资源。当提供选项参数时,仅对指定的资源类型执行删除操作。
-M, —shmem-key shmkey删除以 shmkey 创建的共享内存段(在最后一次分离后)。
-m, —shmem-id shmid删除由 shmid 标识的共享内存段(在最后一次分离后)。
-Q, —queue-key msgkey删除以 msgkey 创建的消息队列。
-q, —queue-id msgid删除由 msgid 标识的消息队列。
-S, —semaphore-key semkey删除以 semkey 创建的信号量。
-s, —semaphore-id semid删除由 semid 标识的信号量。
-h, —help显示帮助文本并退出。
-V, —version打印版本信息并退出。
进程间通信:XSI【Unix编程】
https://milkfunc.top/posts/unix环境高级编程学习摘要/进程/进程间通信/进程间通信xsi/
作者
CapCake
发布于
2025-08-21
许可协议
CC BY-NC-SA 4.0