您的位置:首页 > 其它

信号量——POSIX 与 System V的接口对比分析

2016-02-20 21:37 162 查看

一 POSIX 标准

#include <semaphore.h>

 

sem_t:信号量的数据结构

 

int sem_init (sem_t *sem, int pshared, unsigned int value)

无名信号量(也称为基于内存的信号量)sem初始化,设置共享选项pshared,并指定一个整数类型的初始值为value。pshared参数控制着信号量的类型。如果
pshared的值是0,就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量。

 

sem_t*sem_open(const char *name,int oflag,mode_t mode,unsigned int value);

有名信号量(返回值)的创建和初始化。出错时返回为SEM_FAILED。

 

intsem_destroy(sem_t *sem);

释放无名信号量。

 

int sem_close(sem_t *sem);

对应有名信号量的关闭,注意不是销毁,是关闭。

 

int sem_unlink(count char*name);

对应有名信号量的销毁,每个信号都有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。

 

int sem_wait(sem_t *sem); 

int sem_trywait(sem_t *sem);

对应信号量的P操作。 sem_wait和sem_trywait的差别是:当所指定信号灯的值已是0时,后者并不将调用线程投入睡眠。相反,他返回一个EAGAIN错误。

 

int sem_post(sem_t *sem);

int sem_getvalue(sem_t *sem,int *valp);

对应信号量的V操作。

 

注意:posix基于内存的信号灯和posix有名信号灯有一些区别,我们必须注意到这些。

1.sem_open不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的。

2.sem_init不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号灯的值。因此,对于一个给定的信号灯,我们必须小心保证只调用一次sem_init。

3.sem_open返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。

4.posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。 posix基于内存的信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。

5.基于内存的信号灯应用于线程很麻烦,而有名信号灯却很方便,基于内存的信号灯比较适合应用于一个进程的多个线程。 

二 System V接口

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

 

sembuf结构体:

struct sembuf {

         shortsem_num;

         shortsem_op;

         shortsem_flg;

};

sem_num是信号量的编号。

sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。

sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO。

 

int semget(key_t key, int nsems, intsemflg);

用来创建和访问一个信号量集,key:信号集的名字,nsems:信号集中信号量的个数,semflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样。成功返回一个非负整数,即该信号集的标识码,失败返回-1。

 

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

信号集的控制操作,包括销毁、设置信号量的值、读出信号量的值。Semid:由semget返回的信号集标识码。Semnum:信号集中信号量的序号。Cmd:将要采取的动作(有三个可取值)。最后一个参数根据命令不同而不同。

 

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

用来修改一个信号集,进行PV操作。关键在于理解sembuf结构体。Semid:是该信号量的标识码,也就是semget函数的返回值。Sops:是个指向一个结构数值的指针。Nsops:信号量的个数。

 

三 总结

         在用法方面,SystemV更突出一个集合的概念,因此,可以同时操作多个信号量,例如需要等待2个信号都满足条件才允许运行临界代码时,使用System V的信号更简易,但这不代表POSIX的信号量不能实现。下面以哲学家就餐问题(需要同时等待左右叉子),分别使用两种方法实现,体会其中的差别。

         System V实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

#ifdef  _SEM_SEMUN_UNDEFINED
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
#endif

#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)

