Linux编程(10)_进程通信

1 进程通信相关概念

1 什么是IPC

进程间通信, InterProcess Communication

2 进程间通信常用几种方式

  • 管道 (使用最简单)
  • 信号 (开销最小)
  • 共享内存/映射区 (无血缘关系)
  • 本地套接字 (最稳定)
  • 共享队列

2 管道(匿名)

1 管道的概念

本质:

内核缓冲区

伪文件 - 不占用磁盘空间

特点:

两部分:

读端,写端,对应两个文件描述符
数据写端流入, 读端流出

操作管道的进程被销毁之后,管道自动被释放了

管道默认是阻塞的。

读写

#### 2 管道的原理

内部实现方式:队列

环形队列
特点:先进先出

缓冲区大小:
默认4k, 大小会根据实际情况做适当调整

3 管道的局限性

队列:

数据只能读取一次,不能重复读取

全双工(Full Duplex)是指在发送数据的同时也能够接收数据,两者同步进行,这好像我们平时打电话一样,说话的同时也能够听到对方的声音。目前的网卡一般都支持全双工。
半双工(Half Duplex)所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,当目前有两量车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台.
单工通信是指通信线路上的数据按单一方向传送.

匿名管道:适用于有血缘关系的进程

4 创建匿名管道

int pipe(int fd[2]); 数组有两个元素, 一个读端一个写端, 各自对应一个文件描述符

  • fd - 传出参数,
  • fd[0] - 读端
  • fd[1] - 写端

例:

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

int main()
{
    int fd[2];
    // 创建管道
    int ret = pipe(fd);
    // 如果返回值为-1, 则创建失败
    if(ret == -1) 
    {   
        perror("pipe error");
        exit(1);          
    }   

    // 读端
    printf("pipe[0] = %d\n", fd[0]);
    // 写端
    printf("pipe[1] = %d\n", fd[1]);

    close(fd[0]);
    close(fd[1]);

    return 0; 
}

5 父子进程使用管道通信

