Linux信号量(进程)

信号量函数由semget、semop、semctl三个函数组成。下面的表格列出了这三个函数的函数原型及具体说明。


1.   semget函数原型

semget(得到一个信号量集标识符或创建一个信号量集对象)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函数说明

得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符

函数原型

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

函数传入值

key

0(IPC_PRIVATE):会建立新信号量集对象

大于0的32位整数:视参数semflg来确定操作,通常要求此值来源于ftok返回的IPC键值

nsems

创建信号量集中信号量的个数,该参数只在创建信号量集时有效

msgflg

0:取信号量集标识符,若不存在则函数会报错

IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错

函数返回值

成功:返回信号量集的标识符

出错:-1,错误原因存于error中

附加说明

上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限

错误代码

EACCESS:没有权限

EEXIST:信号量集已经存在,无法创建

EIDRM:信号量集已经删除

ENOENT:信号量集不存在,同时semflg没有设置IPC_CREAT标志

ENOMEM:没有足够的内存创建新的信号量集

ENOSPC:超出限制

如果用semget创建了一个新的信号量集对象时,则semid_ds结构成员变量的值设置如下:

Ÿ        sem_otime设置为0。

Ÿ        sem_ctime设置为当前时间。

Ÿ        msg_qbytes设成系统的限制值。

Ÿ        sem_nsems设置为nsems参数的数值。

Ÿ        semflg的读写权限写入sem_perm.mode中。

Ÿ        sem_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。


2.   semop函数原型

semop(完成对信号量的P操作或V操作)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函数说明

对信号量集标识符为semid中的一个或多个信号量进行P操作或V操作

函数原型

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

函数传入值

semid:信号量集标识符

sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:

struct sembuf {

    short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/

    short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */

/*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/

  /*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/

    short flag;  /*0 设置信号量的默认操作*/

/*IPC_NOWAIT设置信号量操作不等待*/

/*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/

  };

nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作

函数返回值

成功:返回信号量集的标识符

出错:-1,错误原因存于error中

错误代码

E2BIG:一次对信号量个数的操作超过了系统限制

EACCESS:权限不够

EAGAIN:使用了IPC_NOWAIT,但操作不能继续进行

EFAULT:sops指向的地址无效

EIDRM:信号量集已经删除

EINTR:当睡眠时接收到其他信号

EINVAL:信号量集不存在,或者semid无效

ENOMEM:使用了SEM_UNDO,但无足够的内存创建所需的数据结构

ERANGE:信号量值超出范围

sops为指向sembuf数组,定义所要进行的操作序列。下面是信号量操作举例。

struct sembuf sem_get={0,-1,IPC_NOWAIT}; /*将信号量对象中序号为0的信号量减1*/

struct sembuf sem_get={0,1,IPC_NOWAIT};  /*将信号量对象中序号为0的信号量加1*/

struct sembuf sem_get={0,0,0};           /*进程被阻塞,直到对应的信号量值为0*/

flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。


3.   semctl函数原型

semctl (得到一个信号量集标识符或创建一个信号量集对象)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

函数说明

得到一个信号量集标识符或创建一个信号量集对象并返回信号量集标识符

函数原型

int semctl(int semid, int semnum, int cmd, union semun arg)

函数传入值

semid

信号量集标识符

semnum

信号量集数组上的下标,表示某一个信号量

cmd

见下文表15-4

arg

union semun {

   short val;          /*SETVAL用的值*/

   struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/

   unsigned short* array; /*SETALL、GETALL用的数组值*/

