linux环境高级编程-进程间通信IPC

 进程间通信

对应APUE第十五章——进程间通信。

进程间通信(IPCInterProcess Communication)分为:

  • PIPE(管道)
  • Socket(套接字)
  • XSI(System V)
    • 消息队列
    • 信号量数组
    • 共享内存

这些手段都是用于进程间通讯的,只有进程间通讯才需要借助第三方机制,线程之间的通讯是不需要借助第三方机制的,因为线程之间的地址空间是共享的。

1. 管道

1.1 管道概述

1.1.1 概念

管道是UNIX系统IPC的最古老形式,所有UNIX系统都提供此种通信机制,管道有以下两种局限性。

  1. 历史上,它们是半双工的(即数据只能在一个方向上流动),现在,某些系统提供全双工管道。
  2. 管道只能在具有公共祖先的两个进程之间使用。通常,一个管道由一个进程创建,在进程调用fork之后,这个管道就能在父进程和子进程之间使用了。

尽管有这两种局限性,半双工管道仍是最常用的IPC形式。每当在管道中键入一个命令序列,让shell执行时,shell都会为每一条命令单独创建一个进程,然后用管道将前一条命令进程的标准输出与后一条命令的标准输入相连接。

管道分为命名管道(FIFO)和匿名管道(PIPE),无论是哪种管道,都是由内核帮你创建和维护的。

本质是内核中的一块缓存区。

1.1.2 分类
  • 匿名管道:没有标识符,不能被其他进程找到,因此只能用于具有亲缘关系的进程间通信
  • 命名管道:有标识符,能被其他进程找到,因此可以用于同一主机上的任意进程间通信
1.1.3 管道特性
  • 管道中没有数据,继续读就会阻塞
  • 管道中数据满了,继续写就会阻塞
  • 管道中所有读端被关闭,继续写就会触发异常,导致程序退出
  • 管道中所有写端被关闭,继续读,则读取完管道中的数据后,将不再阻塞,而是返回0
1.1.4 命名管道独有特性
  • 若以只写方式打开管道文件,则会阻塞,直到管道被任意进程以读的方式打开
  • 若以只读方式打开管道文件,则会阻塞,直到管道被任意进程以写的方式打开
  • 因为一个管道如果不构成同时读写,就没有必要开辟缓存区
  • 匿名管道有的特性,命名管道也有

1.2 匿名管道

 匿名管道是通过调用pipe函数创建的。

#include <unistd.h>

int pipe(int pipefd[2]);

返回值:
    成功返回0
    失败返回-1

pipefd 是一个数组,表示管道的两端文件描述符,pipefd[0] 端作为读端,pipefd[1] 端作为写端。

pipe产生的是匿名管道,在磁盘的任何位置上找不到这个管道文件,而且匿名管道只能用于具有亲缘关系的进程之间通信。一般情况有亲缘关系的进程之间使用管道进行通信时,会把自己不用的一端文件描述符关闭。

注意:子进程打开的文件描述符与父进程的一致。

 代码示例

#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd[2];
    pid_t pid;
    char buf[1024] = {"hello world"};
    

    //pipe,创建一个匿名管道(匿名管道看不到)
    if (pipe(fd) < 0)
    {
        perror("fail to pipe!");
        return -1;
    }

    fflush(NULL);
    pid = fork();
    if (pid < 0)
    {
        perror("fail to fork!");
        return -1;
    }
    if (pid == 0)//child
    {
        close(fd[0]);
        write(fd[1], buf, strlen(buf));//写进管道
        return 0;
    }
    else if (pid > 0)//parent
    {
        close(fd[1]);
        read(fd[0], buf, sizeof(buf));//从管道读
        write(1, buf, strlen(buf));//将读的数据从终端打印出来
        return 0;
    }

    return 0;
}

运行结果:

lei@ubuntu:~/Desktop/ipc$ ./a.out 
hello worldlei@ubuntu:~/Desktop/ipc$

1.3 命名管道mkfifo

mkfifo函数用于创建命名管道,作用与匿名管道相同,不过可以在不同的进程之间使用,相当于对一个普通文件进行读写操作就可以了。

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
  • pathname:管道文件的路径和文件名。
  • mode:创建管道文件的权限。该mode还需要和umask做并运算来确定最后的管道文件权限。
  • 返回值:成功返回 0,失败返回 -1 并设置 errno

当用mkfifo创建FIFO时,要用open来打开它。

FIFO有以下两种用途:

  • shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件
  • 客户进程-服务器进程应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程二者之间传递数据。

管道必须凑齐读写双方才能正常运行

可以使用命令,来创建管道文件:

