Linux进程间通信-信号量机制


看下面一段用进程间通信-信号量的代码,一个程序的两个实例分别访问临界区。

代码如下:

   
  #include <unistd.h>
  
#include <stdlib.h>
#include <stdio.h>

#include <sys/sem.h>//包含信号量定义的头文件

//联合类型semun定义
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
};

//函数声明
//函数:设置信号量的值
static int set_semvalue(void);
//函数:删除信号量
static void del_semvalue(void);
//函数:信号量P操作
static int semaphore_p(void);
//函数:信号量V操作
static int semaphore_v(void);

static int sem_id;//信号量ID

int main(int argc,char *argv[])
{
int i;
int pause_time;
char op_char = 'O';

srand((unsigned int)getpid());

//创建一个新的信号量或者是取得一个已有信号量的键
sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);

//如果参数数量大于1,则这个程序负责创建信号和删除信号量
if(argc > 1)
{
if(!set_semvalue())
{
fprintf(stderr,"failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}

op_char = 'X';//对进程进行标记
sleep(5);
}

//循环:访问临界区
for(i = 0;i < 10;++i)
{
//P操作,尝试进入缓冲区
if(!semaphore_p())
exit(EXIT_FAILURE);
printf("%c",op_char);
fflush(stdout);//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上

pause_time = rand() % 3;
sleep(pause_time);

printf("%c",op_char);
fflush(stdout);

//V操作,尝试离开缓冲区
if(!semaphore_v())
exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}

printf("\n %d - finished \n",getpid());

if(argc > 1)
{
sleep(10);
del_semvalue();//删除信号量
}
}

//函数:设置信号量的值
static int set_semvalue(void)
{
union semun sem_union;
sem_union.val = 1;

if(semctl(sem_id,0,SETVAL,sem_union))
return 0;

return 1;
}

//函数:删除信号量
static void del_semvalue(void)
{
union semun sem_union;

if(semctl(sem_id,0,IPC_RMID,sem_union))
fprintf(stderr,"Failed to delete semaphore\n");
}

//函数:信号量P操作:对信号量进行减一操作
static int semaphore_p(void)
{
struct sembuf sem_b;

sem_b.sem_num = 0;//信号量编号
sem_b.sem_op = -1;//P操作
sem_b.sem_flg = SEM_UNDO;

if(semop(sem_id,&sem_b,1) == -1)
{
fprintf(stderr,"semaphore_p failed\n");
return 0;
}

return 1;
}

//函数:信号量V操作:对信号量进行加一操作
static int semaphore_v(void)
{
struct sembuf sem_b;

sem_b.sem_num = 0;//信号量编号
sem_b.sem_op = 1;//V操作
sem_b.sem_flg = SEM_UNDO;

if(semop(sem_id,&sem_b,1) == -1)
{
fprintf(stderr,"semaphore_v failed\n");
return 0;
}

return 1;

}

执行结果:

请输入图片描述

其中代码开头的stati int变量sem_id是信号量ID。
我想问的是:这个static int变量为什么能够在两个进程之间共享呢?

Linux 操作系统 多线程

时十十十月 11 years, 7 months ago

LZ似乎误会了system V信号量的本质了,信号量本质上是一个内核数据,并不是用户空间的一个变量,跟sem_id是不是static没有关系。

内核中维持一份全局的struct semid_ds数组 ,semid_ds是信号量集合的结构。semget函数返回的信号量标示符(semaphore identify)就是信号量集合在该数组中的下标。你对sem_id的操作就是在操作相应的内核信号量数据结构。(LZ可以在程序中将sem_id的值打印出来,可以发现同一程序的两个实例输出的值是一样的)
struct semid_ds
{
struct ipc_perm sem_perm; //该信号量集合的操作许可权限
struct sem * sem_base ; //该数组的元素是:该集合包含的信号量的结构。
ushort sem_nsems; //sem_base数组的个数
time_t sem_otime; //最后一次成功修改该信号量数组的时间。
time_t sem_ctime; //成功创建的时间
};

struct sem
{
ushort semval; //信号量的当前值
short sempid; //最后一次返回该信号量的进程的id号
ushort semncnt; //等待semval大于当前值的进程的个数
ushort semzcnt; //等待semval变成0的进程的个数
};
信号量内核数据结构

到这里,还有一个问题,semget是如何保证同一程序的两个实例返回的值一样呢?这里就要用到key了。

int semget(key_t key, int nsems, int flag); 返回值是信号量标示符。

key : key_t是int型的,这个是一个整数, 要保证内核唯一性
nsems:该集合包含的信号量的个数。
flag:创建的权限, 可以使一些读写权限与:IPC_CREAT ( | IPC_EXCL )的按位或。

为了便于理解,可以将key看成是一个文件名,将semaphore identify理解成操纵该文件的文件描述符。
平时没有用system V的信号量编程,也不敢说完全理解,如有描述不妥之处,还请大家指出。

参考链接: IPC笔记之System V信号量
参考阅读:UNIX网络编程 卷2:进程间通信

不信上帝信春哥 answered 11 years, 7 months ago

Your Answer