进程间通讯——POSIX 有名信号量与无名信号量

原文地址:blogof33.com/post/9/编程

前言

在 POSIX 系统中,进程间通讯是一个颇有意思的话题。bash

POSIX信号量进程是3种 IPC(Inter-Process Communication) 机制之一,3种 IPC 机制源于 POSIX.1 的实时扩展。Single UNIX Specification 将3种机制(消息队列,信号量和共享存储)置于可选部分中。在 SUSv4 以前,POSIX 信号量接口已经被包含在信号量选项中。在 SUSv4 中,这些接口被移至了基本规范,而消息队列和共享存储接口依然是可选的。多线程

POSIX 信号量接口意在解决 XSI 信号量接口的几个缺陷。函数

  • 相比于 XSI 接口,POSIX 信号量接口考虑了更高性能的实现。post

  • POSIX 信号量使用更简单:没有信号量集,在熟悉的文件系统操做后一些接口被模式化了。尽管没有要求必定要在文件系统中实现,可是一些系统的确是这么实现的。性能

  • POSIX 信号量在删除时表现更完美。回忆一下,当一个 XSI 信号量被删除时,使用这个信号量标识符的操做会失败,并将 errno 设置成 EIDRM。使用 POSIX 信号量时,操做能继续正常工做直到该信号量的最后一次引用被释放。ui

    ——摘自《UNIX高级环境编程(中文第3版)》465-466页spa

前段时间笔者在写管道通讯的时候,探究了一下 POSIX 进程间的两种信号量通讯方式:有名信号量和无名信号量。有不少人认为进程间通讯只能使用有名信号量,无名信号量只能用于单进程间的多线程通讯。其实无名信号量也能够进行进程间通讯。线程

区别

有名信号量和无名信号量的差别在于建立和销毁的形式上,可是其余工做同样。指针

无名信号量只能存在于内存中,要求使用信号量的进程必须能访问信号量所在的这一块内存,因此无名信号量只能应用在同一进程内的线程之间(共享进程的内存),或者不一样进程中已经映射相同内存内容到它们的地址空间中的线程(即信号量所在内存被通讯的进程共享)。意思是说无名信号量只能经过共享内存访问。

相反,有名信号量能够经过名字访问,所以能够被任何知道它们名字的进程中的线程使用。

单个进程中使用 POSIX 信号量时,无名信号量更简单。多个进程间使用 POSIX 信号量时,有名信号量更简单。

联系

不管是有名信号量仍是无名信号量,均可以经过如下函数进行信号量值操做。

wait

weit 为信号量值减一操做,总共有三个函数,函数原型以下:

#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

Link with -pthread.这一句表示 gcc 编译时,要加 -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码

其中,第一个函数的做用是,若 sem 小于 0 ,则线程阻塞于信号量 sem ,直到 sem 大于 0 ;不然信号量值减1。

第二个函数做用与第一个相同,只是此函数不阻塞线程,若是 sem 小于 0,直接返回一个错误(错误设置为 EAGAIN )。

第三个函数做用也与第一个相同,第二个参数表示阻塞时间,若是 sem 小于 0 ,则会阻塞,参数指定阻塞时间长度。 abs_timeout 指向一个结构体,这个结构体由从 1970-01-01 00:00:00 +0000 (UTC) 开始的秒数和纳秒数构成。结构体定义以下:

struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };
复制代码

若是指定的阻塞时间到了,可是 sem 仍然小于 0 ,则会返回一个错误 (错误设置为 ETIMEDOUT )。

post

post 为信号量值加一操做,函数原型以下:

#include <semaphore.h>

int sem_post(sem_t *sem);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码

应用实例

有名信号量

建立

有名信号量建立能够调用 sem_open 函数,函数说明以下:

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);  
sem_t *sem_open(const char *name, int oflag,	
                       mode_t mode, unsigned int value);	

Link with -pthread.返回值:若成功,返回指向信号量的指针;若出错,返回SEM_FALLED
复制代码

其中第一种函数是当使用已有的有名信号量时调用该函数,flag 参数设为 0

若是要调用第二种函数,flag 参数应设为 O_CREAT ,若是有名信号量不存在,则会建立一个新的,若是存在,则会被使用而且不会再初始化。

当咱们使用 O_CREAT 标志时,须要提供两个额外的参数:

mode 参数指定谁能够访问信号量,即权限组,mode 的取值和打开文件的权限位相同,好比 0666 表示 全部用户可读写 。由于只有读和写访问要紧,因此实现常常为读和写打开信号量。

value 指定信号量的初始值,取值范围为 0~SEM_VALUE_MAX 。

若是信号量存在,则调用第二个函数会忽略后面两个参数(即 modevalue )。

释放

当完成信号量操做之后,能够调用 sem_close 函数来释听任何信号量的资源。函数说明以下:

#include <semaphore.h>

int sem_close(sem_t *sem);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码

若是进程没有调用该函数便退出了,内核会自动关闭任何打开的信号量。不管是调用该函数仍是内核自动关闭,都不会改变释放以前的信号量值。

销毁

可使用 sem_unlink 函数销毁一个有名信号量。函数说明以下:

#include <semaphore.h>

int sem_unlink(const char *name);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码

sem_unlink 函数会删除信号量的名字。若是没有打开的信号量引用,则该信号量会被销毁,不然,销毁会推迟到最后一个打开的引用关闭时才进行。

例子

例如,管道通讯中,若是父进程使用 fork()建立两个子进程1和2,子进程1,2按顺序向管道写一段文字,最后父进程从管道将子进程写入的内容读出来,要保证进程执行的前后顺序,能够用有名信号量来解决。

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>

