Linux进阶-ipc信号量

目录

system-V IPC信号量

semget():创建或获取信号量

semop():PV操作

semctl():信号量集的一系列控制操作

sem.h文件

sem.c文件

main.c文件

Makefile文件

执行过程


system-V IPC信号量

本质上是一个计数器,用于协调多进程间对共享数据对象的读取,主要用来保护共享资源,使得该共享资源在一个时刻只有一个进程独享。

信号量只能进行两种操作:P(锁,申请资源)V(解锁,释放资源)

P操作:

        如果有可用的资源(信号量值大于0),则申请一个资源(信号量值减1,进入临界区代码);

        如果没有可用的资源(信号量值等于0),则阻塞,直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。

V操作:

        如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞的进程。

        如果在该信号量的等待队列中没有进程在等待资源,则释放一个资源(信号量值加1)。

在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)。

        原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的。

semget():创建或获取信号量

semget()函数是创建或获取一个已经创建的信号量。

#include 
#include 
#include 
int semget(key_t key, int nsems, int semflg);
/*
key:表示系统内的信号量。如果key已存在,nsems参数指定为0,semflg参数也指定为0。可以使用IPC_PRIVATE创建一个没有key的信号量
nsems:用于创建信号量时,表示可用的信号量数目。
semflg:指定标志位和mode。
    IPC_CREAT:创建新的信号量,即使信号量已经存在也不会出错。
    IPC_CREAT | IPC_EXCL:创建一个新的唯一的信号量,如果已经存在会返回报错
返回值:
    执行成功:信号量标识符
    执行失败:-1
*/

创建信号量时,受到一下系统信息的影响:

        SEMMNI:系统中信号量总数的最大值。

        SEMMSL:每个信号量中信号量元素个数的最大值。

        SEMMNS:系统中所有信号量中的信号量元素总数的最大值。

在Linux系统中,以下信息可通过命令ipcs -l查看。

semop():PV操作

#include 
#include 
#include 
int semop(int semid, struct sembuf *sops, size_t nsops);
/*
semid:system-v IPC信号量的标识符
sops:指向一个struct sembuf结构体数组的指针,该数组是一个信号量操作数组,如下文
nsops:表示sops参数数组的数量
*/
struct sembuf{
        unsigned short int sem_num;    // 信号量的序号:0~nsems-1
        short int sem_op;              // 对信号量的操作:>0、0、<0
        short int sem_flg;             // 操作标识:0、IPC_WAIT、SEM_UNDO
};

sem_num标识信号量的第几个信号量,0标识第一个,nsems-1标识最后一个。

sem_op标识对信号量所进行的操作类型。

        >0:表示进程对资源使用完毕,交回该资源,即对该信号量执行V操作,交回的资源数由sem_op决定,系统会把sem_op的值加到该信号量的信号量当前值semval上。如果sem_flg指定了SEM_UNDO(还原)标志,则从该进程的信号量调整值中减去sem_op。

        <0:表示进程希望使用资源,对该信号量执行P操作,当信号量当前值semval大于或等于-sem_op时,semval减去sem_op的绝对值,为该进程分配对应数目的资源。如果sem_flg指定了SEM_UNDO(还原)标志,则sem_op的绝对值加到该进程的此信号量调整值上。当semval小于-sem_op时,相应信号量的等待进程数量就加1,调用进程被阻塞,直到semval大于或等于-sem_op时,调用进程被唤醒,执行相应的P操作。

        0:表示进程要阻塞等待,直到信号量当前值semval变为0。

sem_flg表示信号量操作的属性标志。

        0:正常操作

        SEM_UNDO:维护进程对信号量的调整值,进程退出时会自动还原它对信号量的操作。

        IPC_WAIT:调用进程在信号量的值不满足条件的情况下不会被阻塞,直接返回-1,并将errno设置为EAGAIN。

信号量调整值:指定信号量针对某个特定进程的调整值。只有sembuf结构的sem_flg指定为SEM_UNDO后,信号量调整值才会随着sem_op而更新。

