嵌入式Linux并发程序设计,进程间通信方式,System V IPC,信号灯集,创建/打开semget(),初始化semctl(),P/V操作semop(),sembuf结构体定义

1,System V IPC - 信号灯

  1. 信号灯也叫信号量,用于进程/线程同步或互斥的机制
  2. 信号灯的类型
    ·Posix 无名信号灯
    ·Posix有名信号灯
    ·System V 信号灯
  3. 信号灯的含义
    ·计数信号灯(信号灯的值就是他代表的资源的数量,Posix 无名信号灯/Posix有名信号灯,都是计数信号灯)

2,System V IPC - 信号灯特点

  1. System V 信号灯是一个或多个计数信号灯的集合
  2. 可同时操作集合中的多个信号灯
  3. 申请多个资源时避免死锁(信号灯在一个集合里,需要的资源同时申请,都满足时资源才能申请到)

3,System V信号灯使用步骤

  1. 打开/创建信号灯 semget
  2. 信号灯初始化 semctl
  3. P/V操作 semop
  4. 删除信号灯 semctl

3.1,信号灯创建/打开 semget()

#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

  1. 成功时返回信号灯的id,失败时返回-1
  2. key 和信号灯关联的key IPC_PRIVATE 或 ftok
  3. nsems 集合中包含的计数信号灯个数
  4. semflg 标志位 IPC_CREAT|0666 IPC_EXCL(通常和IPC_CREAT一起使用,加上IPC_EXCL,如果信号灯存在,则报错)

信号灯在使用之前必须初始化,且只能初始化一次,通常第一个进程初始化,后面的进程就不能初始化了。

if((semid = semget(key,3,IPC_CREAT|0666|IPC_EXCL)) < 0)
{
	if(errno == EEXIST)
	{
		semid = semget(key,3,IPC_CREAT|0666)
	}
}
else
{
	初始化信号灯集;
}

3.2,信号灯初始化 semctl()

#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …);

  1. 成功时返回0,失败时返回EOF
  2. semid 要操作的信号灯集id
  3. semnum 要操作的集合中的信号灯编号
  4. cmd 执行的操作 SETVAL(设置信号灯值) IPC_RMID(删除整个信号灯集)
  5. union semun 取决于cmd(SETVAL需要用到第四个参数,IPC_RMID不需要第四个参数),该共用体需要用户自己定义

系统中union semun 共用体参考

union semun {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) */
          };

3.2.1,信号灯初始化—示例

要求:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0

union  semun  myun;

myun.val = 2;
if (semctl(semid, 0, SETVAL, myun) < 0) 
{
	perror(“semctl”);     exit(-1);
}
myun.val = 0;
if (semctl(semid, 1, SETVAL, myun) < 0) 
{
	perror(“semctl”);     exit(-1);
}

3.3,信号灯P/V操作 semop()

#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);

  1. 成功时返回0,失败时返回-1
  2. semid 要操作的信号灯集id
  3. sops 描述对信号灯操作的结构体(数组)
  4. nsops 要操作的信号灯的个数(一次操作sops数组里的前nsops个元素)

3.3.1,信号灯操作 sembuf结构体定义

struct  sembuf 
 {
     short  sem_num;
     short  sem_op;
     short  sem_flg;
 };
  1. semnum 信号灯编号
  2. sem_op -1:P操作 1:V操作 (也可以让信号数量增加或减少多个,将“1”换成“n”)
  3. sem_flg 0(阻塞方式) / IPC_NOWAIT
假设当前信号灯集合中有3个信号灯(意味着最多可以同时操作3个信号灯),编号:0,1,2
现在同时对编号为0和2的信号灯进程P操作
struct  sembuf buf[3];//结构体数组的大小应该和信号灯集中信号灯的数目保持一致
buf[0].sem_num = 0;
buf[0].sem_op = -1;
buf[0].sem_flg = 0;
buf[1].sem_num = 2;
buf[1].sem_op = -1;
buf[1].sem_flg = 0;
semop(semid,&buf,2);//2表示依次取出buf中的前两个元素进行操作

4,信号灯集/共享内存—示例

要求:父子进程通过System V信号灯同步对共享内存的读写
·父进程从键盘输入字符串到共享内存
·子进程删除字符串中的空格并打印
·父进程输入quit后删除共享内存和信号灯集,程序结束

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

#define N 64  	//共享内存大小
#define READ 0	//可读缓冲区(个数)信号灯在信号灯集合中的编号为0
#define WRITE 1	//可写缓冲区(个数)信号灯在信号灯集合中的编号为1

union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo * __buf;
};
void init_sem(int semid,int s[],int n);
void sem_pv(int semid,int num,int op);

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

	if((key = ftok(".",'s')) == -1)//生成KEY值
	{
		perror("ftok");
		exit(-1);
	}

	if((shmid = shmget(key,N,IPC_CREAT|0666)) < 0)//申请共享内存
	{
		perror("shmget");
		exit(-1);
	}

	if((semid = semget(key,2,IPC_CREAT|0666)) < 0)//申请信号灯集
	{
		perror("semget");
		goto _error1;//申请失败,释放申请的共享内存,程序退出
	}

	init_sem(semid,s,2);//信号灯集初始化

	if((shmaddr = shmat(shmid,NULL,0)) == (char *)-1)//映射共享内存地址
	{
		perror("shmat");
		goto _error2;//映射失败,释放申请的信号灯集,释放申请的共享内存,程序退出
	}
	
	if((pid = fork()) < 0)//创建子进程
	{
		perror("fork");
		goto _error2;
	}
	else if(pid == 0)
	{
		char *p,*q;
		while(1)
		{
			sem_pv(semid,READ,-1);//对读信号灯进行P操作
			p = q = shmaddr;
			while(*q)
			{
				if(*q != ' ')
				{
					*p++ = *q;
				}
				q++;
			}
			*p = '\0';
			printf("%s",shmaddr);
			sem_pv(semid,WRITE,1);//对写信号灯进行V操作
		}
	}
	else
	{
		while(1)
		{
			sem_pv(semid,WRITE,-1);//对写信号灯进行P操作
			printf("input > ");
			fgets(shmaddr,N,stdin);
			if(strcmp(shmaddr,"quite\n") == 0)break;
			sem_pv(semid,READ,1);//对读信号灯进行V操作
		}
		kill(pid,SIGUSR1);//给子进程发信号,让其结束
	}


_error2:
	semctl(semid,0,IPC_RMID);//释放申请的信号灯集
_error1:
	shmctl(shmid,IPC_RMID,NULL);//释放申请的共享内存

	return 0;
}

void init_sem(int semid,int s[],int n)//信号灯集初始化函数
{
	int i;
	union semun myyun;
	
	for(i=0;i<n;i++)
	{
		myyun.val = s[i];//读信号灯值初始化为0,写信号灯值初始化为1
		semctl(semid,i,SETVAL,myyun);
	}
}

void sem_pv(int semid,int num,int op)//信号灯集P/V操作函数
{
	struct sembuf buf;

	buf.sem_num = num;//信号灯编号
	buf.sem_op = op;
	buf.sem_flg = 0;
	semop(semid,&buf,1);
}

猜你喜欢

转载自blog.csdn.net/m0_37542524/article/details/84034229