思考:

  • 单个进程能否使用管道完成读写操作?

    可以

  • 父子进程间通信是否需要sleep函数?

    父写 - 写的慢

    子读 - 读的快

    不用, 因为管道是阻塞的

  • 注意事项

    先创建管道, 再创建子进程

  • 父子进程实现 ps aux | grep "bash"

    数据重定向: dup2

    execlp

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

    int main(int argc, const char* argv[])
    {
    // 创建管道
    int fd[2];
    int ret = pipe(fd);
    if(ret == -1)
    {
    perror(“pipe error”);
    exit(1);
    }

    // 创建子进程
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    
    // ps aux | grep bash
    // 父进程执行 ps aux , 写管道, 关闭读端
    if(pid > 0)
    {
        //关闭读端
        close(fd[0]);
        // 数据写到管道,STDOUT_FILENO 指向fd[1]的指向,也就是管道的写端
        dup2(fd[1], STDOUT_FILENO);
        // 执行命令ps -aux
        execlp("ps", "ps", "aux", NULL);
        // 如果上面失败则:
        perror("execlp ps");
        exit(1);
    }
    // 子进程 grep bash 从管道中搜索, 读管道, 关闭写端
    else if(pid == 0)
    {
        // 关掉写端
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        execlp("grep", "grep", "bash","--color=auto",  NULL);
        perror("execlp grep");
        exit(1);
    }
    close(fd[0]);                                                                    
    close(fd[1]);
    
    return 0;
    

    ​“`

  • 兄弟进程实现ps aux | grep "bash"

    
    #include 
    
    
    #include 
    
    
    #include 
    
    
    #include 
    
    
    #include 
    
    
    #include 
    
    
    #include 
    
    
    int main(int argc, const char* argv[])
    {
        int fd[2];
        int ret  = pipe(fd);
        if(ret == -1)
        {
            perror("pipe error");
            exit(1);
        }
    
        printf("fd[0] = %d\n", fd[0]);
        printf("fd[1] = %d\n", fd[1]);
    
        //创建两个子进程
        int i = 0;
        int num = 2;
        for(; i//如果是子进程则跳出
            if(pid == 0)
            {
                break;
            }
        }
    
        //父进程回收紫禁城的pcb
        if(i == num)
        {
            close(fd[0]);
            close(fd[1]);
    
            //循环回收子进程
            //WNOHANG 非阻塞回收
            pid_t wpid;
            while( (wpid = waitpid(-1, NULL, WNOHANG)) != -1 )
            {
                if(wpid == 0)
                {
                    continue;
                }
                printf("died child pid = %d\n", wpid);
            }
        }
        else if(i == 0)
        {
            // ps aux
            close(fd[0]);
            dup2(fd[1], STDOUT_FILENO);
            execlp("ps", "ps", "aux", NULL);
        }
        else if(i == 1)
        {
            // grep bash
            close(fd[1]);
            dup2(fd[0], STDIN_FILENO);
            execlp("grep", "grep", "bash", NULL);
        }
    
        return 0;

6 管道读写行为

读操作
  • 有数据 read(fd) - 正常读, 返回读出的字节

  • 无数据

    • 可能写端全部关闭

      read解除阻塞, 返回0

      相当于读文件读到了尾部

    • 没有全部关闭

      read阻塞

写操作
  • 读端全部被关闭

    • 管道破裂, 进程被中止

      内核给当前进程法信号SIGPIPE, 中止进程

  • 读端没有全部关闭

    • 缓冲区写满

      write函数阻塞

    • 缓冲区没满

      write继续写, 知道满

如何设置非阻塞?
  • 默认读写两端都阻塞

  • 设置读端为非阻塞pipe(fd)

    • fcntl - 变参函数

      • 复制文件描述符
      • 修改文件的属性 - open的时候对应的flag属性
    • 设置方法:

      获取原来的flags: int flags = fcntl(fd[0], F_GETFL);

      设置新的flags: 非阻塞

      flag |= O_NONBLOCK

      fcntl(fd[0], F_SETFL, flags);

7 查看缓冲区大小

  1. 命令ulimit -a

  2. 函数fpathconf

    例: long num = fpathconf(fd[0], _PC_PIPE_BUF);

3 fifo

1 特点

  • 有名管道
  • 在磁盘上为类型为p(管道)的文件
  • 伪文件, 在磁盘大小永远为0
  • 数据在内核中对应的缓冲区
  • 半双工通信

2 使用场景

无血缘关系进程间通信

3 创建方式

  1. 命令 mkfifo 管道名
  2. 函数 mkfifo

4 fifo文件使用IO函数进行操作

  • open/close
  • read/write
  • 不能lseek

5 进程间通信

  1. fifo文件 — myfifo

    • 两个不相干的进程A(a.c) B(b.c)

    a.c –> read

    • int fd = open(“myfifo”, O_RDONLY);
    • read(fd, buf, sizeof(buf));
    • close(fd);

    • b.c — write

    int fd1 = open(“myfifo”, O_WRONLY);

    write(fd1, “hello”, 5);

    close(fd1);

4 内存映射区

1 mmap – 创建内存映射区

  • 作用: 将磁盘文件的数据映射到内存, 用户通过修改内存就能修改磁盘文件

  • 函数原型

    void *mmap
    {
        void *adrr, //映射区首地址, 传NULL
        size_t length, //映射区的大小, 最小4k, 不能为0, 一般和文件同大小
        int prot, //映射区权限 PROT_READ--映射区必须要有读权限 
        int flags, //标志位参数 
        //MAP_SHARED共享的,数据会同步到磁盘. MAP_PRIVATE私有的,不会同步
        int fd, //文件描述符, 需要映射的源文件, 需要先open一个文件
        off_t offset //映射文件的指针偏移量, 为4k的整数倍
    }
  • 返回值:

    成功: 返回映射区的首地址

    失败: 返回MAP_FAILED, 就是(void *)-1

2 munmap – 释放内存映射区

  • 函数原型 int munmap(void *addr, size_t length);

    addr–映射区的首地址

    length–映射区的长度

3 注意事项

  • 如修改内存映射区是地址的指针, 则释放失败, 可以复制之后再操作

    char *pt = ptr

  • open的文件的权限应 >= 映射区的权限

4 有血缘关系的进程间通信

父子进程间永远共享:

  • 文件描述符

  • 内存映射区

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

    int main(int argc, const char* argv[])
    {
    //获取文件描述符
    int fd = open(“english.txt”, O_RDWR);
    if(fd == -1)
    {
    perror(“open error”);
    exit(1);
    }

    // get file length
    // len > 0
    int len = lseek(fd, 0, SEEK_END);
    
    void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }
    close(fd);
    
    char buf[4096];
    // 从内存中读数据
    printf("buf = %s\n", (char*)ptr);
    strcpy(ptr, "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyaaaaa");
    
    
    // 进程间通信
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    //父进程
    if(pid >0)
    {
        //写数据
        strcpy((char*)ptr, "come on!");
        //回收
        wait(NULL);
    }
    else if(pid == 0)
    {
        //读数据
        printf("%s\n", (char*)ptr);
    }
    

    // ptr++;
    int ret = munmap(ptr, len);
    if(ret == -1)
    {
    perror(“munmap error”);
    exit(1);
    }

    return 0;
    

    ​“`

匿名映射区:

int main(int argc, const char* argv[])
{
    //创建匿名映射区
    int len = 4096;
    void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
    if(ptr == MAP_FAILED)
    {
        perror("mmap error");
        exit(1);
    }

    // 进程间通信
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork error");
        exit(1);
    }
    //父进程
    if(pid >0)
    {
        //写数据
        strcpy((char*)ptr, "come on!");
        //回收
        wait(NULL);
    }
    else if(pid == 0)
    {
        //读数据
        printf("%s\n", (char*)ptr);
    }

    //释放内存映射区
    int ret = munmap(ptr, len);
    if(ret == -1)
    {
        perror("munmap error");
        exit(1);
    }

    return 0;

5 无血缘关系的进程间通信

a.c和b.c, 只能借助磁盘文件创建映射区, 不阻塞

  • a.c

    int fd = open(“hello”);

    void* ptr = mmap(, , , ,fd, 0);

    int main(int argc, char *argv[])
    {
        int fd = open("temp", O_RDWR | O_CREAT, 0664);
    
        void* ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(ptr == MAP_FAILED)
        {
            perror("mmap");
            exit(1);
        }
    
        while(1)
        {
            char*p = (char*)ptr;
            p += 1024;
            strcpy(p, "hello parent, i am your 朋友!!!\n");
            sleep(2);
        }
    
        // 释放
        int ret = munmap(ptr, 4096);
        if(ret == -1)
        {
            perror("munmap");
            exit(1);
        }
    
        return 0;
    }

  • b.c

    int fd1 = open(“hello”);

    void* ptr1 = mmap( , , , , fd1, 0);

    int main(int argc, char *argv[])
    {
        int fd = open("temp", O_RDWR | O_CREAT, 0664);
        ftruncate(fd, 4096);
        int len = lseek(fd, 0, SEEK_END);
    
        void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(ptr == MAP_FAILED)
        {
            perror("mmap");
            exit(1);
        }
    
        while(1)
        {
            sleep(1);
            printf("%s\n", (char*)ptr+1024);
        }
    
        // 释放
        int ret = munmap(ptr, len);
        if(ret == -1)
        {
            perror("munmap");
            exit(1);
        }
    
        return 0;
    }

5 共享内存

ipcs 查看共享内存队列消息

ipcrm -m -q -s id 删除对应的内容

Linux编程(10)_进程通信_第1张图片

  • shmget() 创建共享内存

    int shmget(key_t key, size_t size, int shmflg);

    • key – 程序需要提供一个参数key(非0整数),它有效地为共享内存段命名
    • size – 以字节为单位指定需要共享的内存容量
    • shmflg – 权限标志, 同open函数的mode参数

    返回值:若成功返回共享内存的ID标识符,错误返回-1,错误原因存于errno

  • shmat() – at: attach 挂载内存

    void *shmat(int shm_id, const void *shm_addr, int shmflg);

    • shm_id – 由shmget()返回的共享内存标识
    • shm_addr – 指定共享内存链接到当前进程中的地址位置
    • shmflg – 设置为0, 则由内核自动分配

    返回值: 若成功返回映射得到的地址,错误返回-1,错误原因存于errno

  • shmdt() 卸载内存

    int shmdt(const void *shmaddr);

    • shmaddr – 为shmat()映射后获得的地址

    返回值: 成功返回0,否则返回-1,错误原因存于errno

  • shmctl() 控制共享内存

    int shmctl(int shm_id, int command, struct shmid_ds *buf);

    • shm_id – shmget()函数返回的共享内存标识符

    • command

      • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
      • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
      • IPC_RMID:删除共享内存段
    • *buf – 结构指针, 指向共享内存模式和访问权限的结构

      struct shmid_ds  
      {  
          uid_t shm_perm.uid;  
          uid_t shm_perm.gid;  
          mode_t shm_perm.mode;  
      }; 

6 消息队列

6.1 创建或使用消息队列 msgget

int msgget(key_t key, int msgflg);

与其他的IPC机制一样,程序必须提供一个键来命名某个特定的消息队列。msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

在程序中若要使用消息队列,必须要能知道消息队列key,因为应用进程无法直接访问内核消息队列中的数据结构,因此需要一个消息队列的标识,让应用进程知道当前操作的是哪个消息队列,同时也要保证每个消息队列key值的唯一性。

申请一块内存,创建一个新的消息队列(数据结构msqid_ds),将其初始化后加入到msgque向量表中的某个空位置处,返回标示符。或者在msgque向量表中找键值为key的消息队列。

6.2 将消息添加到消息队列中 msgsend

int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);  

