Linux进程通信-信号量

时间:2021-07-15 15:14:13

一、相关知识

 信号量:一个整数;
  大于或等于0时代表可供并发进程使用的资源实体数;
  小于0时代表正在等待使用临界区的进程数;
  用于互斥的信号量初始值应大于0;
  只能通过P、V原语操作而改变;
 信号量元素组成:
  1、表示信号量元素的值;
  2、最后操作信号量元素的进程ID
  3、等待信号量元素值+1的进程数;
  4、等待信号量元素值为0的进程数;
 
二、主要函数
 
 1.1 创建信号量
 int semget(
  key_t key,  //标识信号量的关键字,有三种方法:1、使用IPC——PRIVATE让系统产生,
     // 2、挑选一个随机数,3、使用ftok从文件路径名中产生
  int nSemes,  //信号量集中元素个数
  int flag  //IPC_CREAT;IPC_EXCL 只有在信号量集不存在时创建
 )
 成功:返回信号量句柄
 失败:返回-1
 
 1.2 使用ftok函数根据文件路径名产生一个关键字
 key_t ftok(const char *pathname,int proj_id);
 路径名称必须有相应权限 
 
 1.3 控制信号量
 int semctl(
  int semid,  //信号量集的句柄
  int semnum,  //信号量集的元素数
  int cmd,  //命令
  /*union senum arg */... //  
 )
 成功:返回相应的值
 失败:返回-1
 
 命令详细说明:
 cmd:   IPC_RMID 删除一个信号量
  IPC_EXCL 只有在信号量集不存在时创建
  IPC_SET 设置信号量的许可权
  SETVAL 设置指定信号量的元素的值为 agc.val
  GETVAL 获得一个指定信号量的值
  GETPID 获得最后操纵此元素的最后进程ID
  GETNCNT 获得等待元素变为1的进程数
  GETZCNT 获得等待元素变为0的进程数
  
 union senum 定义如下:
 union senum{
  int val;
  struct semid_ds *buf;
  unsigned short * array;
 }agc;


 其中 semid_ds 定义如下:
 struct semid_ds{
  struct ipc_pem sem_pem;  //operation pemission struct
  time_t sem_otime;  //last semop()time
  time_t sem_ctime;  //last time changed by semctl()
  struct sem *sembase;  //ptr to first semaphore in array
  struct sem_queue *sem_pending; //pending operations
  struct sem_queue *sem_pending_last; //last pending operations
  struct sem_undo *undo;  //undo requests on this arrary
  unsigned short int sem_nsems; //number of semaphores in set
 };
  
 1.4 对信号量 +1 或 -1 或测试是否为0
 int semop(
  int semid, 
  struct sembuf *sops, //指向元素操作数组
  unsigned short nsops //数组中元素操作的个数
 )
 
 结构 sembuf 定义
 sembuf{
  short int sem_num; //semaphore number
  short int sem_op; //semaphore operaion
  short int sem_flg //operation flag
 };
 
三、例子:
 2.1 服务器

#i nclude <sys/sem.h>
#i nclude <sys/ipc.h>

#define SEGSIZE 1024
#define READTIME 1
union semun {
 int val;
 struct semid_ds *buf;
 unsigned short *array;
} arg;
//生成信号量
int sem_creat(key_t key)
{
 union semun sem;
 int semid;
 sem.val = 0;
 semid = semget(key,1,IPC_CREAT|0666);
 if (-1 == semid){
  printf("create semaphore error\n");
  exit(-1);
 }
 semctl(semid,0,SETVAL,sem);
 return semid;
}
//删除信号量
void del_sem(int semid)
{
 union semun sem;
 sem.val = 0;
 semctl(semid,0,IPC_RMID,sem);
}

