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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

2 C8 l1 s# d# p8 p9 }, ?6 ?& W实验环境:Linux2.61 L" T0 A+ F* n( D5 g6 G( a% L
& i7 h+ h% M# [% G5 Q% N4 E
最近在操作设备文件的时候,要求使用独占模式使用串口设备,即一个进程用完之后释放该串口,供其他进程使用。该如何实现该需求呢?自然想到了用信号量来实现。信号量是什么呢?2 @4 R: \. w8 _8 @' B
/ X& W7 |3 d( a5 N, X8 c. ?
首先了解一下,信号量机概念是由荷兰科学家Dijkstr引入,值得一提的是,它提出的Dijksrtr算法解决了最短路径问题。
+ X+ o8 z5 x. B  Y      信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。
! @0 f9 d) x- x. s: s# K
2 M5 {, o" [% N9 |0 q" e: Y因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念: 1 ^6 v$ P  Y- m. I) H8 |
     用于等待(wait)的P(信号量变量) ; $ n0 a9 S4 Q+ O6 z
     用于信号(signal)的V(信号量变量) ; 3 W: N( P% v2 z' f4 E3 O* X
6 U9 {1 u) x1 {# @$ m6 e4 M
这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。% G: k3 V( j$ K3 x! W0 ]) H% |

3 W0 E- i: N( q- bP操作 负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。
* }, U" K+ N2 Z* e
  w- E2 o+ X( ^5 e5 W; J+ U( L9 c" s: l
操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;; }6 \5 z9 m1 \
, C6 }/ I/ i2 J0 h! G
/ t1 B2 b9 V5 N; x: v0 n
V操作 负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。
& Z. n  }4 G7 \3 x+ i6 |
& d; j7 l9 e* E  S* o( c操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。
! ~: Q' s5 o5 I$ P. e. ?, X3 A5 a. k7 E+ M5 |8 Q5 R
补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (全部的话是ipcs -a) ;查看共享信息的内存的命令是ipcs [-m|-s|-q]。
, `' d$ A9 J( C0 [
; g& L! n9 X% r7 w+ g( H头文件pv.h
% X7 x) H. f# T; H" ?" d
+ S4 H+ h: s9 z' f- ?# g
  • //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();
    8 Z/ O3 e1 I* T: m5 ]

$ _+ B5 j- a- o( D! _1 H//实现pv.c
( Q; j( j$ x; [
7 i* `/ b  g) h; `' s
  • //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是删除命令
  • }
    9 h* j# g9 I9 v" T; R+ z
       1 M, P7 S& S- S; A
测试程序如下:
; g" m; v* z# Q) b+ ?+ B2 H- O" h
7 {. S5 V) k$ |! P2 w
  • //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);
  • }2 i: h* h& {$ r1 `3 K2 O
   
* I! [- `0 P: N5 s编译命令gcc pv.c testsem.c -o testsem
3 b# b7 o1 Y6 H- M, H
5 e' I( \8 o0 W; J5 p执行之后即可看到效果,有个地方不太明白,就是信号量应该在什么时候释放掉?请各位网友多指教,感激不尽。- k) f5 F+ B( t3 ~' d! U

( y2 W6 O; O/ G7 ?* G* @' t9 n- g1 r$ H" H( b; z8 T' |
; S" z* w: @2 x

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-26 01:26 , Processed in 0.140625 second(s), 23 queries , Gzip On.

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

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

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