#include<fcntl.h>

int main(){
    int pid1,pid2;
    sem_t *resource1; 
    sem_t *resource2; 
    int Cpid1,Cpid2=-1;
    int fd[2];//0为读出段,1为写入端
    char outpipe1[100],inpipe[200],outpipe2[100];
    pipe(fd);//创建一个无名管道

    pid1 = fork();
    if(pid1<0){
        printf("error in the first fork!");
    }else if(pid1==0){//子进程1
        resource1=sem_open("name_sem1",O_CREAT,0666,0);
        Cpid1 = getpid();
        close(fd[0]);//关掉读出端
        lockf(fd[1],1,0);//上锁,则锁定从当前偏移量到文件结尾的区域
        sprintf(outpipe1,"Child process 1 is sending a message!");
        write(fd[1],outpipe1,strlen(outpipe2));
        lockf(fd[1],0,0);//解锁
        sem_post(resource1);
        sem_close(resource1);
        exit(0);
   }else{
        
        pid2 = fork();
        if(pid2<0){
            printf("error in the second fork!\n");
        }else if(pid2==0){  
                resource1=sem_open("name_sem1",O_CREAT,0666,0);
                resource2=sem_open("name_sem2",O_CREAT,0666,0);
                Cpid2 = getpid();
                sem_wait(resource1);
				close(fd[0]);
                lockf(fd[1],1,0);
                sprintf(outpipe2,"Child process 2 is sending a message!");

                write(fd[1],outpipe2,strlen(outpipe2));
                lockf(fd[1],0,0);//解锁
                sem_post(resource2);
                sem_close(resource1);
                sem_close(resource2);
                exit(0);
        }
        if(pid1 > 0 && pid2 >0){
                resource2=sem_open("name_sem2",O_CREAT,0666,0);
                sem_wait(resource2);
                waitpid(pid1,NULL,0);
                waitpid(pid2,NULL,0);
                close(fd[1]);//关掉写端
                read(fd[0],inpipe,200);
                printf("%s\n",inpipe);
                sem_close(resource2);
                
                exit(0);
        }
        sem_unlink("name_sem1");
        sem_unlink("name_sem2");
    }
    return 0;
}

复制代码

无名信号量

建立

无名信号量能够经过 sem_init 函数建立,函数说明以下:

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码

pshared 参数指示该信号量是被一个进程的多个线程共享仍是被多个进程共享。

若是 pshared 的值为 0 ,那么信号量将被单进程中的多线程共享,而且应该位于某个地址,该地址对全部线程都可见(例如,全局变量或变量在堆上动态分配)。

若是 pshared 非零,那么信号量将在进程之间共享,而且信号量应该位于共享内存区域。

销毁

若是无名信号量使用完成,能够调用 sem_destory 函数销毁该信号量。函数说明以下:

#include <semaphore.h>

int sem_destroy(sem_t *sem);

Link with -pthread.返回值:若成功,返回 0 ;若出错,返回-1
复制代码

注意:

  • 销毁其余进程或线程当前被阻塞的信号量会产生未定义的行为。
  • 使用已销毁的信号量会产生未定义的结果,除非使用 sem_init 从新初始化信号量。
  • 一个无名信号量应该在它所在的内存被释放前用 sem_destroy 销毁。若是不这样作,可能会致使某些实现出现资源泄漏。

例子

使用无名信号量实现有名信号量中的例子:

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include<fcntl.h>

int main(){
    int pid1,pid2;
    int Cpid1,Cpid2=-1;
    int fd[2];//0为读出段,1为写入端
    char outpipe1[100],inpipe[200],outpipe2[100];
    void *shm = NULL;
    sem_t *shared;
    int shmid = shmget((key_t)(1234), sizeof(sem_t *), 0666 | IPC_CREAT);//建立一个共享内存,返回一个标识符
    if(shmid == -1){
        perror("shmat :");
        exit(0);
    }
    shm = shmat(shmid, 0, 0);//返回指向共享内存第一个字节的指针
    shared = (sem_t *)shm;
    sem_init(shared, 1, 0);//初始化共享内存信号量值为0
    pipe(fd);//创建一个无名管道

    pid1 = fork();
    if(pid1<0){
        printf("error in the first fork!");
    }else if(pid1==0){//子进程1

        Cpid1 = getpid();
        close(fd[0]);//关掉读出端
        lockf(fd[1],1,0);//上锁,则锁定从当前偏移量到文件结尾的区域
        sprintf(outpipe1,"Child process 1 is sending a message!");
        write(fd[1],outpipe1,strlen(outpipe1));
        lockf(fd[1],0,0);//解锁
        sem_post(shared);

        exit(0);
   }else{

        pid2 = fork();
        if(pid2<0){
            printf("error in the second fork!\n");
        }else if(pid2==0){
                sem_wait(shared);
                Cpid2 = getpid();
				close(fd[0]);
                lockf(fd[1],1,0);
                sprintf(outpipe2,"Child process 2 is sending a message!");

                write(fd[1],outpipe2,strlen(outpipe2));
                lockf(fd[1],0,0);//解锁

                exit(0);
        }
        if(pid1 > 0 && pid2 >0){

                waitpid(pid2,NULL,0);//同步,保证子进程先写父进程再读
                close(fd[1]);//关掉写端
                read(fd[0],inpipe,200);
                printf("%s\n",inpipe);

                exit(0);
        }

    }
    return 0;
}
复制代码
相关文章
相关标签/搜索