关于信号量 sem系列函数

本来想自己写一篇关于信号量的总结,发现网络上有很多不错的,我给结合了一下:
https://blog.csdn.net/woxiaozhi/article/details/20461681
https://blog.csdn.net/hyman_c/article/details/53727177

#include<semaphore.h>
int sem_wait(sem_t*sem);
intsem_trywait(sem_t *sem);
intsem_timedwait(sem_t *sem, const struct timespec *abs_timeout); 与 -lrt 或 -pthread 一起链接。

glibc 需要特性测试宏(参看 feature_test_macros(7)):
sem_timedwait():_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

描述
(1)sem_wait() 递减(锁定)由 sem 指向的信号量。如果信号量的值大于零,那么递减被执行,并且函数立即返回。如果信号量的当前值是零,那么调用将阻塞到它可以执行递减操作为止(如信号量的值又增长超过零),或者调用被信号打断。

(2)sem_trywait() 与 sem_wait() 类似,只是如果递减不能立即执行,调用将返回错误(errno 设置为EAGAIN)而不是阻塞。

(3)sem_timedwait() 与 sem_wait() 类似,只不过 abs_timeout 指定一个阻塞的时间上限,如果调用因不能立即执行递减而要阻塞。abs_timeout 参数指向一个指定绝对超时时刻的结构,这个结果由自 Epoch,1970-01-0100:00:00 +0000(UTC) 秒数和纳秒数构成。

这个结构定义如下:

struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};

如果调用时超时时刻已经到点,并且信号量不能立即锁定,那么 sem_timedwait()将失败于超时(errno 设置为 ETIMEDOUT)。

如果操作能被立即执行,那么 sem_timedwait()永远不会失败于超时错误,而不管 abs_timeout的值。进一步说,abs_timeout 的验证在此时没有进行。

返回值
所有这些函数在成功时都返回 0;错误保持信号量值没有更改,-1 被返回,并设置 errno 来指明错误。
错误
・EINTR:这个调用被信号处理器中断,参看 signal(7)。
・EINVAL:sem 不是一个有效的信号量。

对 sem_trywait()有如下额外的错误:
EAGAIN
操作不能执行而不阻塞(也就是说,信号量当前值是零)。
ーーーーーーーーーーーーーーーーーーーーーーーー
对 sem_timedwait()有如下额外的错误:
EINVAL:abs_timeout.tv_nsecs 的值小于0,或者大于等于 100 百万。
ETIMEDOUT: 调用在信号量锁定之前超时。

遵循于 POSIX.1-2001.
注意: 信号处理器问题中断这些函数调用,无论在 sigaction(2) 里有没有使用标志 SA_RESTART,

示例:
下面展示一个操作匿名信号量的小程序。这个程序期待两个命令行参数。第一个参数指定一个秒数,这个秒数将用于设置由通过 SIGALRM 产生的定时器秒数。这个信号的处理器执行sem_post(3) 来递增一个信号量,这个信号量已经在 main() 函数里使用 sem_timedwait() 等待。第二个命令行参数指定调用 sem_timedwait()超时的长度,以秒为单位。现在示例了这个程序两种不同运行情况:

$ ./a.out 2 3

About to callsem_timedwait()
sem_post() fromhandler
sem_getvalue()from handler; value = 1
sem_timedwait()succeeded
ーーーーー
$ ./a.out 2 1
About to callsem_timedwait()
sem_timedwait()timed out

程序源码:

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<semaphore.h>
#include<time.h>
#include<assert.h>
#include<errno.h>
#include<signal.h>

sem_t sem;

#define handle_error(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void handler(int sig)
{
    write(STDOUT_FILENO, "sem_post() fromhandler\n", 24);
    if (sem_post(&sem) == -1) {
        write(STDERR_FILENO, "sem_post()failed\n", 18);
        _exit(EXIT_FAILURE);

    }
}


