Linux进程间通信——信号量

一、什么是信号量

信号量(Semaphore),也称为信号灯,主要用来控制多个进程对共享资源的访问,信号量是进程通信的一种重要方法,但是它本身并不进行数据交换,这点和前面介绍的管道和消息队列不同。

信号量是一个整数,只能通过P、V原语进行操作,信号量大于或者等于0,表示可提供并发进程使用的资源数;小于0时表示正在等待使用资源的进程数。

如果一个进程需要访问共享资源,执行的操作如下所示:

(1)测试控制该资源的信号量。

(2)如果信号量的值大于0,则访问该资源,并将信号量减1.

(3)如果信号量的值小于或等于0,则进入休眠状态,直至信号量的值大于0时才被唤醒,返回到第一步。

(4)进程不再使用共享资源时,将信号量加1,此时如果有其它进程正在休眠等待该信号,则将其唤醒。

二、信号量的创建

1、semget函数
它的作用是创建一个新信号量或取得一个已有信号量。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflag);

参数key为信号量的键值,它可以由用户指定,也可以调用ftok函数来生成。
参数nsems指定需要的信号量数目,它的值几乎总是1。
参数semflag是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1,并设置相应的错误码。
示例:
//使用semget函数创建一个信号量集然后删除
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int main()
{
    int semid;
    key_t key;
    key=ftok("/home", 'a'); //生成信号量的键值
    if (key<0)
    {
        perror("ftok error");
        exit(1);
    }
    semid=semget(key, 1, IPC_CREAT|0666); //创建一块信号量集
    if (semid<0)
    {
        perror("semget error");
        exit(1);
    }
    printf("Done!\n");
    
    sleep(5);
    
    semid=semctl(semid, 0, IPC_RMID,0); //删除该信号量集
    if (semid<0)
    {
        printf("semget error");
        exit(1);
    }
    printf("removed!\n");
    return 0;
    
}
三、信号量的控制
1、semctl函数
该函数用来直接控制信号量信息.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int sem_id, int sem_num, int cmd, union semun arg);

参数sem_id是由semget返回的信号量标识符,
参数nsems指定需要的信号量数目。
参数cmd为控制命令,它的取值可以为:
(1)IPC_RMID:删除一个信号量
(2)IPC_EXCL:只有在信号量集不存在时才创建
(3)IPC_SET:设置信号量的访问权限
(4)SETVAL:设置信号量的值
(5)GETVAL:获取指定信号量的值
(6)GETPID:获取最后操作信号量进程的标识符
(7)GETNCNT:获取等待信号量变1的进程数
(8)GETZCNT:获取等待信号量变为0的进程数
参数arg为操作值,它的类型为semun共享体。
union semun { 
         int val;               // 信号量的值,cmd的值为SETVAL时使用   
         struct semid_ds *buf;  // 信号量的状态,cmd的值为GETVAL、GETPID、GETNCNT、或GETZCNT时使用
         unsigned short *array; // 所有信号量的值
         struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区 
};

四、信号量的操作
1、semop函数
它的作用是改变信号量的值.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int sem_id, struct sembuf *sem_opa, unsigned short nsops);
sem_id是由semget返回的信号量标识符。
参数sem_opa为指向信号量操作数组的指针。
参数nsops为数组中的元素个数。
sembuf结构的定义如下:
struct sembuf{
    short sem_num;//除非使用一组信号量,否则它为0
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                    //一个是+1,即V(发送信号)操作。
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                    //并在进程没有释放该信号量而终止时,操作系统释放信号量
};
其中,sem_num为要操作的信号量在信号集中的序号;sem_op可以取为正数、负数或0,表示要执行的操作;sem_flg为操作的选项,可以设为IPC_NOWAIT或SEM_UNDO。
如果sem_op为正数,则表示进程不再使用该资源,semop函数将该值加到相应的信号量上,并唤醒等待的进程;如果为负数,表示进程希望使用资源,semop函数将该值加到相应的信号量上,如果结果为负数,则阻塞进程,如果sem_op为0,表示进程要一直等待,直到信号量的值变为0.
函数调用成功后,返回0,调用过程中发生错误或被信号中断则返回-1,并设置相应的错误码。
示例:通过信号量来实现进程间的同步。
//通过信号量实现进程间的同步
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <sys/wait.h>
int main()
{
    int semid;
    pid_t pid;
    key_t key;
    struct sembuf lock={0,-1,SEM_UNDO};
    struct sembuf unlock={0,1,SEM_UNDO|IPC_NOWAIT};
    int i,ret,status;
    key=ftok("/home", 'a');   //生成信号量的键值
    semid=semget(key, 1, IPC_CREAT|0666);  //创建一个信号量集
    if (semid<0)
    {
        perror("semget error");
        exit(1);
    }
    ret=semctl(semid, 0, SETVAL,1);    //将信号量的值设为1
    if (ret<0)
    {
        perror("semctl error");
        exit(1);
    }
    pid=fork();     //创建子进程
    if (pid<0)
    {
        perror("fork error");
        exit(1);
    }
    if (pid==0)    //子进程
    {
        for (i=0; i<3; i++)
        {
            sleep(abs((int)3.0*rand()/(RAND_MAX+1)));//休眠0-3秒
            ret=semop(semid, &lock, 1);       //申请访问共享资源
            if (ret==-1)
            {
                perror("lock error");
                exit(1);
            }
            printf("child process access the resource.\n");  //开始访问共享资源
            sleep(abs((int)3.0*rand()/(RAND_MAX+1)));
            printf("complete!\n");
            ret=semop(semid, &unlock, 1);               //共享资源访问完毕
            if (ret==-1)
            {
                perror("unlock error");
                exit(1);
            }
        }
    }
    else                //父进程
    {
        for (i=0; i<3; i++)
        {
            sleep(abs((int)(3.0*rand()/(RAND_MAX+1))));
            ret=semop(semid, &lock, 1);         //申请访问共享资源
            if (ret==-1)
            {
                perror("lock error");
                exit(1);
            }
            printf("father process access the resource.\n");    //开始访问共享资源
            sleep(abs((int)3.0*rand()/(RAND_MAX+1)));
            printf("complete!\n");
            ret=semop(semid, &unlock, 1);       //共享资源访问完毕
            if (ret==-1)
            {
                perror("unlock error");
                exit(1);
            }
        }
        if (pid!=wait(&status))     //等待子进程结束
        {
            printf("wait error\n");
            exit(1);
        }
        ret=semctl(semid, 0, IPC_RMID,0);//删除信号量
        if (ret==-1)
        {
            perror("semctl error");
            exit(1);
        }
    }
    return 0;
}



五、信号量的总结
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。我们通常通过信号来解决多个进程对同一资源的访问竞争的问题,使在任一时刻只能有一个执行线程访问代码的临界区域,也可以说它是协调进程间的对同一资源的访问权,也就是用于同步进程的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值