//p
int p(int semid)
{
 struct sembuf sops={0,+1,IPC_NOWAIT};
 return (semop(semid,&sops,1));
}
//v
int v(int semid)
{
 struct sembuf sops={0,-1,IPC_NOWAIT};
 return (semop(semid,&sops,1));
}
int main()
{
 key_t key;
 int shmid,semid;
 char *shm;
 char msg[7] = "-data-";
 char i;
 struct semid_ds buf;
 
 key = ftok("/",0);
 shmid = shmget(key,SEGSIZE,IPC_CREAT|0604);
 if (-1 == shmid){
  printf(" create shared memory error\n");
  return -1;
 }
 shm = (char *)shmat(shmid,0,0);
 if (-1 == (int)shm){
  printf(" attach shared memory error\n");
  return -1;
 }
 semid = sem_creat(key);
 for (i = 0;i <= 3;i++){
  sleep(1);
  p(semid);
  sleep(READTIME);
  msg[5] = '0' + i;
  memcpy(shm,msg,sizeof(msg));
  sleep(58);
  v(semid);
 }
 shmdt(shm);
 shmctl(shmid,IPC_RMID,&buf);
 del_sem(semid);
 return 0;
//gcc -o shm shm.c -g
}
 
 2.2 客户端
 
#i nclude <sys/sem.h>
#i nclude <time.h>
#i nclude <sys/ipc.h>

#define SEGSIZE 1024
#define READTIME 1
union semun {
 int val;
 struct semid_ds *buf;
 unsigned short *array;
} arg;

// 打印程序执行时间
void out_time(void)
{
 static long start = 0;
 time_t tm;
 if (0 == start){
  tm = time(NULL);
  start = (long)tm;
  printf(" now start ...\n");
 }
 printf(" second: %ld \n",(long)(time(NULL)) - start);
}

//创建信号量
int new_sem(key_t key)
{
 union semun sem;
 int semid;
 sem.val = 0;
 semid = semget(key,0,0);
 if (-1 ==  semid){
  printf("create semaphore error\n");
  exit(-1);
 }
 return semid;
}

//等待信号量变成0
void wait_v(int semid)
{
 struct sembuf sops={0,0,0};
 semop(semid,&sops,1);
}

int main(void)
{
 key_t key;
 int shmid,semid;
 char *shm;
 char msg[100];
 char i;
 
 key = ftok("/",0);
 shmid = shmget(key,SEGSIZE,0);
 
 if(-1 == shmid){
  printf(" create shared memory error\n");
  return -1;
 }
 shm = (char *)shmat(shmid,0,0);
 if (-1 == (int)shm){
  printf(" attach shared memory error\n");
  return -1;
 }
 semid = new_sem(key);
 for (i = 0;i < 3;i ++){
  sleep(2);
  wait_v(semid);
  printf("Message geted is: %s \n",shm + 1);
  out_time();
 }
 shmdt(shm);
 return 0;
// gcc -o shmc shmC.c -g
}



另一个例子:

题目是:写一个程序,该程序创建两个进程,分别打印"this is the child process"和"father say hello to child",要求交替打印,输出成"this father is say the hello child to process child",每打印一个单词进程阻塞一段时间。将输出打印到当前目录下的tmp文件中。

