进程间通信(IPC,Inter-Process Communication)是指不同进程之间进行信息交换和数据共享的机制。在多进程编程中,进程间通信是非常重要的,它允许不同的进程之间进行协调和合作,从而实现更复杂的任务。
在Linux/Unix操作系统中,提供了多种IPC对象,其中包括共享内存、消息队列、信号量等。这些IPC对象允许进程之间共享数据、发送消息和进行同步操作。接下来,我们将详细介绍共享内存、消息队列和信号灯集这三种常用的进程间通信IPC对象。
ftok 函数用于通过给定的文件名和项目ID生成一个唯一的IPC键值。IPC键值在创建IPC对象时被使用,确保进程能够正确地识别和访问同一个IPC对象。
#include
key_t ftok(const char *pathname, int proj_id);
功能:通过参数给的简单值生成一个复杂值(IPC键值)
返回值:成功返回生成的key,失败返回-1
参数说明:
pathname:文件名 会使用文件的inode节点编号
proj_id:随便一个数值,用于与pathname联合生成IPC键值。
//生成IPC 值
key_t key = ftok(".", 'n');
if(key < 0){
perror("ftok");
return -1;
}
共享内存是一种IPC对象,允许多个进程共享同一块物理内存区域,从而避免了进程间的数据复制,提高了进程间通信的效率。
直接读写内存(内核的空间),需要映射
#include
int shmget(key_t key, size_t size, int shmflg);
在这里插入代码片
功能:打开一个共享内存对象(如果不存在则会先创建再打开)
返回值:成功返回对象ID号,失败返回-1
参数说明:
key:唯一表示某个对象的关键字,ftok()生成,也可以写IPC_PRIVATE,表示创建新的私有共享内存对象。
size:指定共享内存有多大,以字节为单位
shmflg:打开或创建对象的方式IPC_CREAT:对象不存在则创建
IPC_CREAT | 0666
//生成IPC 值
key_t key = ftok(".", 'n');
if(key < 0){
perror("ftok");
return -1;
}
//创建打开共享内存对象
int shmid = shmget(key, 64, IPC_CREAT | 0666);
//shmid = shmget(IPC_PRIVATE, 128, IPC_CREAT | 0666);
if(shmid < 0){
perror("shmget");
return -1;
}
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:把内核空间的地址映射到用户空间来,读写用户空间其实就是读写内核空间
返回值:成功返回映射后的地址,失败返回(void *)-1
参数说明:
shmid:已经打开的对象ID号
shmaddr:映射空间的地址,如果让系统帮忙分配空间就写NULL
shmflg:空间访问方式SHM_RDONLY:只读
0:读写
//映射用户空间
char *shmaddr = (char *)shmat(shmID, NULL, 0);
if(shmaddr == (char *)-1){
perror("shmat");
return -1;
}
int shmdt(const void *shmaddr);
功能:取消已经映射的空间
返回值:成功返回0,失败返回-1
参数说明:
shmaddr:映射后的地址
//取消映射对象
shmdt(shmadder);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:整体操作共享内存对象(获取属性、设置属性、删除对象…)
返回值:成功返回0,失败返回-1
参数说明:
shmid:共享内存ID号
cmd:整体操作指令IPC_STAT:获取属性
IPC_SET:设置属性
IPC_RMID:标记删除对象,只有最后一个连接的进程也标记了删除,才会真正的删除buf:主要用于获取和设置属性,删除对象写NULL
结构体 struct shmid_ds 包含了共享内存的相关属性信息,包括:
struct ipc_perm shm_perm:用于标识共享内存对象的权限信息。
size_t shm_segsz:共享内存段的大小,以字节为单位。
time_t shm_atime:最后一次附加(映射)共享内存的时间。
time_t shm_dtime:最后一次分离共享内存的时间。
time_t shm_ctime:最后一次更改共享内存的时间。
pid_t shm_cpid:创建共享内存的进程ID号。
pid_t shm_lpid:最后一次附加共享内存的进程ID号。
shmatt_t shm_nattch:当前附加(映射)共享内存的进程数量。struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) /
uid_t uid; / Effective UID of owner /
gid_t gid; / Effective GID of owner /
uid_t cuid; / Effective UID of creator /
gid_t cgid; / Effective GID of creator /
unsigned short mode; / Permissions + SHM_DEST and SHM_LOCKED flags /
unsigned short __seq; / Sequence number */
};
//删除对象
shmctl(shmID, IPC_RMID, NULL);
#include
#include
#include
#include
#include
#include
#define SHM_SIZE 1024
int main() {
int shmid;
char *shmaddr;
pid_t pid;
// 创建共享内存对象
shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
if (shmid == -1) {
perror("shmget");
exit(0);
}
// 将共享内存映射到进程的地址空间
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shmat");
exit(0);
}
pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程写入数据到共享内存
sprintf(shmaddr, "Hello from child process!");
exit(EXIT_SUCCESS);
} else {
// 父进程等待子进程退出
wait(NULL);
// 父进程读取共享内存中的数据并打印
printf("Message from child process: %s\n", shmaddr);
}
// 取消映射并删除共享内存对象
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
消息队列是一种IPC对象,允许多个进程之间通过发送消息进行通信。每个消息都有一个特定的类型,接收进程可以选择接收某个特定类型的消息。
各种类型的消息统统放到对象里面,只需要进程收发在意类型的消息
#include
int msgget(key_t key, int msgflg);
功能:打开消息队列对象,如果不存在则创建后再打开
返回值:成功返回消息队列ID号,失败返回-1
参数说明:
key:可以是IPC_PRIVATE也可以是ftok()返回值
msgflg:打开创建方式 (IPC_CREAT | 0777)
key_t key = ftok(".", 'a');
if(key < 0){
perror("ftok");
return -1;
}
//创建打开消息队列
int msgid = msgget(key, IPC_CREAT | 0666);
if(msgid < 0){
perror("msgget");
return -1;
}
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:往消息队列对象中添加一个类型的消息
返回值:成功返回0,失败返回-1
参数说明:
msqid:已经打开的消息队列ID号
msgp:要发送的消息所在的缓冲区struct msgbuf{
long type; // > 0
char text[N];
}msgsz:表示要发送的消息正文大小,以字节为单位
msgflg:发送选项0:阻塞
IPC_NOWAIT:非阻塞
//发送消息msgsnd
signal(SIGCHLD, sig_handler);
msgbuf.mtype = 100;//指定消息类型
char buf[256];
while(1){
fgets(msgbuf.mtext, 256, stdin);
int msg_snd = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
if(msg_snd < 0){
perror("msgsnd");
continue;
}
if(strncasecmp(msgbuf.mtext, "quit", 4) == 0){
msgctl(msgid, IPC_RMID, NULL);
kill(pid, SIGKILL);
}
}
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:从消息队列对象中读取一个在意类型的消息
返回值:成功返回收到的消息正文字节数,失败返回-1
参数说明:
msqid:消息队列ID号
msgp:用于存放消息的结构体struct msgbuf{
long type; // > 0
char text[N];
}msgsz:要收的消息正文大小
msgtyp:在意的消息类型
msgflg:接收方式,接收进程只接收该类型的消息0:阻塞
IPC_NOWAIT:非阻塞
//接收消息msgrcv
msgbuf.mtype = 200;
while(1){
int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
if(ret < 0){
perror("msgrcv");
continue;
}
printf("msgrcv form B:%s", msgbuf.mtext);
if(strncasecmp(msgbuf.mtext, "quit",4) == 0){
//如果接收到的内容是quit时就删除消息队列对象
msgctl(msgid, IPC_RMID, NULL);
exit(1);
}
}
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:整体控制消息队列(获取、设置属性,删除对象…)
返回值:成功返回0,失败返回-1
参数说明:
msqid:要操作的消息队列ID号
cmd:操作指令IPC_STAT
IPC_SET
IPC_RMIDbuf:用于设置或获取属性,删除对象使用NULL
结构体 struct msqid_ds 包含了消息队列的相关属性信息,包括:
struct msqid_ds {
struct ipc_perm msg_perm:用于标识消息队列对象的权限信息。
time_t msg_stime:最后一次发送消息的时间。
time_t msg_rtime:最后一次接收消息的时间。
time_t msg_ctime:最后一次更改消息队列的时间。
unsigned long __msg_cbytes:消息队列中当前消息正文的总字节数。
msgqnum_t msg_qnum:当前消息队列中的消息数量。
msglen_t msg_qbytes:消息队列的最大容量,以字节为单位。
pid_t msg_lspid:最后一次发送消息的进程ID号。
pid_t msg_lrpid:最后一次接收消息的进程ID号。
…
};
msgctl(msgid, IPC_RMID, NULL);
msg_A:
#include
#include
#include
#include
#include
#include
#include
#include
struct msgbuf{
long mtype;
char mtext[256];
}msgbuf;
void sig_handler(int sig){
int status;
wait(&status);
printf("exit = %d\n",(status >> 8) & 0xff);
exit(0);
}
int main(int argc, char *argv[])
{
key_t key = ftok(".", 'a');
if(key < 0){
perror("ftok");
return -1;
}
//创建打开消息队列
int msgid = msgget(key, IPC_CREAT | 0666);
if(msgid < 0){
perror("msgget");
return -1;
}
pid_t pid = fork();
if(pid < 0){
perror("fork");
return -1;
}else if(pid == 0){
//接收消息msgrcv
msgbuf.mtype = 200;
while(1){
int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
if(ret < 0){
perror("msgrcv");
continue;
}
printf("msgrcv form B:%s", msgbuf.mtext);
if(strncasecmp(msgbuf.mtext, "quit",4) == 0){
//如果接收到的内容是quit时就删除消息队列对象
msgctl(msgid, IPC_RMID, NULL);
exit(1);
}
}
}else{
//发送消息msgsnd
signal(SIGCHLD, sig_handler);
msgbuf.mtype = 100;//指定消息类型
char buf[256];
while(1){
fgets(msgbuf.mtext, 256, stdin);
int msg_snd = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
if(msg_snd < 0){
perror("msgsnd");
continue;
}
if(strncasecmp(msgbuf.mtext, "quit", 4) == 0){
msgctl(msgid, IPC_RMID, NULL);
kill(pid, SIGKILL);
}
}
}
return 0;
}
msg_B:
#include
#include
#include
#include
#include
#include
#include
#include
struct msgbuf{
long mtype;
char mtext[256];
}msgbuf;
void sig_handler(int sig){
int status;
wait(&status);
printf("exit = %d\n",(status >> 8) & 0xff);
exit(0);
}
int main(int argc, char *argv[])
{
key_t key = ftok(".", 'a');
if(key < 0){
perror("ftok");
return -1;
}
//创建打开消息队列
int msgid = msgget(key, IPC_CREAT | 0666);
if(msgid < 0){
perror("msgget");
return -1;
}
pid_t pid = fork();
if(pid < 0){
perror("fork");
return -1;
}else if(pid == 0){
//接收消息msgrcv
msgbuf.mtype = 100;
while(1){
int ret = msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);
if(ret < 0){
perror("msgrcv");
continue;
}
printf("msgrcv form A:%s", msgbuf.mtext);
if(strncasecmp(msgbuf.mtext, "quit",4) == 0){
//如果接收到的内容是quit时就删除消息队列对象
msgctl(msgid, IPC_RMID, NULL);
exit(1);
}
}
}else{
//发送消息msgsnd
signal(SIGCHLD, sig_handler);
msgbuf.mtype = 200;//指定消息类型
char buf[256];
while(1){
fgets(msgbuf.mtext, 256, stdin);
int msg_snd = msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);
if(msg_snd < 0){
perror("msgsnd");
continue;
}
if(strncasecmp(msgbuf.mtext, "quit", 4) == 0){
msgctl(msgid, IPC_RMID, NULL);
kill(pid, SIGKILL);
}
}
}
return 0;
}
信号灯集是一种IPC对象,允许多个进程对一组信号灯进行操作。信号灯集主要用于实现进程间的同步和互斥,确保多个进程之间按照特定顺序进行执行。
信号量的集合
#include
int semget(key_t key, int nsems, int semflg);
功能:打开对象,不存在则创建后再打开
返回值:成功返回ID号,失败返回-1
参数说明:
key:ftok()返回值
nsems:信号灯集下面信号灯的种类数
semflg: IPC_CREAT | 0666
//生成IPC值
key_t key = ftok(".", 'a');
if(key < 0){
perror("ftok");
return -1;
}
//创建打开信号量数组对象
int semid = semget(key, 3, IPC_CREAT | 0666);
if(semid < 0){
perror("semget");
return -1;
}
int semctl(int semid, int semnum, int cmd, ...);
功能:整体操作(初始化个数、获取属性、删除对象)
返回值:成功返回0,失败返回-1
参数说明:
semid:对象ID号
semnum:信号灯集下某种信号灯的编号,从0开始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) */
};cmd:操作指令
SETVAL:初始化值
IPC_SET
IPC_STAT
IPC_RMID:删除对象
//初始化信号量数组中的所有信号量
int init_sem(int semid, int* init_vals){
union semun arg;
//myun arg
arg.array = (unsigned short *)init_vals;
return semctl(semid, 0, SETALL, arg);
}
int semop(int semid, struct sembuf *sops, size_t nsops);
功能:PV操作的集合,可以通过参数不同而实现申请释放
返回值:成功返回0,失败返回-1
参数说明:
semid:对象ID号
sops:所有的操作所在的结构体struct sembuf {
unsigned short sem_num; /* 信号灯编号 /
short sem_op; / 信号量操作:-1表示P操作(申请),1表示V操作(释放) /
short sem_flg; / 操作标志:0表示阻塞,IPC_NOWAIT表示非阻塞 */
};nsops:要操作的信号灯个数
//对信号量数组中的某个信号执行P操作
int sem_P(int semid, int sem_num){
struct sembuf sb;
//mysembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1;//P操作,申请
//设置SEM_UNDO标志,确保进程结束时会释放信号量
sb.sem_flg = SEM_UNDO;
semop(semid, &sb, 1);
}
//对信号量数组中的某个信号执行V操作
int sem_V(int semid, int sem_num){
struct sembuf sb;
//mysembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1;//V操作,释放
sb.sem_flg = SEM_UNDO;
return semop(semid, &sb, 1);
}
#include
#include
#include
#include
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
}myun;
#if 0
struct sembuf {
unsigned short sem_num; /* 信号灯编号 */
short sem_op; /* 信号量操作:-1表示P操作(申请),1表示V操作(释放)*/
short sem_flg; /* 操作标志:0表示阻塞,IPC_NOWAIT表示非阻塞 */
}mysembuf;
#endif
//初始化信号量数组中的所有信号量
int init_sem(int semid, int* init_vals);
//对信号量数组中的某个信号执行P操作
int sem_P(int semid, int sem_num);
//对信号量数组中的某个信号执行V操作
int sem_V(int semid, int sem_num);
int main(int argc, char *argv[])
{
//生成IPC值
key_t key = ftok(".", 'a');
if(key < 0){
perror("ftok");
return -1;
}
//创建打开信号量数组对象
int semid = semget(key, 3, IPC_CREAT | 0666);
if(semid < 0){
perror("semget");
return -1;
}
//初始化信号量数组
int init_vals[3] = {1, 0, 1};
int ret = init_sem(semid, init_vals);
if(ret < 0){
perror("semctl");
return -1;
}
//创建子进程
pid_t pid = fork();
if(pid < 0){
perror("fork");
return -1;
}else if(pid == 0){
//子进程执行P操作
sem_P(semid, 0);//申请第一个信号量
printf("子进程申请的资源 1\n");
sleep(3);
//子进程执行V操作
sem_V(semid, 2);//释放第三个信号量
printf("子进程释放的资源 3\n");
}else{
//父进程执行P操作
sem_P(semid, 2);//申请第三个信号量
printf("父进程申请的资源 3\n");
sleep(3);
//子进程执行V操作
sem_V(semid, 0);//释放第三个信号量
printf("父进程释放的资源 1\n");
}
//删除信号量数组对象
int de_sem = semctl(semid, 0, IPC_RMID);
if(de_sem < 0){
perror("semctl");
return -1;
}
return 0;
}
//初始化信号量数组中的所有信号量
int init_sem(int semid, int* init_vals){
union semun arg;
//myun arg
arg.array = (unsigned short *)init_vals;
return semctl(semid, 0, SETALL, arg);
}
//对信号量数组中的某个信号执行P操作
int sem_P(int semid, int sem_num){
struct sembuf sb;
//mysembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1;//P操作,申请
//设置SEM_UNDO标志,确保进程结束时会释放信号量
sb.sem_flg = SEM_UNDO;
semop(semid, &sb, 1);
}
//对信号量数组中的某个信号执行V操作
int sem_V(int semid, int sem_num){
struct sembuf sb;
//mysembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1;//V操作
sb.sem_flg = SEM_UNDO;
return semop(semid, &sb, 1);
}