linux 信号量semget,信号量

信号量实现的是课堂上“操作系统原理”里提到的经典PV操作

即:

点击(此处)折叠或打开

semaphore s = 1;

...

P(s);

critical code (临界区)

V(s);

...

信号量可以近似理解为博物馆里的互动体验区:

体验区有一个初始的机器数;

每来一个人,机器数就减少1;

每离开一个人,机器数就加1;

当机器数降到0时,新来的人就必须等待有人离开才能得到机器;

与互动体验区 不同的是,信号量通常用来实现串行机制,因此初始数量通常是1。这样被信号量保护的一小段代码在同一时间就只能被一个线程执行。

linux里使用信号量需要三个函数

点击(此处)折叠或打开

#include

#include

#include

// 创建信号量

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

// 控制信号量

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

// 实现PV操作

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

创建信号量

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

key:自定义一个整数。这个参数类似open函数的第一个参数,由调用者指定一个“文件名”。

nsems:要初始化多少个信号量,通常设为1。

semflg:这个参数也和open函数类似。支持权限和IPC_CREAT以及IPC_EXCL

IPC_CREAT表示要创建一个信号量,但是如果信号量已经存在了也不会报错。

IPC_EXCL和IPC_CREAT一起使用时,如果信号量已经存在就会报EEXIST。

通常可以将semflg设为IPC_CREAT|IPC_EXCL|0666等

例子

#include

#include

#include

#include

#include

#include

#include

#include

int init_sem(int key, int val)

{

int id = semget((key_t)key, 1, IPC_CREAT|0666|IPC_EXCL);

if(id < 0)

{

if(errno == EEXIST)

{

printf("sem %d exists ... ", key);

if((id = semget((key_t)key, 1, 0)) > 0)

printf("get existed sem %d.\n", key);

}

}

else

{

printf("new sem %d created.\n", key);

}

return id;

}

int main()

{

int pid = getpid();

printf("pid %d started\n", pid);

int semid = init_sem(9999, 1);

printf("sem id %d\n", semid);

}

程序指定key为9999,这个是随意的。

第一次执行时,用“IPC_CREAT|0666|IPC_EXCL ”可以成功,会提示创建成功。

[root@server2 sem]# ./a

pid 5273 started

new sem 9999 created.

sem id 360451

此时用ipcs -s可以看到创建的信号量。即使程序推出了,这个信号量也会留在系统里。

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

0x00000000 0          root       600        1

0x00000000 32769      root       600        1

0x1d00b184 163842     oracle     640        154

0x0000270f 360451     root       666        1

再次执行程序时,由于指定的是相同的key,系统会发现已经存在这个信号量了。

[root@server2 sem]# ./a

pid 5319 started

sem 9999 exists ... get existed sem 9999.

sem id 360451

控制信号量

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

semid:有semget返回的id

semnum:由semid指向的第几个信号量。由于semid是唯一标识,因此semnum通常是0,也就是第1个。

cmd:进行何种操作。支持的操作比较多,常用的有:

GETVAL:设置信号量的值,semctl(sem_id, 0, GETVAL);

IPC_RMID:删除信号量,semctl(sem_id, 0, IPC_RMID);

SETVAL:设置信号量的值,这个需要用到semctl的第四个参数

第四个参数是一个union,根据cmd不同赋不同的值。

要使用第四个参数,需要先自定义这个union

#ifndef _SEMUN_H

#define _SEMUN_H

union semun

{

int val;               // value for SETVAL

struct semid_ds *buf;      // buffer for IPC_STAT & IPC_SET

unsigned short int *array;     // array for GETALL & SETALL

struct seminfo *__buf;     // buffer for IPC_INFO

};

#endif

然后就可以SETVAL了:

union semun sem_union;

sem_union.val = 1;

semctl(sem_id, 0, SETVAL, sem_union);

实现PV操作

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

semid:semget返回的id。

sops:要进行的操作的地址。是个数组,但通常只有一个元素。

nsops:要进行几项操作,通常都是1。

描述操作的结构体:

struct sembuf

{

unsigned short int sem_num;   /* semaphore number */

short int sem_op;     /* semaphore operation */

short int sem_flg;        /* operation flag */

};

其中:

sem_num是这个元素在数组里的下表,只有一个就写0。

sem_op是要给信号量加或减多少。

sem_flg通常设置为SEM_UNDO,这样当系统异常退出,系统可以回退进程对信号量的操作。

简单的PV操作

int sem_p(int id)

{

struct sembuf buf = {0, -1, SEM_UNDO};

return semop(id, &buf, 1);

}

int sem_v(int id)

{

struct sembuf buf = {0, 1, SEM_UNDO};

return semop(id, &buf, 1);

}

完整例子

#include

#include

#include

#include

#include

#include

#include

#include

#ifndef _SEMUN_H

#define _SEMUN_H

union semun

{

int val;               // value for SETVAL

struct semid_ds *buf;      // buffer for IPC_STAT & IPC_SET

unsigned short int *array;     // array for GETALL & SETALL

struct seminfo *__buf;     // buffer for IPC_INFO

};

#endif

int init_sem(int key, int val)

{

int id = semget((key_t)key, 1, IPC_CREAT|0666|IPC_EXCL);

if(id < 0)

{

if(errno == EEXIST)

{

printf("sem exists ... ");

if((id = semget((key_t)key, 1, 0)) > 0)

{

printf("get existed sem %d ", id);

int ret = semctl(id, 0, GETVAL);

printf("with value %d\n", ret);

}

}

}

else

{

printf("new sem %d created ... ", id);

union semun sun;

sun.val = val;

int ret = semctl(id, 0, SETVAL, sun);

if(ret == 0)

{

printf("initial value %d.\n", val);

}

else return ret;

}

return id;

}

int sem_p(int id)

{

struct sembuf buf = {0, -1, SEM_UNDO};

return semop(id, &buf, 1);

}

int sem_v(int id)

{

struct sembuf buf = {0, 1, SEM_UNDO};

return semop(id, &buf, 1);

}

int getsem(int id)

{

return semctl(id, 0, GETVAL);

}

int main()

{

int pid = getpid();

printf("pid %d started\n", pid);

int semid = init_sem(9999, 1);

if(semid < 0)

{

printf("%s\n", strerror(errno));

}

char c = 0;

while(1)

{

if((c = getchar()) == 'q') return 1;

printf("sem value = %d.  P ... ", getsem(semid));

sem_p(semid);

printf("ok, sem value = %d.\n", getsem(semid));

if((c = getchar()) == 'q') return 1;

printf("sem value = %d.  V ... ", getsem(semid));

sem_v(semid);

printf("ok, sem value = %d.\n", getsem(semid));

}

}

使用方法

开两个shell

shell 1

[root@server2 sem]# ./a

pid 5800 started

new sem 458755 created ... initial value 1.

shell 2

[root@server2 sem]# ./a

pid 5803 started

sem exists ... get existed sem 458755 with value 1

shell 1

按回车

sem value = 1.  P ... ok, sem value = 0.

shell 2

按回车,陷入等待

shell 1

再按回车

sem value = 0.  V ... ok, sem value = 0.

shell 2

此时P操作完成,显示

sem value = 0.  P ... ok, sem value = 0.

最后,不管两个进程是什么状态,推出后系统都会回退进程所做的操作。信号量变回1。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值