答:

 
 
  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3. #include <sys/types.h> 
  4. #include <sys/ipc.h> 
  5. #include <sys/sem.h> 
  6. #include <sys/stat.h> 
  7. #include <fcntl.h> 
  8.  
  9. union semun 
  10.     int val; 
  11.     struct semid_ds *buf; 
  12.     unsigned short int *array; 
  13.     struct seminfo *__buf; 
  14. }; 
  15.  
  16. int main(void
  17.     char* buf_child[]={"this""is""the""child""process"}; 
  18.     char* buf_father[]={"father""say""hello""to""child"}; 
  19.     int i = 0, semid, fd; 
  20.     pid_t pid; 
  21.     struct sembuf sb; //信号量操作
  22.     union semun sem; 
  23.     semid = semget(1000, 2, 0666 | IPC_CREAT); //申请信号量组,包含2个信号量
  24.  
  25.     sem.val = 0; 
  26.     semctl(semid, 0, SETVAL, sem); //初始化0号信号量为0
  27.     sem.val = 1; 
  28.     semctl(semid, 1, SETVAL, sem); //初始化1号信号量为1
  29.  
  30.     fd=open("tmp",O_CREAT|O_TRUNC|O_WRONLY,0666); 
  31.  
  32.     pid = fork(); 
  33.     switch (pid) { 
  34.         case -1: 
  35.             perror("fork fail"); 
  36.             break
  37.         case 0: /* child consume */ 
  38.             srand((unsigned int)getpid()); 
  39.             while (i < 5) { 
  40.                 sb.sem_num = 1; //将1号信号量
  41.                 sb.sem_op = -1; //减1
  42.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT; 
  43.                 semop(semid, &sb, 1); 
  44.  
  45.                 write(fd,buf_child[i], strlen(buf_child[i])); 
  46.                 sleep(rand()); 
  47.                 write(fd,&" ", 1); 
  48.                 i++; 
  49.  
  50.                 sb.sem_num = 0; //将0号信号量
  51.                 sb.sem_op = 1;  //加1
  52.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT; 
  53.                 semop(semid, &sb, 1); //操作信号量
  54.             } 
  55.             break
  56.         default:/* parent production  */ 
  57.             srand((unsigned int)getpid()); 
  58.             while (i < 5) { 
  59.                 sb.sem_num = 0; //将0号信号量
  60.                 sb.sem_op = -1; //减1
  61.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT; 
  62.                 semop(semid, &sb, 1); //操作信号量
  63.  
  64.                 write(fd,buf_father[i], strlen(buf_father[i])); 
  65.                 sleep(rand()); 
  66.                 write(fd,&" ", 1); 
  67.                 i++; 
  68.  
  69.                 sb.sem_num = 1; 
  70.                 sb.sem_op = 1; 
  71.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT; 
  72.                 semop(semid, &sb, 1); 
  73.             } 
  74.             break
  75.     } 
  76.     return 0; 
  77. }



posix信号量:

1。POSIX无名信号量     如果你学习过操作系统,那么肯定熟 悉PV操作了.PV操作是原子操作.也就是操作是不可以中断的,在一定的时间内,只能够有一个进程的代码在CPU上面执行.在系统当中,有时候为了顺利的 使用和保护共享资源,大家提出了信号的概念. 假设我们要使用一台打印机,如果在同一时刻有两个进程在向打印机输出,那么最终的结果会是什么呢.为了处理 这种情况,POSIX标准提出了有名信号量和无名信号量的概念,由于Linux只实现了无名信号量,我们在这里就只是介绍无名信号量了. 信号量的使用主 要是用来保护共享资源,使的资源在一个时刻只有一个进程所拥有.为此我们可以使用一个信号灯.当信号灯的值为某个值的时候,就表明此时资源不可以使用.否 则就表>示可以使用. 为了提供效率,系统提供了下面几个函数 
POSIX的无名信号量的函数有以下几个: 

#include

int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem);

sem_init 创建一个信号灯,并初始化其值为value.pshared决定了信号量能否在几个进程间共享.由于目前Linux还没有实现进程间共享信号灯,所以这个 值只能够取0. sem_destroy是用来删除信号灯的.sem_wait调用将阻塞进程,直到信号灯的值大于0.这个函数返回的时候自动的将信号灯 的值的件一.sem_post和sem_wait相反,是将信号灯的内容加一同时发出信号唤醒等待的进程..sem_trywait和sem_wait相 同,不过不阻塞的,当信号灯的值为0的时候返回EAGAIN,表示以后重试.sem_getvalue得到信号灯的值. 
由于Linux不支 持,我们没有办法用源程序解释了. 
这几个函数的使用相当简单的.比如我们有一个程序要向一个系统打印机打印两页.我们首先创建一个信号灯,并 使其初始值为1,表示我们有一个资源可用.然后一个进程调用sem_wait由于这个时候信号灯的值为1,所以这个函数返回,打印机开始打印了,同时信号 灯的值为0 了. 如果第二个进程要打印,调用sem_wait时候,由于信号灯的值为0,资源不可用,于是被阻塞了.当第一个进程打印完成以后,调用 sem_post信号灯的值为1了,这个时候系统通知第二个进程,于是第二个进程的sem_wait返回.第二个进程开始打印了.