关于Linux信号量的理解和探讨(别说看不懂,耐心看完,你会恍然大悟~)

工作环境(蓝色粗体字为特别注意内容)

1,实验环境:Linux2.6

2,参考文献:https://www.cnblogs.com/LZYY/p/3453582.html

最近在操作设备文件的时候,要求使用独占模式使用串口设备,即一个进程用完之后释放该串口,供其他进程使用。该如何实现该需求呢?自然想到了用信号量来实现。信号量是什么呢?

首先了解一下,信号量机概念是由荷兰科学家Dijkstr引入,值得一提的是,它提出的Dijksrtr算法解决了最短路径问题。
      信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况,信号量是一个特殊的变量,并且只有两个操作可以改变其值:等待(wait)与信号(signal)。
因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义了(暂不知这特殊意义是啥),所以原始概念: 
     用于等待(wait)的P(信号量变量) ; 
     用于信号(signal)的V(信号量变量) ; 
这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。
P操作 负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。
操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;
V操作 负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。
操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。 

补充:查看共享信息的内存的命令是ipcs [-m|-s|-q] (全部的话是ipcs -a) ;查看共享信息的内存的命令是ipcs [-m|-s|-q]。

头文件pv.h

//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();

//实现pv.c

//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是删除命令
}

测试程序如下:

//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);
}

编译命令gcc pv.c testsem.c -o testsem

执行之后即可看到效果,有个地方不太明白,就是信号量应该在什么时候释放掉?请各位网友多指教,感激不尽。



猜你喜欢

转载自blog.csdn.net/pang9998/article/details/80833345