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; }*/
相关文章推荐
- linux进程间通信之信号量
- Linux进程间通信(IPC)之三——信号量(Semaphore)
- Linux进程间通信之信号量
- Linux进程间通信(一)——管道、信号量 .
- Linux进程间通信之信号量(semaphore)、消息队列(Message Queue)和共享内存(Share Memory)
- Linux进程间通信之信号量
- linux进程间通信之信号量(semaphore)
- Linux进程间通信之信号量(semaphore)、消息队列(Message Queue)和共享内存(Share Memory)
- Linux环境进程间通信(四):信号量
- Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存
- Linux进程间通信(一)——管道、信号量
- Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存
- Linux--进程间通信(信号量,共享内存)(转)
- 【Linux】进程间通信-信号量详解及编程实例
- Linux进程间通信——信号量
- unix/linux下的共享内存、信号量、队列信息管理(进程间通信)
- 9、linux进程间通信之信号量
- Linux进程间通信之信号量
- Linux进程间通信(四): 信号量
- linux进程间通信之信号量(semaphore)