Linux进程间通讯(信号量)

本文深入讲解了信号量机制,包括其基本概念、P/V原语操作、信号量集的使用及其实现代码示例,帮助读者理解进程间的同步与互斥。
信号量

信号量主要用于进程间的同步与互斥,类似于计数器,用于标记资源个数。
S > 0:S表示可用资源个数。
S == 0:表示无可用资源,无等待进程。
S < 0:|S|表示等待队列中,等待资源进程资源个数。
进程互斥:
由于进程间要求资源共享,而有些资源一次只能被一个进程访问(临界资源),从而进程间竞争使用这种资源,进程间的这种关系称为进程互斥。
程序中涉及到临界资源的代码段称为临界区。
进程同步:
有时两个进程之间必须按照一定时序才能完成特定的任务,比如:想要抽完一根烟,需要A进程点着烟,接下来B进程才能抽烟,只有按照这个次序才能完成抽完一根烟的动作,否则将不能完成这个动作。这就是进程同步。
P、V原语操作:
P、V原语操作是一段不可分割不可中断的程序,用于操作信号量,可避免在操作信号量过程中由于进程切换造成的数值不唯一错误。
同步:P、V操作不在同一个进程中。
互斥:P、V操作在同一个进程中。
信号量结构伪代码:

struct Semaphore{
  int value;//资源个数
  pointer_PCB queue;//等待队列
}

P操作伪代码:

p(s){
  s.value--;
  if(s.value < 0){
    //1、将该进程状态置为等待状态
    //2、将该进程PCB插入s.queue等待队列末尾
  }
}

V操作伪代码:

v(s){
  s.value++;
  if(s.value <= 0){
    //唤醒等待队列s.queue中的一个进程,
    //改变其状态为就绪状态。
  }
}

信号量集结构:

struct semid_ds {
    struct ipc_perm sem_perm;       /* 权限和所有者 */
    __kernel_time_t sem_otime;      /* 最后操作时间 */
    __kernel_time_t sem_ctime;      /* 最后改变时间(模式更改) */
    struct sem  *sem_base;      /* 指向信号量集数组中第一个信号量的指针 */
    struct sem_queue *sem_pending;      /* 指向等待队列中首先处理的进程 */
    struct sem_queue **sem_pending_last;    /* 指向等待队列中最后处理的进程 */
    struct sem_undo *undo;          /* 撤销此数组上的请求 */
    unsigned short  sem_nsems;      /* 信号量集数组中元素的个数 */
};

信号量集函数:
semget()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
  • 说明:该函数用于创建或者带开已有的信号量集。
  • 参数:
    • key:关键字,通常由ftok()产生。
    • nsems:信号量集中信号量的个数。
    • semflg:类似于文件操作的mode模式。
  • 返回值:失败返回-1,并设置errno,成功返回信号量集标识符。

semctl()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);
  • 说明:此函数用于控制信号量集。
  • 参数:
    • semid:信号量标识符,由semget()函数产生。
    • semnum:信号量集中要操作的信号量下标。
    • cmd:将要采取的命令,可能的三个取值如下:
      IPC_STAT:读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
      IPC_SET:设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
      IPC_RMID:将信号量集从内存中删除
    • …:可变参数,依据cmd的不同而不同,通常是union semun的一个实例。
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 */
    void *__pad;
}; 
    • 返回值:成功返回0,失败返回-1,并设置errno。

semop()函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);
  • 说明:操作一个或一组信号量,PV操作会调用此函数。
  • 参数:
    • semid:信号量集标识符。
    • sops:指向存储信号操作结构的数组指针。信号操作结构原型如下:
struct sembuf {
    unsigned short  sem_num;    /* semaphore index in array */
    short       sem_op;     /* semaphore operation */
    short       sem_flg;    /* operation flags */
};
sem_num:信号量集中信号量的下标。
sem_op:信号量一次PV操作现有值加上的值,一般只会用到三个值:
+1:也就是V操作,信号量值+1.
-1:也就是P操作,信号量值-1.
0:如果没有设置IPC_NOWAIT,PV操作会暂时睡眠,直到信号量的值为0,否则,不会睡眠,函数返回EAGAIN。
sem_flg:信号操作标志,可能有两个取值,如下:
IPC_NOWAIT:对信号的操作不能满足时,不阻塞,立即返回,并设置错误信息。
IPC_UNDO:程序结束时,保证信号量的值会被设置成semop()调用前的值,这样做的目的在于防止资源泄露。
    • nsops:信号操作结构的数量,恒大于或等于1。
  • 返回值:成功返回0,失败返回-1,并设置errno。

实例代码:

comm.h:

#ifndef __COMM_H_
#define __COMM_H_

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>


#define PATHNAME "."
#define PROJ_ID 0x666

//用于semctl()的可变参数列表
union semun{
  int val;
  struct semid_ds *buf;
  unsigned short *array;
  struct seminfo *__buf;
};

int CreateSemSet(int nsems);
int initSem(int semid,int nsem,int initVal);
int GetSemSet(int nsems);
int P(int semid,int who);
int V(int semid,int who);
int DestroySemSet(int semid);

#endif //__COMM_H_