int
wait_1fork(int no,int semid)
{
//int left = no;
//int right = (no + 1) % 5;
struct sembuf sb = {no,-1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}

int
free_1fork(int no,int semid)
{
struct sembuf sb = {no,1,0};
int ret;
ret = semop(semid,&sb,1);
if(ret < 0) {
ERR_EXIT("semop");
}
return ret;
}

//这里表明叉子是一个临界资源
#define DELAY (rand() % 5 + 1)
//相当于P操作
void
wait_for_2fork(int no,int semid)
{
//哲学家左边的刀叉号数
int left = no;
//右边的刀叉
int right = (no + 1) % 5;

//刀叉值是两个
//注意第一个参数是编号
struct sembuf buf[2] = {
{left,-1,0},
{right,-1,0}
};
//信号集中有5个信号量,只是对其中的
//资源sembuf进行操作
semop(semid,buf,2);
}

//相当于V操作
void
free_2fork(int no,int semid)
{
int left = no;
int right = (no + 1) % 5;
struct sembuf buf[2] = {
{left,1,0},
{right,1,0}
};
semop(semid,buf,2);
}

void philosophere(int no,int semid)
{
srand(getpid());
for(;;) {
#if 1
//这里采取的措施是当两把刀叉都可用的时候
//哲学家才能吃饭,这样不相邻的哲学家就可
//吃上饭
printf("%d is thinking\n",no);
sleep(DELAY);
printf("%d is hungry\n",no);
wait_for_2fork(no,semid);//拿到叉子才能吃饭
printf("%d is eating\n",no);
sleep(DELAY);
free_2fork(no,semid);//释放叉子
#else
//这段代码可能会造成死锁
int left = no;
int right = (no + 1) % 5;
printf("%d is thinking\n",no);
sleep(DELAY);
printf("%d is hungry\n",no);
wait_1fork(left,semid);
sleep(DELAY);
wait_1fork(right,semid);
printf("%d is eating\n",no);
sleep(DELAY);
free_2fork(no,semid);
#endif
}
}

int
main(int argc,char *argv[])
{
int semid;
//创建信号量
semid = semget(IPC_PRIVATE,5,IPC_CREAT | 0666);
if(semid < 0) {
ERR_EXIT("semid");
}
union semun su;
su.val = 1;
int i;
for(i = 0;i < 5;++i) {
//注意第二个参数也是索引
semctl(semid,i,SETVAL,su);
}
//创建4个子进程
int num = 0;
pid_t pid;
for(i = 1;i < 5;++i) {
pid = fork();
if(pid < 0) {
ERR_EXIT("fork");
}
if(0 == pid) {
num = i;
break;
}
}
//这里就是哲学家要做的事情
philosophere(num,semid);
return 0;
}

         POSIX实现(关键在于在尝试获得第二把叉子时,使用的是sem_trywait否则会造成死锁)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>

#define PEOPLE_NUM 5
#define THINKING 1
#define HUNGRY 2
#define EATING 3

sem_t chopsticks[PEOPLE_NUM];
pthread_mutex_t mutex;

void *philosopher(void *arg){
int id = (int) arg;

int state = TH
d775
INKING;
int right = (id + 1) % PEOPLE_NUM;
int left = (id + PEOPLE_NUM - 1) % PEOPLE_NUM;
char ptrState[32];

while(1){
switch(state){
case THINKING:
usleep(300);
state = HUNGRY;
strcpy(ptrState,"Thinking before eat");
break;
case HUNGRY:
strcpy(ptrState,"Hungry");
if(sem_wait(&chopsticks[left]) == 0){//阻塞状态
if(sem_trywait(&chopsticks[right]) == 0){//非阻塞
strcpy(ptrState,"I will Eating");
state = EATING;
}else{
state = THINKING;
strcpy(ptrState,"I have not chopsticks");
sem_post(&chopsticks[left]);//释放请求的得到的left筷子
printf("Philosopher right chopsticks is busy,right=%d,thread id is %d\n",right,id);
}
}else{
printf("Philosopher left chopsticks is busy,left=%d,thread id is %d\n",left,id);//这句话由于上面被阻塞永远不会输出
}
break;
case EATING:
printf("Philosopher fetch left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);
sem_post(&chopsticks[left]);
sem_post(&chopsticks[right]);
printf("Philosopher release left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);
usleep(500);
state = THINKING;
strcpy(ptrState,"Thinking after eat");
break;
}
pthread_mutex_lock(&mutex);
printf("Philosopher is %s, thread id is %d\n",ptrState,id);
pthread_mutex_unlock(&mutex);
usleep(1000);
}

pthread_exit((void*)0);
}

int main(){
pthread_t tid[PEOPLE_NUM];
int i;
pthread_mutex_init(&mutex,NULL);
for(i = 0 ; i < PEOPLE_NUM ; i ++){
sem_init(&chopsticks[i],0,1);
}
for(i = 0 ; i < PEOPLE_NUM ; i ++){
pthread_create(&tid[i],NULL,philosopher,(void*)i);
}
for(i = 0 ; i < PEOPLE_NUM ; i ++){
pthread_join(tid[i],NULL);
}
return 0;
}


         另外,也给出使用POSIX 互斥量的方法解决(与POSIX信号量,有相似之处)

 

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
//筷子作为mutex
pthread_mutex_t chopstick[6] ;
void *eat_think(void *arg)
{
char phi = *(char *)arg;
int left,right; //左右筷子的编号
switch (phi){
case 'A':
left = 5;
right = 1;
break;
case 'B':
left = 1;
right = 2;
break;
case 'C':
left = 2;
right = 3;
break;
case 'D':
left = 3;
right = 4;
break;
case 'E':
left = 4;
right = 5;
break;
}

int i;
for(;;){
usleep(3); //思考
pthread_mutex_lock(&chopstick[left]); //拿起左手的筷子
printf("Philosopher %c fetches chopstick %d\n", phi, left);
if (pthread_mutex_trylock(&chopstick[right]) == EBUSY){ //拿起右手的筷子
pthread_mutex_unlock(&chopstick[left]); //如果右边筷子被拿走放下左手的筷子
continue;
}

//  pthread_mutex_lock(&chopstick[right]); //拿起右手的筷子,如果想观察死锁,把上一句if注释掉,再把这一句的注释去掉
printf("Philosopher %c fetches chopstick %d\n", phi, right);
printf("Philosopher %c is eating.\n",phi);
usleep(3); //吃饭
pthread_mutex_unlock(&chopstick[left]); //放下左手的筷子
printf("Philosopher %c release chopstick %d\n", phi, left);
pthread_mutex_unlock(&chopstick[right]); //放下左手的筷子
printf("Philosopher %c release chopstick %d\n", phi, right);

}
}
int main(){
pthread_t A,B,C,D,E; //5个哲学家

int i;
for (i = 0; i < 5; i++)
pthread_mutex_init(&chopstick[i],NULL);
pthread_create(&A,NULL, eat_think, "A");
pthread_create(&B,NULL, eat_think, "B");
pthread_create(&C,NULL, eat_think, "C");
pthread_create(&D,NULL, eat_think, "D");
pthread_create(&E,NULL, eat_think, "E");

pthread_join(A,NULL);
pthread_join(B,NULL);
pthread_join(C,NULL);
pthread_join(D,NULL);
pthread_join(E,NULL);
return 0;
}


参考文献

http://blog.csdn.net/jasenwan88/article/details/7766808
http://blog.csdn.net/acceptedxukai/article/details/8307247 http://www.oschina.net/code/snippet_724028_36857 http://blog.chinaunix.net/uid-26746982-id-3388652.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: