本文共 6073 字,大约阅读时间需要 20 分钟。
信号量主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。
进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。 信号量有以下两种类型: 二值信号量: 最简单的信号量形式,信号量的值只能取0或1,类似于互斥锁。 注:二值信号量能够实现互斥锁的功能,但两者的关注内容不同。信号量强调共享资源,只要共享资源可用,其他进程同样可以修改信号量的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。 计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)。 和消息队列一样,通过struct kern_ipc_perm
的指针可以找到相应的信号量,在System V IPC 信号量中,我们的每一个条目为一个信号量,定义在/include/linux/sem.h
: struct sem_array { struct kern_ipc_perm ____cacheline_aligned_in_smp sem_perm; /* permissions .. see ipc.h */ time_t sem_otime; /* last semop time */ time_t sem_ctime; /* last change time */ struct sem *sem_base; /* ptr to first semaphore in array */ struct list_head sem_pending; /* pending operations to be processed */ struct list_head list_id; /* undo requests on this array */ int sem_nsems; /* no. of semaphores in array */ int complex_count; /* pending complex operations */};
其中struct sem *sem_base
为信号量列表的头指针。struct sem
是一个简单的数据结构:
struct sem { int semval; /* current value */ int sempid; /* pid of last operation */ struct list_head sem_pending; /* pending single-sop operations */};
它维持一个当前值,最后操作的进程ID以及一个阻塞队列。在用户空间可以通过struct sembuf
对sem中的信号量的值进行改变**(SETVAL操作)** 或者通过通过联合体union semun 对整个信号量进行改变 (IPC_STAT SETVAL等操作) ,两个结构分别如下:
/*semop system calls takes an array of these. */struct sembuf { unsigned short sem_num; /* semaphore index in array */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */};/* arg for semctl system calls. */union semun { int val; /* value for SETVAL */ struct semid_ds __user *buf; /* buffer for IPC_STAT & IPC_SET */ unsigned short __user *array; /* array for GETALL & SETALL */ struct seminfo __user *__buf; /* buffer for IPC_INFO */ void __user *__pad;};
在include/linux/ipc.h
文件中定义相应的操作:
操作 | 含义 |
---|---|
#define SEMOP 1 | 改变信号量的值 |
#define SEMGET 2 | 打开或者创建一个信号量 |
#define SEMCTL 3 | 消息量控制 |
#define SEMTIMEDOP 4 | 好像是内部使用吧,没有仔细去看 |
信号量主要是实现进程间共享资源访问的控制。
对信号量的操作主要有P()和V(),假设sv是一个信号量变量:操作 | 含义 |
---|---|
SEMOP | 改变信号量的值 |
SEMGET | 打开或者创建一个信号量 |
SEMCTL | 信号量控制 |
对信号量的操作流程是:
SEMGET —> SEMOP(-1) —> (操作临界区) —> SEMOP(+1)(SEMCTL删除或者修改信号量参数等)
#include#include #include int semget(key_t key, int nsems, int semflg);
key
是一个键值,唯一标识一个信号灯集,用法与msgget()
中相同。
nsems
指定打开或者新创建的信号灯集中将包含信号量的数目,一般情况下,都是取值为1。
semflg
是一些标志位。参数key和semflg的取值,以及何时打开已有信号灯集或者创建一个新的信号灯集与msgget()
中的对应部分相同,不再祥述。该调用返回与健值key相对应的信号灯集描述字。
返回:成功返回信号灯集描述字
semid
,否则返回-1。
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid
为semget()
返回的信号量描述符
如果nsems
参数不为1,此时semid
指向的是一个信号量集,而不是单独的一个信号量。因此每次对该信号集进行操作时候必须指定需要操作的信号量数目,即nsops
大小。struct sembuf *sops指向的是一个struct sembuf结构体数组,数组大小即为nsops
。
struct sembuf { unsigned short sem_num; /* semaphore index in array */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */};
semnum: 当前需要操作的信号量在信号集中编号,从0开始
sem_flg:IPC_NOWAIT
或SEM_UNDO
,如果设置了SEM_UNDO
标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。 sem_op: PV操作,其值为正,加到现有的信号内含值。通常用于释放所控资源的使用权;值为负,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权;如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0。
int semctl(int semid, int semnum, int cmd, ...);
semnum
为需要控制的信号量在信号集中的编号,如果信号集只有一个元素,该值为0。cmd为控制类型,对于有些操作,需要第四个参数,即为一个union semun联合体,根据cmd不同,使用联合体中不同的字段:
union semun { int val; /* value for SETVAL */ struct semid_ds __user *buf; /* buffer for IPC_STAT & IPC_SET */ unsigned short __user *array; /* array for GETALL & SETALL */ struct seminfo __user *__buf; /* buffer for IPC_INFO */ void __user *__pad;};
cmd | 描述 |
---|---|
SETVAL | 用于把信号量初始化为一个已知的值,用于第一次使用该信号量时,完成信号量值的初始化。此时使用的是union semun 中val字段。 |
IPC_RMID | 用于删除已经不再继续使用的信号量标识符,该操作会解除所有在该信号量上的挂起进程。 |
IPC_STAT/IPC_SET | 和消息队列相似。 |
实现字符成对打印
#include#include #include #include //包含信号量定义的头文件 //联合类型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; }
运行结果
转载地址:http://wnvzi.baihongyu.com/