comm.c:

#include"comm.h"

//依据flag的不同,创建或者打开信号量集
static int CommSemSet(int nsems,int flags){
  key_t _key = ftok(PATHNAME,PROJ_ID);
  if(_key == -1){
    perror("ftok");
    return -1;
  }
  int semid = semget(_key,nsems,flags);
  if(semid == -1){
    perror("semget");
    return -2;
  }
  return semid;
}

//创建信号量集
int CreateSemSet(int nsems){
  return CommSemSet(nsems,IPC_CREAT|IPC_EXCL|0666);
}
//打开信号量集
int GetSemSet(int nsems){
  return CommSemSet(nsems,IPC_CREAT);
}

//初始化信号量集
int initSem(int semid,int nsem,int initVal){
  union semun _un;
  _un.val = initVal;
  //设置信号量数值
  int ret = semctl(semid,nsem,SETVAL,_un);
  if(ret == -1){
    perror("semctl");
    return -1;
  }
  return 0;
}
//根据op参数的不同,进行P或者V操作
static int CommPV(int semid,int who,int op){
  struct sembuf _sbuf;
  _sbuf.sem_num = who;
  _sbuf.sem_op = op;
  _sbuf.sem_flg = 0;
  int ret = semop(semid,&_sbuf,1);
  if(ret == -1){
    perror("semop");
    return -1;
  }
  return 0;
}

//P操作,申请资源
int P(int semid,int who){
  return CommPV(semid,who,-1);
}
//V操作释放资源
int V(int semid,int who){
  return CommPV(semid,who,1);
}

//删除信号量集
int DestroySemSet(int semid){
  int ret = semctl(semid,0,IPC_RMID);
  if(ret == -1){
    perror("semctl");
    return -1;
  }
  return 0;
}

sem.c:

#include"comm.h"


int main(){
  int semid = CreateSemSet(1);
  initSem(semid,0,1);
  pid_t id = fork();
  if(id < 0){ 
    perror("fork");
    return -1; 
  }else if(id == 0){ 
    //child
    int _semid = GetSemSet(0);
    while(1){
      P(_semid,0);
      printf("A");
      fflush(stdout);
      usleep(123456);
      printf("A ");
      fflush(stdout);
      usleep(456123);
      V(_semid,0);
    }
  }else{
    //father
    while(1){
      P(semid,0);
      printf("B");
      fflush(stdout);
      usleep(345612);
      printf("B ");
      fflush(stdout);
      usleep(561234);
      V(semid,0);
    }
    wait(NULL);
  }
  DestroySemSet(semid);
  return 0;
}

Makefile:

.PHONY:sem
sem:sem.c comm.c
        gcc $^ -o $@

.PHONY:cl
cl:
        rm -f sem

结果演示:
使用信号量的结果:
这里写图片描述

未使用信号量的结果:

这里写图片描述


将P、V操作封装成静态库、动态库:

静态库:
所需文件:

[DELL@MiWiFi-R1CL-srv lib]$ ll
总用量 16
-rw-rw-r--. 1 DELL DELL 1392 52 18:42 comm.c
-rw-rw-r--. 1 DELL DELL  505 52 18:42 comm.h
-rw-rw-r--. 1 DELL DELL  666 52 18:45 lib.c
-rw-rw-r--. 1 DELL DELL  310 52 20:56 Makefile

lib.c:

#include"comm.h"


int main(){
  int semid = CreateSemSet(1);
  initSem(semid,0,1);
  pid_t id = fork();
  if(id < 0){ 
    perror("fork");
    return -1; 
  }else if(id == 0){ 
    //child
    int _semid = GetSemSet(0);
    while(1){
      P(_semid,0);
      printf("A");
      fflush(stdout);
      usleep(123456);
      printf("A ");
      fflush(stdout);
      usleep(456123);
      V(_semid,0);
    }
  }else{
    //father
    while(1){
      P(semid,0);
      printf("B");
      fflush(stdout);
      usleep(345612);
      printf("B ");
      fflush(stdout);
      usleep(561234);
      V(semid,0);
    }
    wait(NULL);
  }
  DestroySemSet(semid);
  return 0;
}

Makefile:

.PHONY:stalib
stalib:
        gcc -c comm.c -o comm1.o
        ar -rc libmystapv.a comm1.o
        gcc lib.c -o stalib -L. -lmystapv
        rm comm1.o libmystapv.a

结果:
这里写图片描述

动态库:
所需文件:若没有贴出的源文件同上。

[DELL@MiWiFi-R1CL-srv lib]$ ll
总用量 16
-rw-rw-r--. 1 DELL DELL 1392 52 18:42 comm.c
-rw-rw-r--. 1 DELL DELL  505 52 18:42 comm.h
-rw-rw-r--. 1 DELL DELL  666 52 18:45 lib.c
-rw-rw-r--. 1 DELL DELL  310 52 20:56 Makefile

Makefile.c:

.PHONY:dynlib
dynlib:
        gcc -fPIC -c comm.c -o comm2.o
        gcc -shared -o libmydynpv.so comm2.o
        gcc lib.c -o dynlib -L. -lmydynpv

结果:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值