lei@ubuntu:~/Desktop/ipc$ mkfifo namefifo
lei@ubuntu:~/Desktop/ipc$ ls -l
total 4
prw-rw-r-- 1 lei lei   0 Oct 19 00:33 namefifo
-rw-rw-r-- 1 lei lei 781 Oct 19 00:30 pipe.c

由第一位p可知其为一个管道文件。

将时间重定向到命名管道中:

lei@ubuntu:~/Desktop/ipc$ date
Thu Oct 19 00:33:46 PDT 2023
lei@ubuntu:~/Desktop/ipc$ date > namefifo 

可以看到shell执行在这步没有结束,原因是我们重定向属于打开命名管道的写入端,但是命名管道必须打开读写两端才可以执行下去。

我们打开读端:

lei@ubuntu:~/Desktop/ipc$ cat namefifo 
Thu Oct 19 00:34:28 PDT 2023
lei@ubuntu:~/Desktop/ipc$ 

可以看到读端直接结束 

lei@ubuntu:~/Desktop/ipc$ date > namefifo 
lei@ubuntu:~/Desktop/ipc$ 

写段也结束了

lei@ubuntu:~/Desktop/ipc$ ls -l
total 4
prw-rw-r-- 1 lei lei   0 Oct 19 00:33 namefifo
-rw-rw-r-- 1 lei lei 781 Oct 19 00:30 pipe.c

再次观察到namefifo文件大小依旧是0,因为他只是一个名字,具体数据在内核的缓存区中。

(管道文件只是一个标识符,一个名字,只有在打开的时候,才会开辟管道空间。)


代码示例1

head.h 

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define MY_FIFO "./fifo"

server.c 

#include "head.h"

#define BUFFSIZE 10

int main(int argc, char const *argv[])
{
    umask(0002);

    if (mkfifo(MY_FIFO, 0664) < 0)
    {   
        perror("faill to fifo");
        return -1; 
    }   

    int fd = open(MY_FIFO, O_RDONLY);
    if (fd < 0)
    {   
        perror("fail to open");
        return -1; 
    }   

    while(1)
    {   
        char buf[BUFFSIZE];
    
        memset(buf, 0, sizeof(buf));

        //从管道读取数据
        ssize_t size = read(fd, buf, BUFFSIZE - 1); 
        if (size < 0)
        {
            perror("read error");
            break;
        }
        else if (size == 0)
        {
            printf("client quit\n");
            break;
        }
        else
        {
            printf("client -> %s\n", buf);
        }
    }   

    close(fd);
    
    return 0;
}

 client.c

#include "head.h"

#define BUFFSIZE 10

int main(int argc, char const *argv[])
{
    int fd = open(MY_FIFO, O_WRONLY);
    if (fd < 0)
    {   
        perror("fail to open");
        return -1; 
    }   

    while(1)
    {   
        printf("请输入-> ");
        fflush(stdout);
        char buf[BUFFSIZE];

        //从标准输入端读取buffsize-1个数据放buf,,read会读取‘\n’
        ssize_t s = read(0, buf, BUFFSIZE  - 1); 
        if(s > 0) {
            if (buf[s-1] == '\n')
            {
                buf[s-1] = '\0';
            }
            else 
            {
                buf[s] = '\0';
            }
 //           printf("buf = %s,strlen(buf) = %d\n", buf,strlen(buf));
            write(fd, buf, strlen(buf));//写入管道
        }
    
    }   

    close(fd);
    
    return 0;
}

执行结果:

lei@ubuntu:~/Desktop/ipc/fifo$ ./server 
client -> hello
client -> hello wor
client -> ld
client -> i love yo
client -> u
client -> nihao
client -> hi
client quit
lei@ubuntu:~/Desktop/ipc/fifo$ ./client 
请输入-> hello
请输入-> hello world
请输入-> 请输入-> i love you
请输入-> 请输入-> nihao
请输入-> hi
请输入-> ^C

代码示例2

使用fifo进行客户端-服务端进行通信

2. XSI IPC

XSI IPC函数是紧密地基于System V的IPC函数的。

system V:同一主机内的进程间通信方案,在OS层面专门为进程间通信设计的方案。

system V标准下的三种通信方式:

  • 共享内存
  • 消息队列
  • 信号量

2.1 相关命令

  • ipcs命令可以查看 XSI IPC 的使用情况。
  • ipcrm命令可以删除指定的 XSI IPC。
lei@ubuntu:~$ ipcs

------ Message Queues -------- #消息队列
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------  #共享内存
key        shmid      owner      perms      bytes      nattch     status              