msgid是由msgget函数返回的消息队列标识符。

msg_ptr是一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。所以消息结构要定义成这样:

struct my_message{    
    long int message_type;    
    /* The data you wish to transfer*/    
};  

msg_szmsg_ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说msg_sz是不包括长整型消息类型成员变量的长度。

msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。

如果调用成功,消息数据的一分副本将被放到消息队列中,并返回0,失败时返回-1.

6.3 从消息队列中获取消息 msgrcv

int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msg

msgid, msg_ptr, msg_st的作用也函数msgsnd函数的一样。

msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息。

msgflg用于控制当队列中没有相应类型的消息可以接收时将发生的事情。

调用成功时,该函数返回放到接收缓存区中的字节数,消息被复制到由msg_ptr指向的用户分配的缓存区中,然后删除消息队列中的对应消息。失败时返回-1.

6.4 消息队列控制函数 msgctl

int msgctl(int msgid, int command, struct msgid_ds *buf);  

command是将要采取的动作,它可以取3个值,

​ IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。

​ IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值

​ IPC_RMID:删除消息队列

buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:

struct msgid_ds    
{    
    uid_t shm_perm.uid;    
    uid_t shm_perm.gid;    
    mode_t shm_perm.mode;    
};  

成功时返回0,失败时返回-1.