   struct seminfo *buf;   /*为控制IPC_INFO提供的缓存*/

  } arg;

函数返回值

成功:大于或等于0,具体说明请参照表15-4

出错:-1,错误原因存于error中

附加说明

semid_ds结构见上文信号量集内核结构定义

错误代码

EACCESS:权限不够

EFAULT:arg指向的地址无效

EIDRM:信号量集已经删除

EINVAL:信号量集不存在,或者semid无效

EPERM:进程有效用户没有cmd的权限

ERANGE:信号量值超出范围

 

表15-4 semctl函数cmd形参说明表

命令

解   释

IPC_STAT

从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中

IPC_SET

设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值

IPC_RMID

从内核中删除信号量集合

GETALL

从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中

GETNCNT

返回当前等待资源的进程个数

GETPID

返回最后一个执行系统调用semop()进程的PID

GETVAL

返回信号量集合内单个信号量的值

GETZCNT

返回当前等待100%资源利用的进程个数

SETALL

与GETALL正好相反

SETVAL

用联合体中val成员的值设置信号量集合中单个信号量的值

PV操作伪代码

[cpp] view plain copy
 print?
  1. semaphore sem_id = 1;  
  2.   
  3. loop{  
  4.     P(sem_id);  
  5.    临界区代码;  
  6.     V(sem_id);  
  7.      非临界区代码;  
  8. }  

semun联合结构的定义:

[cpp] view plain copy
 print?
  1. semun是在linux/sem.h中定义的:  
  2.   /*arg for semctl systemcalls.*/  
  3.   union semun{  
  4.   int val;/*value for SETVAL*/  
  5.   struct semid_ds *buf;/*buffer for IPC_STAT&IPC_SET*/  
  6.   ushort *array;/*array for GETALL&SETALL*/  
  7.   struct seminfo *__buf;/*buffer for IPC_INFO*/  
  8.   void *__pad; 

4. 信号量应用程序举例

sem.c源代码如下:

#include <stdio.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

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 */

        };

/***对信号量数组semnum编号的信号量做P操作***/

int P(int semid, int semnum)

{

        struct sembuf sops={semnum,-1, SEM_UNDO};

        return (semop(semid,&sops,1));

}

/***对信号量数组semnum编号的信号量做V操作***/

int V(int semid, int semnum)

{

        struct sembuf sops={semnum,+1, SEM_UNDO};

        return (semop(semid,&sops,1));

}

 

int main(int argc, char **argv)

{

        int key ;

        int semid,ret;

        union semun arg;

        struct sembuf semop;

        int flag ;

 

        key = ftok("/tmp", 0x66 ) ;

        if ( key < 0 )

        {

            perror("ftok key error") ;

            return -1 ;

        }

        /***本程序创建了三个信号量,实际使用时只用了一个0号信号量***/

        semid = semget(key,3,IPC_CREAT|0600);

        if (semid == -1)

        {

                perror("create semget error");

                return ;

        }

        if ( argc == 1 )

        {

            arg.val = 1;

            /***对0号信号量设置初始值***/

            ret =semctl(semid,0,SETVAL,arg);

            if (ret < 0 )

            {

                    perror("ctl sem error");

                    semctl(semid,0,IPC_RMID,arg);

                    return -1 ;

            }

        }

        /***取0号信号量的值***/

        ret =semctl(semid,0,GETVAL,arg);

        printf("after semctl setval  sem[0].val =[%d]\n",ret);

        system("date") ;

        printf("P operate begin\n") ;

        flag = P(semid,0)  ;

        if ( flag )

        {

            perror("P operate error") ;

            return -1 ;

        }

        printf("P operate end\n") ;

        ret =semctl(semid,0,GETVAL,arg);

        printf("after P sem[0].val=[%d]\n",ret);

        system("date") ;

        if ( argc == 1 )

        {

            sleep(120) ;

        }

        printf("V operate begin\n") ;

    if (V(semid, 0) < 0)

        {

            perror("V operate error") ;

            return -1 ;

        }

        printf("V operate end\n") ;

        ret =semctl(semid,0,GETVAL,arg);

        printf("after V sem[0].val=%d\n",ret);

        system("date") ;

        if ( argc >1 )

        {

            semctl(semid,0,IPC_RMID,arg);

        }

 

        return 0 ;

}

①    编译 gcc sem.c –o sem。

②    在一窗口执行./sem,执行结果如下:

after semctl setval  sem[0].val =[1]

2011年 01月 11日 星期二 10:08:11 CST

P operate begin

P operate end

after P sem[0].val=[0]

2011年 01月 11日 星期二 10:08:11 CST

V operate begin

V operate end

after V sem[0].val=0

2011年 01月 11日 星期二 10:10:11 CST

③    然后在另一窗口中执行./sem test1,执行结果如下:

after semctl setval  sem[0].val =[0]

2011年 01月 11日 星期二 10:08:36 CST

P operate begin

P operate end

after P sem[0].val=[0]

2011年 01月 11日 星期二 10:10:11 CST

V operate begin

V operate end

after V sem[0].val=1

2011年 01月 11日 星期二 10:10:11 CST


在这里同时访问临界区的是一个程序的两个不同实例,并且使用参数个数的不同来进行区别。其中一个需要完成信号量的创建及其删除的额外操作。


两个程序在进入临界区和离开临界区的时候分别都会输出两个不同的字符,以此来进行区分。可以发现,两个不同的字符是成对出现的。因为同一时刻只有一个进程可以进入临界区。


完整代码:

[cpp] view plain copy
 print?
  1. #include <unistd.h>  
  2. #include <stdlib.h>  
  3. #include <stdio.h>  
  4.   
  5. #include <sys/sem.h>//包含信号量定义的头文件  
  6.   
  7. //联合类型semun定义  
  8. union semun{  
  9.     int val;  
  10.     struct semid_ds *buf;  
  11.     unsigned short *array;  
  12. };  
  13.   
  14. //函数声明  
  15. //函数:设置信号量的值  
  16. static int set_semvalue(void);  
  17. //函数:删除信号量  
  18. static void del_semvalue(void);  
  19. //函数:信号量P操作  
  20. static int semaphore_p(void);  
  21. //函数:信号量V操作  
  22. static int semaphore_v(void);  
  23.   
  24. static int sem_id;//信号量ID  
  25.   
  26.   
  27. int main(int argc,char *argv[])  
  28. {  
  29.     int i;  
  30.     int pause_time;  
  31.     char op_char = 'O';  
  32.   
  33.     srand((unsigned int)getpid());  
  34.   
  35.     //创建一个新的信号量或者是取得一个已有信号量的键  
  36.     sem_id = semget((key_t)1234,1,0666 | IPC_CREAT);  
  37.   
  38.     //如果参数数量大于1,则这个程序负责创建信号和删除信号量  
  39.     if(argc > 1)  
  40.     {  
  41.         if(!set_semvalue())  
  42.         {  
  43.             fprintf(stderr,"failed to initialize semaphore\n");  
  44.         exit(EXIT_FAILURE);  
  45.         }  
  46.   
  47.         op_char = 'X';//对进程进行标记  
  48.         sleep(5);  
  49.     }  
  50.   
  51.     //循环:访问临界区  
  52.     for(i = 0;i < 10;++i)  
  53.     {  
  54.         //P操作,尝试进入缓冲区  
  55.         if(!semaphore_p())  
  56.         exit(EXIT_FAILURE);  
  57.         printf("%c",op_char);  
  58.         fflush(stdout);//刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上  
  59.   
  60.         pause_time = rand() % 3;  
  61.         sleep(pause_time);  
  62.   
  63.         printf("%c",op_char);  
  64.         fflush(stdout);  
  65.   
  66.         //V操作,尝试离开缓冲区  
  67.         if(!semaphore_v())  
  68.         exit(EXIT_FAILURE);  
  69.         pause_time = rand() % 2;  
  70.         sleep(pause_time);  
  71.     }  
  72.   
  73.     printf("\n %d - finished \n",getpid());  
  74.   
  75.     if(argc > 1)  
  76.     {  
  77.         sleep(10);  
  78.         del_semvalue();//删除信号量  
  79.     }  
  80. }  
  81.   
  82.   
  83. //函数:设置信号量的值  
  84. static int set_semvalue(void)  
  85. {  
  86.     union semun sem_union;  
  87.     sem_union.val = 1;  
  88.   
  89.     if(semctl(sem_id,0,SETVAL,sem_union))  
  90.         return 0;  
  91.   
  92.     return 1;  
  93. }  
  94.   
  95. //函数:删除信号量  
  96. static void del_semvalue(void)  
  97. {  
  98.     union semun sem_union;  
  99.   
  100.     if(semctl(sem_id,0,IPC_RMID,sem_union))  
  101.         fprintf(stderr,"Failed to delete semaphore\n");  
  102. }  
  103.   
  104. //函数:信号量P操作:对信号量进行减一操作  
  105. static int semaphore_p(void)  
  106. {  
  107.     struct sembuf sem_b;  
  108.   
  109.     sem_b.sem_num = 0;//信号量编号  
  110.     sem_b.sem_op = -1;//P操作   
  111.     sem_b.sem_flg = SEM_UNDO;  
  112.   
  113.     if(semop(sem_id,&sem_b,1) == -1)  
  114.     {  
  115.         fprintf(stderr,"semaphore_p failed\n");  
  116.         return 0;  
  117.     }  
  118.   
  119.     return 1;  
  120. }  
  121.   
  122. //函数:信号量V操作:对信号量进行加一操作  
  123. static int semaphore_v(void)  
  124. {  
  125.     struct sembuf sem_b;  
  126.   
  127.     sem_b.sem_num = 0;//信号量编号  
  128.     sem_b.sem_op = 1;//V操作    
  129.     sem_b.sem_flg = SEM_UNDO;  
  130.   
  131.     if(semop(sem_id,&sem_b,1) == -1)  
  132.     {  
  133.         fprintf(stderr,"semaphore_v failed\n");  
  134.         return 0;  
  135.     }  
  136.   
  137.     return 1;  
  138.   
  139. }  

运行结果:


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值