int main(int argc,char *argv[])
{
    struct sigaction sa;
    struct timespec ts;
    int s;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s<alarm-secs> <wait-secs>\n",

                argv[0]);
        exit(EXIT_FAILURE);
    }

    if (sem_init(&sem, 0, 0) == -1)
        handle_error("sem_init");

    /* 安置 SIGALRM 处理器;使用 argv[1] 设置警告时钟 */
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGALRM, &sa, NULL) ==-1)
        handle_error("sigaction");

    alarm(atoi(argv[1]));

    /* 计算相关的时刻,把当前时时加上 argv[2] 指定的秒数 */
    if (clock_gettime(CLOCK_REALTIME, &ts)== -1)
       handle_error("clock_gettime");

    ts.tv_sec += atoi(argv[2]);
    printf("main() about to callsem_timedwait()\n");

    while ((s = sem_timedwait(&sem,&ts)) == -1 && errno == EINTR)
        continue;       /* Restart if interrupted by handler */

    /* 检查发生了什么 */
    if (s == -1) {
        if (errno == ETIMEDOUT)
            printf("sem_timedwait() timedout\n");
        else
            perror("sem_timedwait");

    } else
        printf("sem_timedwait()succeeded\n");
    exit((s == 0) ? EXIT_SUCCESS :EXIT_FAILURE);
}

但是要注意的是,上面的代码在初始化信号量时sem_init(&sem, 0, 0) ,第二个参数为0,则为线程内共享,>0即为进程间共享,如下:


本文主要介绍下在多进程中使用信号量semaphore的方法。在上一文中,我们已经知道semaphore和mutex对临界区访问控制的一个最主要区别就是semaphore可以跨进程使用,而mutex只能在一个进程中使用。我们再来看下sem_init的原型,熟悉决定进程共享或者线程共享的方法:

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

通过设置pshared的值来控制信号量是属于进程间共享还是线程间共享,若pshared为0表明是多线程共享,否则就是多进程间共享。接下来我们实验思路是:创建两个进程,一个进程负责读取用户在界面输入的数据,然后存入本地的test.txt文件;另一个进程负责读取该文件,然后在标准输出上显示读取的内容。为此,我们需要创建两个个支持两个进程访问的信号量sem1和sem2,读文件时需要获取sem1信号,读取结束后释放sem2信号;写文件需要获取sem2信号,写文件结束后方式sem1信号。sem2的初始值为1,sem1的初始值为0,以保证先写入再进行读取,源代码如下,稍后挑关键内容进行解释:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<string.h>
#include<sys/mman.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define BUF_SIZE 30
void readfile(sem_t* psem1,sem_t* psem2)
{
    FILE* fp;
    char buf[BUF_SIZE];
    int str_len,str_seek=0;
    while(1)
    {
        sem_wait(psem1);
        fp=fopen("data.txt","r+");
        if(fp==NULL)
        return ;
        memset(buf,0,sizeof(BUF_SIZE));
        fseek(fp,str_seek,SEEK_SET);
        str_len=fread(buf,sizeof(char),BUF_SIZE-1,fp);
        buf[str_len]=0;
        str_seek+=str_len;
        fputs("output:",stdout);
        puts(buf);
        fclose(fp);
        sem_post(psem2);
    }
}
void writefile(sem_t* psem1,sem_t* psem2)
{
        FILE* fp;
        char buf[BUF_SIZE];
        while(1)
        {
            sem_wait(psem2);
            fp=fopen("data.txt","a");
            if(fp==NULL)
            return;
            memset(buf,0,BUF_SIZE);
            fputs("Input:",stdout);
            fgets(buf,BUF_SIZE,stdin);
            fwrite(buf,sizeof(char),strlen(buf),fp);
            fclose(fp);
            sem_post(psem1);
        }
}