简单的例子:

消息发送端: send.c

/*send.c*/  
#include    
#include    
#include    
#include    
#include    

#define MSGKEY 1024   

struct msgstru  
{  
   long msgtype;  
   char msgtext[2048];   
};  

main()  
{  
  struct msgstru msgs;  
  int msg_type;  
  char str[256];  
  int ret_value;  
  int msqid;  

  msqid=msgget(MSGKEY,IPC_EXCL);  /*检查消息队列是否存在*/  
  if(msqid < 0)
  {  
    msqid = msgget(MSGKEY,IPC_CREAT|0666);/*创建消息队列*/  
    if(msqid <0)
    {  
    printf("failed to create msq | errno=%d [%s]\n",errno,strerror(errno));  
    exit(-1);  
    }  
  }   

  while (1){  
    printf("input message type(end:0):");  
    scanf("%d",&msg_type);  
    if (msg_type == 0)  
       break;  
    printf("input message to be sent:");  
    scanf ("%s",str);  
    msgs.msgtype = msg_type;  
    strcpy(msgs.msgtext, str);  
    /* 发送消息队列 */  
    ret_value = msgsnd(msqid,&msgs,sizeof(struct msgstru),IPC_NOWAIT);  
    if ( ret_value < 0 ) {  
       printf("msgsnd() write msg failed,errno=%d[%s]\n",errno,strerror(errno));  
       exit(-1);  
    }  
  }  
  msgctl(msqid,IPC_RMID,0); //删除消息队列   
}