------ Semaphore Arrays --------  #信号量数组
key        semid      owner      perms      nsems

通过 ipcs 命令可以看出来,命令的输出结果分为三个部分,第一部分是系统中当前开辟的共享内存(shm),第二部分是信号量数组(sem),第三部分是消息队列(msg)

可以看到,不论是哪一部分,都有一列叫做key,使用 XSI IPC 通信的进程就是通过同一个 key 值操作同一个共享资源的。这个 key 是一个正整数,与文件描述符不同的是,生成一个新 key 值时它不采用当前可用数值中的最小值,而是类似生成进程 ID 的方式,key 值连续的加 1,直至达到一个整数的最大正值,然后再回转到 0 从头开始累加。

  • ipcs -l 查看IPC相关的限制 
lei@ubuntu:~/Desktop/ipc/fifo$ ipcs -l

------ Messages Limits --------
max queues system wide = 32000
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384

------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 18014398509465599
max total shared memory (kbytes) = 18014398509481980
min seg size (bytes) = 1

------ Semaphore Limits --------
max number of arrays = 32000
max semaphores per array = 32000
max semaphores system wide = 1024000000
max ops per semop call = 500
semaphore max value = 32767

2.2 标识符和键

每个内核中的IPC结构(消息队列、信号量或共享存储)都用一个非负整数的标识符(identifier)加以引用。例如,要向一个消息队列发送消息或者从一个消息队列取消息,只需要知道其队列标识符。标识符是IPC对象的内部名,为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案。为此,每个 IPC对象都与一个(key)相关联,将这个键作为该对象的外部名。

无论何时创建IPC结构(通过调用msgget、semget或shmget 创建),都应指定一个键。这个键的数据类型是基本系统数据类型key_t,通常在头文件<sys/types.h>中被定义为长整型。这个键由内核变换成标识符

有多种方法使客户进程和服务器进程在同一IPC结构上汇聚。

  • 方法1:服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(如一个文件)以便客户进程取用。键IPC_PRIVATE保证服务器进程创建一个新IPC结构。这种技术的缺点是:文件系统操作需要服务器进程将整型标识符写到文件中,此后客户进程又要读这个文件取得此标识符。IPC_PRIVATE键也可用于父进程子关系。父进程指定IPC_PRIVATE创建一个新IPC结构,所返回的标识符可供fork后的子进程使用。接着,子进程又可将此标识符作为exec函数的一个参数传给一个新程序。

  • 方法2:可以在一个公用头文件中定义一个客户进程和服务器进程都认可的键。然后服务器进程指定此键创建一个新的IPC结构。这种方法的问题是该键可能已与一个IPC结构相结合,在此情况下,get 函数(msgget、semget 或shmget)出错返回。服务器进程必须处理这一错误,删除已存在的IPC结构,然后试着再创建它。

  • 方法3:客户进程和服务器进程认同一个路径名和项目ID(项目ID是0~255之间的字符值),接着,调用函数ftok将这两个值变换为一个键。然后在方法2中使用此键。ftok提供的唯一服务就是由一个路径名和项目ID产生一个键。
#include <sys/ipc.h>

key_t ftok(const chat *path, int id);

返回值:
    成功返回键
    失败返回(key_t)-1

path参数必须引用一个现有的文件。当产生键时,只使用id参数的低8位。对于不同文件的两个路径名,如果使用同一项目ID,可能产生相同的键。

3个get 函数(msgget、semget和shmget)都有两个类似的参数:一个key和一个整型flag。在创建新的IPC结构(通常由服务器进程创建)时,如果key是IPC_PRIVATE或者和当前某种类型的IPC结构无关,则需要指明 flag 的IPC_CREAT标志位。为了引用一个现有队列(通常由客户进程创建),key必须等于队列创建时指明的key的值,并且IPC_CREAT必须不被指明。

注意,决不能指定IPC_PRIVATE作为键来引用一个现有队列,因为这个特殊的键值总是用于创建一个新队列。

如果希望创建一个新的IPC结构,而且要确保没有引用具有同一标识符的一个现有IPC结构,那么必须在flag中同时指定IPC_CREATIPC_EXCL位。这样做了以后,如果IPC结构已经存在就会造成出错,返回EEXTST

2.3 消息队列

消息队列是消息的链接表,存储在内核中,由消息队列标识符标识。

本质:内核中的一个优先级队列

msg、sem 和 shm 都有一系列函数遵循下面的命名规则:

  • xxxget():创建或引用,将key转换为标识符id
  • xxxop():相关操作
  • xxxctl():其它的控制或销毁

创建每一个队列都会有 一个msqid_ds结构体与之关联:

struct msqid_ds {
    struct ipc_perm msg_perm; // 保存ipc权限信息的结构体
    msgqnum_t msg_qnum;
    msglen_t msg_qbytes;
    pid_t msg_lspid;
    pid_t msg_lrpid;
	// ...
}

相关系统调用

// msgget - get a System V message queue identifier

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

// 函数的作用是创建或引用一个消息队列,消息队列是双工的,两边都可以读写。
int msgget(key_t key, int msgflg);
  • key:IPC内核标识符的外部方案实现,拥有相同 key 的双方才可以通信。key 值必须是唯一的,ftok 函数可以用于获取 key
  • msgflg:特殊要求,没有写0
  • 返回:非负的队列ID或出错-1

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

// 将 msgp 指向的结构体存放到 msgid 的消息队列中,这段空间有 msgz 个字节大小,msgz 的值要减掉强制的成员 mtype 的大小(sizeof(long))。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

// 从 msgid 这个消息队列中取出 msgp 结构体数据,msgp 的大小是 msgsz,msgflg 是特殊要求,没有特殊要求可以写 0。 
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
               int msgflg);
// msgtyp可以指定接收哪一种信息“
// 0:  返回队列的第一个信息
// >0: 返回队列中消息类型为 type 的第一个消息。
// <0: 返回队列中消息类型值小于等于 type 绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。


/* msgp 指向的结构体的成员定义要类似 msgbuf 这个结构体,第一个成员必须是 long 类型的 mtype,并且必须是 > 0 的值 */
struct msgbuf {
    long mtype;       /* 消息类型,必须 > 0 */
    char mtext[512];  /* 消息数据字段 */
};
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
// msqctl函数对队列执行多种操作。它和另外两个与信号量及共享存储有关的函数(semctl 和shmctl)都是XSI IPC的类似于ioctl的函数(亦即垃圾桶函数)。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

cmd参数指定对msqid指定的队列要执行的命令:

  • IPC_STAT:取此队列的msqid_ds结构,并将它存放在buf指向的结构中。
  • IPC_RMID:从系统中删除该消息队列以及仍在该队列中的所有数据。这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将得到EIDRM错误。此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.cuid或msg_perm.uid;另一种是具有超级用户特权的进程。

代码示例

 代码示例

send.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;
    char buf[1024] = {0};
    
    key = ftok("./ipcfile", 100);
    if (key == -1)
    {
        perror("fail to ftok!");
        return -1;
    }

    msgid = msgget(key, IPC_CREAT|0664);
    if (msgid == -1)
    {
        perror("fail to msgget!");
        return -1;
    }
    
    while(1)
    { 
        memset(buf, 0, sizeof(buf));
        fgets(buf, sizeof(buf),stdin);//会接收‘\n’
  
        int size = msgsnd(msgid, &buf, strlen(buf), 0);
        if (size < 0)
        {
            perror("fail to msgsnd!");
            return -1;
        }
        printf("buf = %s", buf);
    }

    msgctl(msgid, IPC_RMID, NULL);

    return 0;
}

recv.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    key_t key;
    int msgid;
    char buf[1024] = {0};
    
    key = ftok("./ipcfile", 100);
    if (key == -1)
    {
        perror("fail to ftok!");
        return -1;
    }

    msgid = msgget(key, IPC_CREAT|0664);
    if (msgid == -1)
    {
        perror("fail to msgget!");
        return -1;
    }

    while(1)
    {    
        memset(buf, 0, sizeof(buf));

        ssize_t size = msgrcv(msgid, &buf, sizeof(buf), 0, 0);
        if (size < 0)
        {
            perror("fail to msgrcv!");
            return -1;
        }       
        printf("buf = %s", buf);

    }

    msgctl(msgid, IPC_RMID, NULL);

    return 0;
}

执行结果:

lei@ubuntu:~/Desktop/ipc/msg$ gcc send.c -o send
lei@ubuntu:~/Desktop/ipc/msg$ ./send 
hello
buf = hello
how
buf = how
are
buf = are
you
buf = you
lei@ubuntu:~/Desktop/ipc/msg$ gcc recv.c -o recv
lei@ubuntu:~/Desktop/ipc/msg$ ./recv 
buf = hello
buf = how
buf = are
buf = you

如果不开启接收端,发送端发送的数据会保存在缓存中,等待接收端开启,一次性将数据接收。

缓存区的大小是有所限制的, POSIX message queues     (bytes, -q) 819200 

lei@ubuntu:~/Desktop/ipc$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 15400
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 15400
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

可以使用ulimit -q 改变缓存区大小。

2.4 共享内存

作用:用于多个进程之间的数据共享

特性:最快的进程间通信方式,生命周期随内核(并不会随着打开的进程退出而释放)

原理:开辟一块物理内存,然后多个进程将这块内存都通过页表映射到自己的虚拟地址空间中,通过虚拟地址直接访问物理内存中数据。(管道是将进程A的数据拷贝到管道缓存区,进程B从缓存区将数据拷贝到自己的地址空间。共享内存相较于没有两次拷贝过程,所以最快)

使用共享存储时要掌握的唯一窍门是,在多个进程之间同步访问一个给定的存储区。若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应当去取这些数据。通常,信号量用于同步共享存储访问。(也可以用记录锁或互斥量)

我们已经看到了共享存储的一种形式,就是在多个进程将同一个文件映射到它们的地址空间的时候。XSI共享存储和内存映射的文件的不同之处在于,前者没有相关的文件。XSI共享存储段是内存的匿名段

相关系统调用 

#include <sys/ipc.h>
#include <sys/shm.h>

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

返回值:
    成功返回共享内存ID
    失败返回-1

 shmget获得一个共享内存 

参数:

key:IPC对象名

size:共享内存空间大小,实现通常将其向上取为系统页长的整数倍

shmflg: IPC_CREAT |  IPC_EXECL | 0664

  • IPC_CREAT  若果不存在则创建并打开,存在则打开
  • IPC_EXECL  与IPC_CREAT搭配使用,共享内存不存在则创建打开,若存在则报错返回
  • mode_flags  共享内存的访问权限 0664 
#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

返回值:    
     成功返回映射到共享内存空间中的地址
     失败返回-1     

shmat 将进程空间地址映射到共享内存 

参数:
shmid:共享内存ID号 
shmaddr:
            NULL    系统选择一个合适的地址映射                       

int shmdt(const void *shmaddr);

返回值:
     成功返回0 
     失败返回-1 

shmdt解除映射  

参数:
        shmaddr:共享内存空间的映射首地址,也是shmat的返回值

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

返回值:
      成功返回0 
      失败返回-1 

shmctl向共享内存中发送一条命令 

参数:
shmid:共享内存ID号 
cmd:
        IPC_STAT 
        IPC_SET 
        IPC_RMID    删除共享内存 
buf: 用于设置获取共享内存信息,当cmd是IPC_RMID 时被忽略
       NULL

shmctl的RMID只是删除标记共享内存需要删除,这时候并不会立即删除共享内存,而是拒绝后续新的映射连接,当映射连接为0的时候则有系统完成删除。

代码示例         

2.5 信号量

信号量与已经介绍过的IPC(管道、FIFO、消息列队、共享内存)不同。它是一个计数器,用于为多个进程提供对共享数据对象的访问。

为了获得共享资源,进程需要执行下列操作:

  1. 测试控制该资源的信号量。
  2. 若此信号量的值为正,则进程可以使用该资源。在这种情况下,进程会将信号量值减1,表示它使用了一个资源单位。
  3. 否则,若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0。进程被唤醒后,它返回至步骤1。

当进程不再使用由一个信号量控制的共享资源时,该信号量值增1。如果有进程正在休眠等待此信号量,则唤醒它们。

为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。

常用的信号最形式被称为二元信号量(binary semaphore)。它控制单个资源,其初始值为1。但是,一般而言,信号量的初值可以是任意一个正值,该值表明有多少个共享资源单位可供共享应用。

相关系统调用

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

// 创建或获得一个信号量id
int semget(key_t key, int nsems, int semflg);
  • key:具有亲缘关系的进程之间可以使用一个匿名的 key 值,key 使用宏 IPC_PRIVATE 即可。
  • nsems:表示有多少个信号。信号量实际上是一个计数器,所以如果设置为 1 可以用来模拟互斥量。
  • semflgIPC_CREAT 表示创建信号量,同时需要按位或一个权限,如果是匿名 IPC 则无需指定这个宏,直接给权限就行了。
  • 成功返回 sem ID,失败返回 -1 并设置 errno。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

// 用来控制或销毁信号量
int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量id
  • semnum:信号量数组的下标
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

// 操作信号量
int semop(int semid, struct sembuf *sops, unsigned nsops);

struct sembuf {
    unsigned short sem_num; /* 对第几个资源(数组下标)操作 */
    short sem_op; /* 取几个资源写负数几(不要写减等于),归还几个资源就写正数几 */
    short sem_flg; /* 特殊要求 */
};
  • sops:结构体数组起始位置;
  • nsops:结构体数组长度;
  • 返回值:成功返回0,失败返回-1并设置 errno。

代码示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值