您的位置:首页 > 运维架构 > Linux

Linux进程间通信之信号量

2017-02-16 21:52 330 查看

信号量

信号量,我们可以理解为一种计数器,记录的是可共享资源的数量。信号量主要被用于协调多个进程对同一共享资源的占用访问。

如果一个进程想要获得并使用共享资源,需经历以下过程:

测试查看控制该共享资源的信号量。

若该信号量的值大于0,则进程可以使用该资源。资源分配给该进程以后,信号量的值减1。表示该资源分配出去到了一个单位。

若该信号量的值等于0,则该进程将被挂起,进入休眠等待状态。直到资源被释放,信号量的值大于0,该进程返回步骤1。(进程被挂起:把控制进程的数据结构PCB链接到信号量集的等待队列中)

当一个共享资源被释放时,控制该资源的信号量将加1。

上述的共享资源我们通常称为临界资源,进程中访问临界资源的代码称为临界区。

现在我们可知,信号量是为了保护临界资源而被设立的(防止多个进程抢占同一临界资源),而信号量本身也算是一种临界资源。信号量又由谁来保护呢?为了解决这一问题,信号量的加1减1操作,均被设计成原子操作来保护自身。(原子操作:要么不做,要么做完做成功)

存在形式

信号量同我们人类一样,是“群居动物”。它并非是单一的存在。而必须定义为一个含有一个或多个信号量的集合,信号量集。当我们创建这个信号量集的时候,需要指定信号量的个数。

信号量集有一个最大缺陷就是,信号量集的创建(semget)与信号量值得初始化(semctl)是相互独立开的。不是原子操作。这样很危险。例如,当我们创建好了一个信号量集,在我们还没有对它进行初始化时,该信号量集就被分配使用,造成无法预料的后果。

同其他PC一样,当占用共享资源的所有进程都执行结束,信号量并不会被程序主动释放掉,所以这种程序总是存在潜在的危险性。

信号量的维护

操作系统内核为每个信号量集合都维护着一个semid_ds结构:

struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集中的每个信号量对应其中一个数组元素 */
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 获取信号量

定义为:

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


参数:

key,为key_t类型的值。System V IPC使用此类型作为系统对它们的唯一标识(名字)。实就为int类型。通常使用ftok函数类获取key值。ftok把一个已经存在的路径名和一个整数标识转换成一个key_t值。

定义:

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


nsems为信号量的个数。

semflg为创建的方式。选项有IPC_CREAT和IPC_EXCL。有以下两种使用方式:

~ IPC_CREAT和IPC_EXCL一起使用。表示创建一个信号量集,若所需的信号量集已存在,则报错退出。

~ IPC_CREAT单独使用。表示创建一个信号量集,若所需的信号量集已存在,则直接获取。

semop,对信号量进行加减操作

定义为:

int semop(int semid,struct sembuf *sops,unsigned nsops)


参数:

semid,指要操作的信号量的id。(即semget的返回值)

sops为一个指针,指向一个由sembuf结构表示的信号量操作数组。(为什么是数组?因为有时我们需要对信号量进行批量操作)

sembuf的结构为:

struct sembuf{
unsigned short sem_num;//表示信号量的“下标”,用来标识信号量集合中的某一信号量
short sem_op;/*当sem_op为正值时,表示进程释放占用的资源数;当sem_op为0时,表示进程希望等待该信号量的值变成0;当semop为负值时,表示进程需要占用的资源数。*/
short sem_flg;//有IPC_NOWAIT,SEM_UNDO,0。
}


注意上述备注sem_flg的SEM_UNDO标识,其用于将修改的信号量的值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常,除0异常,收到KILL信号等)时归还信号量。

例如:假设现在有两个信号量A,B。两个的信号量值都为10。现认为A设置了SEM_UNDO标志,而B没有设置。



假设A,B量信号量所控制的临界资源分别向c,d两个进程分配了2个单位资源。A,B的值此时都变为8。



c,d进程异常结束,按道理说,c,d所占用的临界资源释放,那么A,B两个信号量的值应该都再加上2,返回10。其实不然,我们会发现,设置了SEM_UNDO的信号量A的值返回了10,而没有设置SEM_UNDO的信号量B的值仍为8 。



semctl

既然有信号量的创建申请,那必然就有信号量的销毁。semctl正有此功能。

定义:

int semctl(int semid, int semnum,int cmd,...)


参数:

semid,同上;

semnum,信号量的编号,也可理解为此信号量在所属信号量集中的下标;

cmd,此命令共有10种,常用的有IPC_RMID,SETVAL两种

当cmd为IPC_RMID时,smctl执行的是删除销毁功能。此时可变参数无用。只需考虑前三个参数即可。当cmd为SELVAL时,semctl执行的是初始化的功能,初始化的是信号量的值。此时可变参数类型为semun,它是多个命令特定参数的联合。

union semun{
int val;//信号量的初值
struct semid_ds *buf;
unsigned sgort *array;
}


在调用semctl进行信号量值初始化前,需要先将第四个参数,联合semun内的val进行初始化。

示例:一个简单的信号量操作

头文件

#ifndef __COMM__
#define __COMM__

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

#define PATHNAME "."
#define PROJID 0x6666 //0 ~ 258
#define SIZE 4096*1

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

int initSem(int semid,int which);
int commSem(int nums,int flags);
int creatSem(int nums);
int gerSem(int nums);
int P(int semid,int which);
int V(int semid,int which);
int destorySem(int semid);
#endif // __COMM__


信号量的创建申请

int commSem(int nums,int flags)
{
key_t _k=ftok(PATHNAME,PROJID);
if(_k < 0)
{
perror("ftok");
return -1;
}
int semid = semget(_k,nums,flags);
if(semid < 0)
{
perror("semget");
return -2;
}
return semid;
}
int creatSem(int nums)
{
return commSem(nums,IPC_CREAT | IPC_EXCL | 0666);
}
int gerSem(int nums)
{
return commSem(nums,IPC_CREAT);
}


信号量的初始化

int initSem(int semid,int which)
{
union semun sem;
sem.val = 1;
if(semctl(semid,which,SETVAL,sem) < 0)
{
perror("initSem_semctl");
return -1;
}
return 0;
}


信号量的删除

int destorySem(int semid)
{
if(semctl(semid,0,IPC_RMID) < 0)
{
perror("semctl Destory");
return -1;
}
return 0;
}


信号量的P/V操作

int op_sem(int semid,int op,int which)
{
struct sembuf s;
s.sem_num = which;
s.sem_op = op;
s.sem_flg = SEM_UNDO;

return semop(semid,&s,1);
}

int P(int semid,int which)
{
if(op_sem(semid,-1,which) < 0)
{
perror("P_op_sem");
return -1;
}
return 0;
}
int V(int semid, int which)
{
if(op_sem(semid,1,which) < 0)
{
perror("V_op_sem");
return -1;
}
return 0;
}


测试用例

#include"comm.h"

int main()
{
printf("test_sem\n");
int semid = creatSem(1);
if(semid < 0)
{
return -1;
}
int ret = initSem(semid,0);
if(ret < 0)
{
return -2;
}
P(semid,0);
sleep(15);
printf("nihao \n");
exit(1);
printf("DEBUG:exit\n");
V(semid,0);
sleep(5);

destorySem(semid);

return 0;
}

/*
int main()
{
printf("test_sem\n");
int semid = creatSem(1);
if(semid < 0)
{
return -1;
}
int ret = initSem(semid,0);
if(ret < 0)
{
return -2;
}
pid_t id = fork();
if(id==0)
{
//child
//     printf("child proc,pid:%d,ppid:%d\n",getpid(),getppid());
while(1)
{
P(semid,0);
usleep(13300);
printf("A");
fflush(stdout);
usleep(10000);
printf("A");
fflush(stdout);
V(semid,0);
}
}
else
{
//father
//  printf("father proc,pid:%d,ppid:%d\n",getpid(),getppid());
while(1)
{
P(semid,0);
usleep(1123);
printf("B");
fflush(stdout);
usleep(100);
printf("B");
fflush(stdout);
V(semid,0);
}
}
//  sleep(10);
destorySem(semid);
return 0;
}*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息