消息接收端 receive.c

/*receive.c */  
#include    
#include    
#include    
#include    
#include    

#define MSGKEY 1024   

struct msgstru  
{  
   long msgtype;  
   char msgtext[2048];  
};  

/*子进程,监听消息队列*/  
void childproc(){  
  struct msgstru msgs;  
  int msgid,ret_value;  
  char str[512];  

  while(1){  
     msgid = msgget(MSGKEY,IPC_EXCL );/*检查消息队列是否存在 */  
     if(msgid < 0){  
        printf("msq not existed! errno=%d [%s]\n",errno,strerror(errno));  
        sleep(2);  
        continue;  
     }  
     /*接收消息队列*/  
     ret_value = msgrcv(msgid,&msgs,sizeof(struct msgstru),0,0);  
     printf("text=[%s] pid=[%d]\n",msgs.msgtext,getpid());  
  }  
  return;  
}  

void main()  
{  
  int i,cpid;  

  /* create 5 child process */  
  for (i=0;i<5;i++){  
     cpid = fork();  
     if (cpid < 0)  
        printf("fork failed\n");  
     else if (cpid ==0) /*child process*/  
        childproc();  
  }  
}

7 信号量

7.1 背景知识

  1. 原子操作(atomic operation)

原子操作意为不可被中断的一个或一系列操作,也可以理解为就是一件事情要么做了,要么没做。而原子操作的实现,一般是依靠硬件来实现的。

  1. 同步与互斥

同步:在访问资源的时候,以某种特定顺序的方式去访问资源
互斥:一个资源每次只能被一个进程所访问。

同步与互斥是保证在高效率运行的同时,可以正确运行。大部分情况下同步是在互斥的基础上进行的。

  1. 临界资源

不同进程能够看到的一份公共的资源(如:打印机,磁带机等),且一次仅允许一个进程使用的资源称为临界资源

  1. 临界区

临界区是一段代码,在这段代码中进程将访问临界资源(例如:公用的设备或是存储器),当有进程进入临界区时,其他进程必须等待,有一些同步的机制必须在临界区段的进入点和离开点实现,确保这些共用资源被互斥所获得。

  1. 相关命令

    • ipcs -s 显示已存在的信号量
    • ipcrm -s 删除指定信号量

7.2 什么是信号量

为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。

信号量是一个特殊的变量,程序对其访问都是原子操作,为了正确地实现信号量,所以信号量通常是在内核中实现的。,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。

7.3 信号量工作原理

由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。

7.4 Linux的信号量机制

  1. 在System V中信号量并非是单个非负值,而必须将信号量定义为含有一个或多个信号量值的集合。当创建一个信号量时,要指定该集合中信号量值的数量。
  2. 创建信号量(semget)和对信号量赋初值(semctl)分开进行,这是一个弱点,因为不能原子地创建一个信号量集合,并且对该集合中各个信号量赋初值。
  3. 即使没有进程在使用I P C资源,它们仍然是存在的,要时刻防止资源被锁定,避免程序在异常情况下结束时没有解锁资源,可以使用关键字(SEM_UNDO )在退出时恢复信号量值为初始值。

