linux进程间通信(六)----IPC篇----信号灯概念以及实现进程间通信

本文深入探讨了System V信号灯的概念及其在进程间通信中的应用,对比POSIX信号量,解释了信号灯如何解决死锁问题,并提供了通过信号灯同步共享内存读写的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
在这里插入图片描述

一 why

linux进程间IPC通信方式有三种,分别是共享内存、消息队列,以及信号灯。它
们都存在于内核空间,之前我们已经探讨过共享内存以及消息队列的实现方式:
linux进程间通信(五)----IPC篇----共享内存实现进程间通信
linux进程间通信(六)----消息队列篇
今天这篇文章来谈谈信号灯基本概念,以及实现进程间通信的步骤。

二 what

(1)概念
第一个问题:信号灯和POSX规范中的信号量之间的区别是什么呢?
posix信号量针对于单个信号量 seminit semwait sempost,Posix有名信号量,无名信号量。什么意思呢?就是POSIX规范中的信号量只能作用于一个信号量(当然这个信号量对应的资源可能不止一个),每个信号对应一个P操作和V操作;而System V信号灯是一个集合,它是信号量的集合,它含有多个信号量,它可以对多个信号灯同时进行P/V操作。
第二个问题:为什么需要信号灯,只有POSIX信号量不是足够了吗?
只有POSIX信号量是不够的,考虑这样一个场景,

  1. 线程A中和线程B都需要访问共享资源1和共享资源2,在线程A中会需要先申请共享资源1,然后再申请共享资源2;
  2. 但是在线程B中,会先申请贡献资源2,然后再申请共享资源1。
  3. 当线程A中开始申请共享资源1时,紧接着会申请共享资源2;而此时线程B中开始申请共享资源2时,紧接着会申请共享资源1;
  4. 线程B正在占用着共享资源2,线程A正在占着共享资源1,导致线程B申请不到共享资源1,它就不会释放共享资源2;线程A申请不到共享资源2,它就不会释放共享资源1;这样就造成了死锁。当然这种情况可以这样避免,每次申请完一个共享资源时,申请结束就释放,而不能同时申请多个共享资源,多个申请成功之后,在释放。当然也可以使用今天的信号灯来解决这个问题
    (2)创建或者打开函数
#includde <sys/types.h>
#includde <sys/ipc.h>
#includde <sys/sem.h>
1. 创建或者打开函数
    原型: int semget(key_t key, int nsems, int semflag)
    参数: key 和信号灯集关联的key值
           nsems 信号灯集包含的信号灯数目
           semflag 信号灯集的访问权限
    返回值: 成功,信号灯ID,出错 -1

(3)关闭

原型: int semctl(int semid, int semnum, int cmd, ...union semun arg)
          注意最后一个参数不是地址,可以有,可以没有
    参数: semid 信号灯集id
           semnum 要修改的信号灯集编号,删除操作时,这个值可以设置为任意值
           cmd GETVAL 获取信号灯的值
               SETVAL 设置信号灯的值
               IPC_RMID 删除信号灯
           union semun arg: 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) */
                            };
    返回值: 成功,消息队列ID,出错 -1

(4)P/V操作函数

P/V操作函数 semop
    原型: int semop(int semid, struct sembuf *opsptr, size_t nops)
    参数: semid 信号灯集id
           opsptr struct sembuf{
                      short sem_num;   //要操作信号灯的编号
                      short sem_op;    //0: 等待,直到信号灯的值变为0,1:资源释放,V操作,-1:分配资源,P操作
                      short sem_flg;   //0: IPC_NOWAIT, SEM_UNDO
                  }
           nops 要操作信号灯个数
    返回值: 成功,消息队列ID,出错 -1

三 how

实现下面一个应用程序
父子进程通过system V信号灯同步对共享内存的读写

父进程从键盘输入字符串到共享内存
子进程删除字符串中的空格并打印
父进程输入quit后删除共享内存和信号灯集,程序结束

源代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>

#define N 64
#define READ 0
#define WRITE 1

union semun {
	int val;
        struct semid_ds *buf;
        unsigned short *array;
        struct seminfo *__buf;
};

void init_sem(int semid, int s[], int n)
{
	int i;
	union semun myun;

	for (i = 0; i < n; i++){
		myun.val = s[i];
		semctl(semid, i, SETVAL, myun);
	}
}

void pv(int semid, int num, int op)
{
	struct sembuf buf;

	buf.sem_num = num;
	buf.sem_op = op;
	buf.sem_flg = 0;
	semop(semid, &buf, 1);
}

int main()
{
	int shmid, semid, s[] = {0, 1};
	pid_t pid;
	key_t key;
	char *shmaddr;

	key = ftok(".", 's');
	if (key == -1){
		perror("ftok");
		exit(-1);
	}

	shmid = shmget(key, N, IPC_CREAT|0666);
	if (shmid < 0) {
		perror("shmid");
		exit(-1);
	}

	semid = semget(key, 2, IPC_CREAT|0666);
	if (semid < 0) {
		perror("semget");
		goto __ERROR1;
	}
	init_sem(semid, s, 2);

	shmaddr = shmat(shmid, NULL, 0);
	if (shmaddr == NULL) {
		perror("shmaddr");
		goto __ERROR2;
	}

	pid = fork();
	if(pid < 0) {
		perror("fork");
		goto __ERROR2;
	} else if (pid == 0) {
		char *p, *q;
		while(1) {
			pv(semid, READ, -1);
			p = q = shmaddr;
			while (*q) {
				if (*q != ' ') {
					*p++ = *q;
				}
				q++;
			}
			*p = '\0';
			printf("%s", shmaddr);
			pv(semid, WRITE, 1);
		}
	} else {
		while (1) {
			pv(semid, WRITE, -1);
			printf("input > ");
			fgets(shmaddr, N, stdin);
			if (strcmp(shmaddr, "quit\n") == 0) break;
			pv(semid, READ, 1);
		}
		kill(pid, SIGUSR1);
	}

__ERROR2:
	semctl(semid, 0, IPC_RMID);
__ERROR1:
	shmctl(shmid, IPC_RMID, NULL);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值