简单应用信号量实现进程同步控制

版权声明:私藏源代码是违反人性的罪恶行为!博客转载无需告知,学无止境。 https://blog.csdn.net/qq_41822235/article/details/83239942

  --------参考文献   W.Richard Stevens, Stephen A.Rago.UNIX环境高级编程[M].北京:人民邮电出版社,2014.6:455-459.

目录

一、问题引出

1.1 情景模拟

1.2 设计方案

1.3 相关知识

二、 代码实现

2.1 头文件semaphore.h

2.2 源文件semaphore.c

2.3 测试文件a.c和b.c


一、问题引出

1.1 情景模拟

某进程向某个txt文件写入"hello",另一进程向该txt文件读出已经写入的"hello"并在屏幕上打印输出。

毋庸置疑,写完了才能读取,读取完了才能写入。这两个进程运行时就需要额外施加同步控制(边读边写会导致不可预料的后果)。在本情景下,该txt文件被看成是资源,而且只有一份。

生活中,比较贴切的例子就是打印店的打印机,假设打印机一共有5台,每台电脑到底使用哪台打印机是未知的,因为电脑对任何一台打印机都没有特殊要求。打印机就被看成是资源,而且有5份。

举这个例子是为了说明,资源不是总是只有1份的,而且申请资源或释放资源时不总是一份份地申请或释放。一切都按照实际需求去做

1.2 设计方案

图1 设计方案

如图1所示,自主实现的函数用蓝色圆角矩形框起来,库函数用橘色椭圆框起来, 我们实现的,实际上更多的是业务处理,并非底层实现。自主实现的函数内部到最后都会去转调库函数(代理)。

1.3 相关知识

创建一个信号量集或者打开一个现有信号量集函数semget

#include<sys/sem.h>

int semget(key_t key, int nsems, int flag);
//返回值:若成功,返回信号量ID;若出错,返回-1。
  • key用于变换标识符。
  • nsems该集合中信号量数。如果是创建新集合(一般在服务器进程中),则必须指定nsems。如果是引用现有集合(一个客户进程),则将nsems指定为0。

信号控制函数semctl包含多种信号量的操作:

#include<sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../*union semun arg*/);
//返回值实际含义是什么由参数cmd所指定,在这里,int不是一个冰冷的数字,而是有丰富含义的。
  • semid是信号量集在内核中的编号;
  • semnum是该集合中某个特定信号量的下标,取值∈[0, nsems-1]
  • cmd是(幻数),代表10种命令的一种(不详细展开到底是哪10种命令);
  • arg是选项参数,这个参数是一个联合体,而非指向联合体的指针。

无名结构:

struct
{
    unsigned short semval;    //资源总数,always >= 0
    pid_t semid;        //上一次操作该信号量的进程ID
    unsigned short semncnt;    //用于进程挂起的情形
    unsigned short semzcnt;    
}

联合体成员如下: 

union semun{
    int val;    //val在参数cmd等于SETVAL时会用到,用于设置成员semnum的semval的值。
    struct semid_ds *buf;    //buf在参数cmd等于IPC_STAT或者IPC_SET时会用到。
    unsigned short *array;    //array在参数cmd等于GETALL或者SETALL时会用到。
};

联合体semun中出现的semid_ds结构体: 

struct semid_ds{
    struct ipc_perm sem_perm;    //权限控制。
    unsigned short sem_nsems;    //集合中信号量的数目。
    time_t sem_otime;    //最后一次调用semop()函数的时间。
    time_t sem_ctime;    //最后一次改变本结构体成员值的时间。
    .
    .
    .
};

自动执行信号量集合上的操作数组semop

#include<sys/sem.h>

int semop(int semid, struct sembuf semoparray[], size_t nops);
//返回值:若成功,返回0;若出错,返回-1。
  • semid是信号量在内核中的编号;
  • semoparray是一个指针(指向一个数组,数组元素类型是struct sembuf);
  • nops是被指针指向的数组元素的个数。
struct sembuf{
    unsigned short sem_num;    //信号量在信号量集合中的下标,取值∈[0,nsems-1]
    short          semop;      //操作(正整数positive、0、负整数negative)
    short          sem_flg;    //命令IPC_NOWAIT、SEM_UNDO
}

二、 代码实现

2.1 头文件semaphore.h

#ifndef __SEM_H
#define __SEM_H

#include<sys/types.h>
#include<sys/sem.h> //semget() semctl semop()                                   
#include<fcntl.h>   

typedef union semun
{
    int val;    //资源数目  
    struct semid_ds *buf;   //for IPC_STAT and IPC_SET
    unsigned short *array;//for GETALL and SETALL
}semun;

int set_init(int key);        //初始化信号量的函数

void set_p(int semid, int semnum);    //申请资源

void set_v(int semid, int semnum);    //释放资源

void sem_del(int semid);        //删除信号量

#endif

2.2 源文件semaphore.c

#include"semaphore.h"                                                           

int sem_init(int key){
    int semid = semget((key_t)key, 0, 0666);
    if(-1 == semid)
    {
        semid = semget((key_t)key, 1, 0666|IPC_CREAT);
        semun un;
        un.val = 0; //setval
        semctl(semid, 0, SETVAL, un); //un.val
    }
    return semid;
}

/*
 * struct sembuf
 * {
 *      unsigned short sem_num; //member # in set(0, 1, ..., nsems-1)
 *      short sem_op; //operation(negative, 0, or pasitive)
 *      short sem_flg; //IPC_NOWAIT, SEM_UNDO
 * }
 *
 * */

void sem_p(int semid, int semnum){ //申请资源
    struct sembuf buf;
    buf.sem_num = semnum;
    buf.sem_op = -1;   
    semop(semid, &buf, 1);
}

void sem_v(int semid, int semnum){ //释放资源
    struct sembuf buf;
    buf.sem_num = semnum;
    buf.sem_op = 1;
    semop(semid, &buf, 1);
}

void sem_del(int id){
    semctl(id, 0, IPC_RMID);
}    

2.3 测试文件a.c和b.c

//a.c

#include"semaphore.h"
#include<assert.h>

int main()
{
    int semid = sem_init(1996);
    int fd = open("a.txt", O_WRONLY | O_CREAT, 0664);                           
    assert(-1 != fd);
    sleep(2);

    write(fd, "hello", 5); 
    sem_v(semid, 0); 
    close(fd);
    return 0;
}

//b.c

#include<assert.h>
#include<stdio.h>
#include"semaphore.h"

int main()
{
    int semid = sem_init(1996);
    sem_p(semid, 0); 
    int fd = open("a.txt", O_RDONLY | O_CREAT, 0664);                           

    assert(-1 != fd);
    char buff[128] = {0};
    read(fd, buff, 127);
    printf("%s\n", buff);
    close(fd);
    sem_del(semid);
    return 0;
}
图2 编译生成可执行文件

图3-1 写端执行前
图3-2 写端执行后

猜你喜欢

转载自blog.csdn.net/qq_41822235/article/details/83239942