7.5 相关函数

1 ftok()函数
#include 
#include 
key_t ftok(const char* path, int id);
  • ftok 函数把一个已存在的路径名和一个整数标识转换成一个key_t值,即IPC关键字
  • path 参数就是你指定的文件名(已经存在的文件名),一般使用当前目录。当产生键时,只使用id参数的低8位。
  • id 是子序号, 只使用8bit (1-255)
  • 返回值:若成功返回键值,若出错返回(key_t)-1
2 semget()函数

用来创建一个信号集,或者获取已存在的信号集。

#include 
#include 
#include 
int semget( key_t key, int nsems, int semflg);
  • key: 整数值(唯一非零), 所创建或打开信号量集的键值(ftok成果执行的返回值)。
  • nsems:创建的信号量集中的信号量个数,它的值几乎总是1, 该参数只在创建信号量时有效。
  • semflg :调用函数的操作类型,也可用于设置信号量集的访问权限,通过or运算使用。
    • IPC_CREAT | IPC _EXCL | 0666 :一般用于创建,可保证返回一个新的ID,同时制定权限为666
    • IPC_CREAT : 用于获取一个已经存在的ID
  • 返回值:成功返回信号量集的标识符,失败返回-1
3 semop()函数

操作一个或一组信号, 改变信号量的值, 也可以叫PV操作

#include 
#include 
#include 
int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:信号集的识别码,可以通过semget获取。

  • nsops:信号操作结构的数量,恒大于或等于1.

  • sops:是一个指针,指向一个信号量操作数组。信号量操作由结构体sembuf 结构表示如下:

    struct sembuf
    {
        unsigned short sem_num; 
        short  sem_op;  
        short  sem_flg; 
    };

    sembuf结构体参数说明:

    1. sem_num:操作信号在信号集中的编号,第一个信号的编号是0,最后一个信号的编号是nsems-1, 除非使用一组信息量, 否则它为0。

    2. sem_op:操作信号量

      信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,一个是+1,即V(发送信号)操作。

      • 若sem_op 为负(P操作), 其绝对值又大于信号的现有值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用权。
      • 若sem_op 为正(V操作), 该值会加到现有的信号内值上。通常用于释放所控制资源的使用权。
      • sem_op的值为0:如果没有设置IPC_NOWAIT,则调用该操作的进程或线程将暂时睡眠,直到信号量的值为0;否则进程或线程会返回错误EAGAIN。
    3. sem_flg: 信号操作标识,有如下两种选择:

      • IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
      • SEM_UNDO:程序结束时(正常退出或异常终止),保证信号值会被重设为semop()调用前的值。避免程序在异常情况下结束时未解锁锁定的资源,早成资源被永远锁定。造成死锁。
  • 返回值:成功执行时,都会回0,失败返回-1,并设置errno错误信息。

4 semctl()函数

该函数用来直接控制信号量信息, 用来初始化信号集,或者删除信号集。

#include 
#include 
#include 
int semctl(int semid, int semun, int cmd, ...);
  • semid:信号量集I P C 标识符。

  • semun:操作信号在信号集中的编号,第一个信号的号是0.

  • cmd:在semid指定的信号量集合上执行此命令。

    • IPC_SEAT:对此集合取semid_ds 结构,并存放在由arg.buf指向的结构中。
    • IPC_RMID:从系统中删除该信号量集合。
    • SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数。
  • 第四个参数是可选的,如果使用该参数,则其类型是semun,它是多个特定命令参数的联合(union):

    union semun
    {
        int val;    
        struct semid_ds * buf;
        unsigned short * array;
        struct seminfo * __buf;
    };
  • 返回值:成功返回一个正数,失败返回-1。

详细链接

你可能感兴趣的:(Linux)