找回密码
 注册
关于网站域名变更的通知
查看: 230|回复: 1
打印 上一主题 下一主题

对于Linux信号量的一些理解和探讨(一定要看完哦,肯定有收获)

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-3-4 09:17 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
9 Q/ A( B3 U% Y" w  ~7 E
实验环境:Linux2.6. u/ O0 N2 g: ^3 q* t5 w8 V, F

1 ?9 T; ], w- h4 j$ s9 x最近在操作设备文件的时候,要求使用独占模式使用串口设备,即一个进程用完之后释放该串口,供其他进程使用。该如何实现该需求呢?自然想到了用信号量来实现。信号量是什么呢?/ I4 T9 b: Y. l, n- a
  m5 F7 K) w( Y) k2 w, ^
首先了解一下,信号量机概念是由荷兰科学家Dijkstr引入,值得一提的是,它提出的Dijksrtr算法解决了最短路径问题。! q: ~: w8 g, _3 }8 e3 }0 h- i8 Y
      信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。
9 Y* F$ i! K; S+ o0 E5 L
- G( f$ V. k5 w6 b4 b因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念: , W' C" X' `# ~8 l7 x2 r3 H* E
     用于等待(wait)的P(信号量变量) ; 1 O3 g+ ]+ N6 J& T) L. j1 q) }
     用于信号(signal)的V(信号量变量) ;
9 {8 e( a- F; w7 B8 ?. x$ a3 V" o
这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。
1 X# u$ ^! ^8 g, a) a$ A3 s! A) \
0 \4 a7 u: {+ yP操作 负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。. E* h6 W! F# M7 n8 Y% C: C5 s) T

" C; Q- |5 g9 ^& o9 h5 L+ H( Y& x6 ^+ [" C
操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;
" Q& _! J3 ?' b/ ]0 w* ]3 R5 D
- d$ a% Y: g0 x5 v, I1 ^3 i5 ~' F% p5 T0 I% S. R5 Q9 _0 z/ g
V操作 负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。
& t# f6 V' G: b  N1 f8 A, X! d" G5 E  K+ e
操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。
  q0 q* U  M6 J2 c, j* O& M3 w# Y' _; _
补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (全部的话是ipcs -a) ;查看共享信息的内存的命令是ipcs [-m|-s|-q]。
# z5 L7 ~; b, L' ~6 ^2 ^" e3 o* {
5 N) k7 l- x3 y  F) X; E头文件pv.h
. [# r+ V' e/ P. \& Z  Y8 E6 p* x8 }* u! a
  • //pv.h头文件
  • #include <sys/types.h>
  • #include <sys/ipc.h>
  • #include <sys/sem.h>
  • #include <errno.h>
  • #include <stdlib.h>
  • #define SEMPERM 0600
  • typedef union _semun {
  •   int val;
  •   struct semid_ds *buf;
  •   ushort *array;
  • } semun;
  • int init_sem(key_t semkey);
  • int p(int semid);
  • int v(int semid);
  • int destroysem();
    ( c  ^6 b0 @) d3 Z2 G  u
% I7 Y% _5 s9 C% @
//实现pv.c
: P' ^; E. I3 S; {1 A- k/ f
& g$ I$ C# C4 O( |" h
  • //pv.c  对信号量赋初值,初值固定为1
  • //查看信号量
  • //ipcs -s
  • /*
  • 创建信号量
  • 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 "pv.h"
  • int init_sem(key_t semkey)
  • {
  •    int status=0,semid;                    //信号量标识符semid
  •   if ((semid=semget(semkey,1,SEMPERM|IPC_CREAT|IPC_EXCL))==-1)
  •   {
  •     if (errno==EEXIST)               //EEXIST:信号量集已经存在,无法创建
  •       semid=semget(semkey,1,0);      //创建一个信号量
  •   }
  •   else
  •   {
  •     semun arg;
  •     arg.val=1;                                        //信号量的初值
  •     status=sEMCtl(semid,0,SETVAL,arg);      //设置信号量集中的一个单独的信号量的值。
  •   }
  •   if (semid==-1||status==-1)
  •   {
  •     perror("initsem failed");
  •     return(-1);
  •   }
  •   /*all ok*/
  •   return(semid);
  • }
  • int p(int semid)
  • {
  •   struct sembuf p_buf;
  •   p_buf.sem_num=0;
  •   p_buf.sem_op=-1;        //信号量减1,注意这一行的1前面有个负号
  •   p_buf.sem_flg=SEM_UNDO;
  •   //p_buf = {0,-1,SEM_UNDO};
  •   if (semop(semid, &p_buf, 1)==-1)
  •   {
  •     perror("p(semid)failed");
  •     exit(1);
  •   }
  •   return(0);
  • }
  • int v(int semid)
  • {
  •   struct sembuf v_buf;
  •   v_buf.sem_num=0;
  •   v_buf.sem_op=1;    //信号量加1
  •   v_buf.sem_flg=SEM_UNDO;
  •   if (semop(semid, &v_buf, 1)==-1)
  •   {
  •     perror("v(semid)failed");
  •     exit(1);
  •   }
  •   return(0);
  • }
  • int destroy_sem(int semid){
  • // fprintf(stderr, "Failed to delete semaphore\n");
  • return semctl(semid,0,IPC_RMID);  //删除进程信号量值,IPC_RMID是删除命令
  • }
    ; G0 n6 j/ I; E% U5 i
       7 f6 ]. z! [. K, @0 k
测试程序如下:; z) m! F! _1 D: W3 b: |
# v$ M1 F. s9 j4 o, ?, x, o* b
  • //testsem.c  主程序,使用PV操作实现三个进程的互斥
  • #include "pv.h"
  • void handlesem(key_t skey);
  • int semid;
  • main()
  • {
  •   key_t semkey=0x200;
  •   int i;
  •   for (i=0;i<3;i++)
  •   {
  •     if (fork()==0)           //父进程负责产生3个子进程
  •       handlesem(semkey);  //子进程中才执行handlesem,做完后就exit。
  •   }
  • if (destroy_sem(semid)<0)
  •   {
  •     perror("semctl error");
  •    exit(1);
  •   }
  • }
  • void handlesem(key_t skey)
  • {
  •   int sleep_s=5;
  •   pid_t pid=getpid();
  •   if ((semid=init_sem(skey))<0)
  •     exit(1);
  •   printf("进程 %d 在临界资源区之前 \n",pid);
  •   p(semid);                                      //进程进入临界资源区,信号量减少1
  •   printf("进程 %d 在使用临界资源时,停止%ds \n",pid,sleep_s);
  •   /*in real life do something interesting */
  •   sleep(sleep_s);
  •   printf("进程 %d 退出临界区后 \n",pid);
  •   v(semid);                                //进程退出临界资源区,信号量加1
  •   printf("进程 %d 完全退出\n",pid);
  •   exit(0);
  • }: D% m6 N# L/ T( x2 d( M$ x
   
, y* r1 D4 p9 R8 a" @4 ^编译命令gcc pv.c testsem.c -o testsem
5 U) `+ g( J; O
' n; b$ g4 w; x执行之后即可看到效果,有个地方不太明白,就是信号量应该在什么时候释放掉?请各位网友多指教,感激不尽。0 B0 ~) b* ?  P
( k. O" s$ T, r( t

2 c+ M! T  R: i
  y$ F+ ^3 K; t4 O% z8 R* I

该用户从未签到

2#
发表于 2020-3-4 17:37 | 只看该作者
对于Linux信号量的一些理解和探讨
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-25 17:36 , Processed in 0.156250 second(s), 24 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表