int main()
{
    int pid;
    int fd1,fd2;
    void* pv;
    sem_t* psem1;
    sem_t* psem2;
    fd1=open("data1",O_CREAT|O_RDWR|O_TRUNC,0666);
    fd2=open("data2",O_CREAT|O_RDWR|O_TRUNC,0666);\
    ftruncate(fd1,8192);
    ftruncate(fd2,8192);
    //lseek(fd,5000,SEEK_SET);
    psem1=(sem_t*)mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
    psem2=(sem_t*)mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd2,0);
    sem_init(psem1,1,0);
    sem_init(psem2,1,1);
    pid=fork();
    if(pid==0)
    {
        puts("进入子进程");
        writefile(psem1,psem2);
    }
    else
    {
        puts("进入父进程");
        readfile(psem1,psem2);
    }
    sem_destroy(psem1);
    sem_destroy(psem2);
    munmap(psem1,sizeof(sem_t));
    munmap(psem2,sizeof(sem_t));
    close(fd1);
    close(fd2);
    return 0; 
}

为了能够跨进程使用semaphore,我们引入了跨进程的技术mmap,第61、第62行分别打开了两个mmap需要映射的文件,和我们平时用的open函数不同,这里面为程序赋予了该文件的666权限。这点很重要,因为mmap需要映射的本地文件必须明确赋予其可读写的权限,否则无法通信。

第63行和第64行分别设置两个本地映射文件的大小,以保证有充分的空间在mmap中映射并容纳我们定义的sem_t变量。这点也很重要,如果空间不够会造成总线错误。

第66行和第67行分别利用mmap在共享内存中映射了两个sem_t类型的指针,这就是我们需要sem_init的信号量。
第68、69行开始初始化信号量。

70行fork了两个进程,在子进程中我们进行写操作,在主进程中我们进行读操作。读写操作的代码比较简单,在这里不再多说。

第81到86行在使用完信号量后分别是销毁信号量、释放共享内存、关闭文件操作符。
程序写到这里基本上完成了这个实验,可以看下执行的结果:

・[Hyman@Hyman-PC semphare]$ ./a.out
  进入父进程
  进入子进程
  Input:你好
  Output:你好
  Input:
・我们可以简单总结下在多进程中使用信号量的步骤:
(1)open()用于进行mmap映射的文件,得到文件操作符fd;
(2)把映射文件用ftruncate或者fseek重新设置大小,以保证有足够的空间容纳我们需要传递的sem_t变量;
(3)利用mmap函数在共享内存中创建sen_t类型的指针。
(4)用sem_init()函数初始化第(3)步中创建的指针,也就得到了我们需要的信号量。
(5)用sem_wait()和sem_post()函数进行信号量的等待和释放。
(6)用sem_destroy()销毁信号量。
(7)用munmap()释放共享内存以及用close()函数关闭文件操作符。
这个程序的例子感觉也不太好,用 mmap的方式生成信号量(为了生成而生成),用共享内存是不是能更好一点


实际上,多进程共享,用无名信号量的方法感觉太过于累赘,还得用共享内存的方式来把sem存起来,想想真的是很麻烦。
一般多进程共享信号量,都是用有名信号量的方式:

有名:sem_t *sem sem_open(const char *name, int oflag, …/*mode_t mode,unsinged int value) ;
无名:int sem_init(sem_t *sem,int shared, unsigned int value);

区别:
1.创建有名信号量必须指定一个与信号量相关链的文件名称,这个name通常是文件系统中的某个文件。
基于内存的信号量不需要指定名称
2.有名信号量sem 是由sem_open分配内存并初始化成value值
基于内存的信号量是由应用程序分配内存,有sem_init初始化成为value值。如果shared为1,则分配的信号量应该在共享内存中。
3.sem_open不需要类似shared的参数,因为有名信号量总是可以在不同进程间共享的
而基于内存的信号量通过shared参数来决定是进程内还是进程间共享,并且必须指定相应的内存
4.基于内存的信号量不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号量的值,因此,对于一个给定的信号量,我们必须小心保证只调用sem_init一次,对于一个已经初始化过的信号量调用sem_init,结果是未定义的。
5.内存信号量通过sem_destroy删除信号量,有名信号量通过sem_unlink删除

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值