对某个进程,在指定了SEM_UNDO后,对信号量的当前值的修改都会反应到信号量调整值上,当该进程终止时,内核会根据信号量调整值重新恢复信号量之前的值,SEM_UNDO操作可以防止进程退出时没有释放信号量导致的死锁。

semctl():信号量集的一系列控制操作

#include 
#include 
#include 
int semctl(int semid, int semnum, int cmd, ...);
/*
semid:system-v IPC信号量的标识符
semnum:表示信号量集的第几个信号量,0表示第一个,nsems-1表示最后一个
cmd:操作命令
    IPC_STAT:获取此信号量集合的 semid_ds 结构,存放在第四个参数的 buf 中。
    IPC_SET:通过第四个参数的 buf 来设定信号量集相关联的 semid_ds 中信号量集合权
限为 sem_perm 中的 uid, gid, mode。
    IPC_RMID:从系统中删除该信号量集合。
    GETVAL:返回第 semnum 个信号量的值。
    SETVAL:设置第 semnum 个信号量的值,该值由第四个参数中的 val 指定。
    GETPID:返回第 semnum 个信号量的 sempid,最后一个操作的 pid。
    GETNCNT:返回第 semnum 个信号量的 semncnt。等待 semval 变为大于当前值的线程
数。
    GETZCNT:返回第 semnum 个信号量的 semzcnt。等待 semval 变为 0 的线程数。
    GETALL:去信号量集合中所有信号量的值,将结果存放到的 array 所指向的数组。
    SETALL:按 arg.array 所指向的数组中的值,设置集合中所有信号量的值。
第四个参数:可选,参数类型为union semun。
    union semun{
        int val;                // setval的值
        struct semid_ds *buf;   // IPC_STAT、IPC_SET的buffer
        unsigned short *array;  // GETALL、SETALL的数组
        struct seminfo *__buf;  // IPC_INFO的buffer
    }
*/

sem.h文件

#ifndef __SEM_H_
#define __SEM_H_

int init_sem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);

#endif

sem.c文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "sem.h"

union semun{
        int val;
        struct semid_ds *buf;
};

int init_sem(int sem_id, int init_value)
{
        union semun sem_union;
        // setval的值
        sem_union.val = init_value;

        // 设置第一个信号量的值
        if(semctl(sem_id, 0, SETVAL, sem_union) == -1){
                perror("sem init");
                return -1;
        }

        return 0;
}

int del_sem(int sem_id)
{
        union semun sem_union;

        // 删除第一个信号量
        if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1){
                perror("sem del");
                return -1;
        }

        return 0;
}

int sem_p(int sem_id)
{
        struct sembuf sops;
        sops.sem_num = 0;               // 单个信号量的编号应该为0
        sops.sem_op = -1;               // P操作
        sops.sem_flg = SEM_UNDO;        // 若进程退出,系统将还原信号量

        if(semop(sem_id, &sops, 1) == -1){
                perror("P");
                return -1;
        }else{
                printf("P successful!\n");
        }

        return 0;
}

int sem_v(int sem_id)
{
        struct sembuf sops;
        sops.sem_num = 0;               // 单个信号量的编号应该为0
        sops.sem_op = 1;                // V操作
        sops.sem_flg = SEM_UNDO;        // 若进程退出,系统将还原信号量

        if(semop(sem_id, &sops, 1) == -1){
                perror("V");
                return -1;
        }else{
                printf("V successful!\n");
        }

        return 0;
}

main.c文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "sem.h"

int main(void)
{
        pid_t pid;
        int sem_id;

        sem_id = semget((key_t)0x1111, 1, 0666 | IPC_CREAT);
        init_sem(sem_id, 0);

        pid = fork();
        if(pid == -1){
                perror("fork");
        }else if(pid == 0){
                // 子进程
                sleep(3);
                printf("the child process pid:%d\n", getpid());
                sem_v(sem_id);
        }else{
                // 父进程
                sem_p(sem_id);
                printf("the father process pid:%d\n", getpid());
                sem_v(sem_id);
                del_sem(sem_id);
        }

        exit(0);
}

Makefile文件

照旧

执行过程

Linux进阶-ipc信号量_第1张图片

你可能感兴趣的:(#,linux基础之路,linux)