Linux进程间通信详解-程序员宅基地

技术标签: Linux  运维  进程间通信  linux  共享内存  管道  开发语言  

进程间通信

进程间通信介绍

  1. 进程间通信目的
  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止
    时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另
    一个进程的所有陷入和异常,并能够及时知道它的状态改变。
  1. 进程间通信发展
  • 管道
  • System V进程间通信
  • POSIX进程间通信
  1. 进程间通信分类
  • 管道
    • 匿名管道pipe
    • 命名管道
  • System V IPC
    • System V 消息队列
    • System V 共享内存
    • System V 信号量
  • POSIX IPC
    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

一. 管道

1. 管道的基本概念

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间(匿名管道),完成数据传递。调用pipe系统函数即可创建一个匿名管道。

有如下特质:

  1. 其本质是一个伪文件(实为内核缓冲区)
  2. 由两个文件描述符引用,一个表示读端,一个表示写端。
  3. 规定数据从管道的写端流入管道,从读端流出。

ps: 管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

  • 管道提供流式服务

  • 一般而言,进程退出,管道释放,所以管道的生命周期随着进程

  • 一般而言,内核会对管道操作进行同步互斥 (互斥:同一时间要么写要么读)

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

管道的局限性

  • 数据自己读不能自己写。
  • 数据一旦被读走,便不在管道中存在,不可反复读取。
  • 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
  • 只能在有公共祖先的进程间使用管道。
  • 常见的通信方式有,单工通信、半双工通信、全双工通信。

管道读写规则

  1. 当没有数据可读时
    • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  2. 当管道满的时候
    • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  3. 管道对应的文件描述符被关闭
    • 写端被关闭,则从管道中读取完所有数据后,继续read会返回0,不再阻塞
    • 读端被关闭,write 操作会产生信号SIGPIPE,进而可能导致write进程退出
  4. 原子性
    • 写入的数据量 不大于 PIPE_BUF时,linux将保证写入的原子性。
    • 写入的数据量 大于 PIPE_BUF时,linux将不再保证写入的原子性。
  5. 其他注意事项
    • 管道的生命周期随进程,本质是内核中的缓冲区,命名管道文件只是标识,用于让多个进程找到同一块缓冲区,删除管道文件后,之前已经打开管道的进程依然可以通信

总而言之:

  • 管道是最简单,效率最差的一种通信方式。

  • 管道本质上就是内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据。

  • 如果想要实现两个进程通过管道来通信,则需要让创建管道的进程fork子进程,这样子进程们就拥有了- 父进程的文件描述符,这样子进程之间也就有了对同一管道的操作。

  • 缺点

    • 半双工通信,一条管道只能一个进程写,一个进程读。
    • 一个进程写完后,另一个进程才能读,反之同理。

2. 管道的创建

①. 匿名管道

pipe

头文件: unistd.h

int pipefd[2]={
    0};
int pipe(int pipefd[2]);
  1. 功能: 创建一无名管道
  2. 参数:pipefd:文件描述符数组
    • pipefd[ 0 ]:读端文件描述符
    • pipefd[ 1 ]:写端文件描述符
  3. 返回值:
    • 成功返回 0 ;
    • 失败返回 -1 且 设置错误码 error

pipe函数的应用

#include<iostream>
#include<cassert>
#include<unistd.h>
#include<cstdio>  //等价于 stdio.h
#include<cstring>  //等价于 string.h  以c++的风格将c语言的string头文件改写
int main()
{
    
    int pipefd[2]={
    0};  
    int n = pipe(pipefd);  //创建管道
    assert(n == 0);   //在debug下,assert会存在 realease 下,该行代码不起作用
    (void)n;   //防止编译器在realease下告警 n被定义未使用
    
    cout<<"pipefd[0]:" <<pipefd[0]<<", pipefd[1]:"<<pipefd[1]<<endl;
    
    return 0;
}

创建匿名管道图文详解:

  1. 父进程调用pipe函数
    在这里插入图片描述

  2. fork创建子进程
    在这里插入图片描述

  3. 子进程关闭读端,父进程关闭写端
    在这里插入图片描述

  • 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
  • 从管道写端写入的数据会存在内核缓冲,直到数据从管道中被读取
  • 父进程读写端都打开本质是为了让子进程继承下来,最后确保管道只能进行单向通信
  • 父子进程通信可不可以创建全局缓冲区来完成通信呢? 不可以 ! 进程运行具有独立性,写时拷贝
②. 命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道
命名管道是一种特殊类型的文件

注意:管道文件是以 p 开头的,如图:

在这里插入图片描述

创建命名管道

  1. 命令行上创建

    #include <sys/stat.h>  //头文件
    mkfifo filename  //创建一个FIFO命名管道文件
    

    命名管道可以从命令行上创建

  2. 程序内创建

    //命名管道也可以从程序里创建
    int mkfifo(const char *filename,mode_t mode);
    //             文件名            文件权限
    int main(int argc, char *argv[])
    {
          
    	mkfifo("p2", 0644);
     	return 0;
    }
    
  3. 功能: 创建一命名管道

  4. 参数

    • filename:指定文件名
    • mode: 指定文件权限
  5. 返回值:

    • 成功返回0
    • 失败返回-1。
    • 如果返回EEXIST则表示管道的标识符文件已经存在了不需要重新创建。

命名管道的特性

  • 如果管道文件被只 打开,会阻塞,直到管道文件也被 方式打开
  • 如果管道文件被只 打开,会阻塞,直到管道文件也被 方式打开


匿名与命名管道的区别
  • 匿名管道pipe 函数创建并打开。
  • 命名管道mkfifo 函数创建,打开用 open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。

3. 删除管道

删除管道文件可以用如下命令

unlink fifo  //删除管道文件
rm -f fifo

4. 管道的4种特殊情况

  • 写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被阻塞挂起,直到管道里面有数据后,读端进程才会被唤醒。
  • 读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被阻塞挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
  • 写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
  • 读端进程将读端关闭,而写端进程还在一直向管道写入数据,没有进程读取,那么写入的数据就没有意义,那么操作系统会将写端进程杀掉。

二、system V

  1. 管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份资源。

  2. system V IPC提供的通信方式有以下三种:

    • system V共享内存
    • system V消息队列
    • system V信号量
  3. 其中,system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴。

​   共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到系统内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,减少了拷贝的次数,加快的程序的运行。

共享内存示意图
在这里插入图片描述

共享内存数据结构

truct shmid_ds {
    struct ipc_perm     shm_perm;   /* operation perms */
    int         shm_segsz;  /* size of segment (bytes) */
    __kernel_time_t     shm_atime;  /* last attach time */
    __kernel_time_t     shm_dtime;  /* last detach time */
    __kernel_time_t     shm_ctime;  /* last change time */
    __kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
    __kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
    unsigned short      shm_nattch; /* no. of current attaches */
    unsigned short      shm_unused; /* compatibility */
    void            *shm_unused2;   /* ditto - used by DIPC */
    void            *shm_unused3;   /* unused */
};

1. 共享内存( shm )

shm基本概念

​ 共享内存本质上就是内存中的一块区域,用于进程间通信使用。该内存空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据块,同时还会创建相应结构体来记录该共享内存属性,以便于管理。

因此,共享内存不只有一份,可以根据需求申请多个。

进程之间进行通信的时候,会获取 到共享内存的地址,写端进程写入数据,读端进程通过直接访问内存完成数据读取。

  • 共享内存优点
    相比于管道而言,共享内存不仅能够用于非父子进程之间的通信,而且访问数据的速度也比管道要快。这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。

  • 共享内存缺点
    用于进程间通信时,共享内存本身不支持阻塞等待操作。这是因为当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。因为共享内存本质是进程直接访问内存,无法主动停止读取,如果读端不加以限制,那么将持续读取数据。同理,写端也会持续写入数据。换句话说,共享内存本身没有访问控制。

    ​      共享内存**没有提供同步的机制**,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。
    

其他注意事项

  • 共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除
  • 共享内存生命周期随内核,只要不删除,就一直存在于内核中,除非重启系统(当然这里指的是非手动操作,可以手动删除)
shm函数

头文件:

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

ftok

函数 ftok 把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。该 IPC 为后续创建共享内存做准备。

该函数需要包含以下两个头文件:

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

fork函数原型

//把从pathname导出的信息与id的低序8位组合成一个整数IPC键
key_t ftok(const char *pathname, int proj_id);

//pathname:指定的文件,此文件必须存在且可存取
//proj_id:计划代号(project ID)

返回值(key_t 一般为32位的 int 型的重定义):

  • 成功:返回 key_t (即IPC 键值)
  • 出错:返回 -1,错误原因存于error中

shmget

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

//示例
int main()
{
    
    key_t n = ftok("/Linux/code",0x11223344);
    int id = shmget(n , 4096 , IPC_CREAT | IPC_EXCL | 0664);
}
  1. 功能:用来创建共享内存

  2. 参数

    • key: 这个共享内存段名字(IPC 值,即key_t )

    • size: 共享内存大小,一般设置为 4096 的倍数

    • shmflg: 创建共享内存的方式以及设置权限,由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

      //IPC_CREAT  可以单独使用
      //如果共享内存不存在,则开辟内存,函数返回值是新开辟的共享内存的ID
      //如果已经存在,则沿用已有的共享内存,函数返回值是已有的共享内存的
      
      //IPC_EXCL  无法单独使用
      //需要配合 IPC_CREAT 使用,即 IPC_CREAT | IPC_EXCL               
      
      //IPC_CREAT | IPC_EXCL 
      //如果共享内存不存在,则重新开辟,函数返回值是新开辟的共享内存的ID
      //如果已经存在,则报错
      
      //IPC_CREAT | IPC_EXCL | 0664
      //开辟共享内存的同时,设置共享内存的访问权限
      
  3. 返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat

void *shmat(int shmid, const void *shmaddr, int shmflg);
//                            一般为nullptr     一般为0

//示例
char* = (char*)shmat(shmid,nullptr,0);
  1. 功能:挂接共享内存至进程

  2. 参数:

    • shmid: 通过 shmget 创建的共享内存的返回值

    • shmaddr: 指定挂接地址, 通常传入 nullptr 即可, 让OS自动选择挂接地址

    • shmflg: 通常为0即可

  3. 返回值

    • 成功:返回一个void* 指针, 这里类似于malloc, 返回值需要根据使用来强制类型转换, 返回的地址是共享内存的起始地址,
    • 失败:如果失败则返回 (void*) -1, 并且错误码 errno 被设置

shmdt

int shmdt(const void *shmaddr);
  1. 功能:将共享内存段与当前进程脱离
  2. 参数
    • shmaddr: 由 shmat 所返回的指针
  3. 返回值
    • 成功:返回 0
    • 失败:返回 -1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//                     IPC_RMID           nullptr
  1. 功能:⽤于控制共享内存

  2. 参数

    • shmid:由shmget返回的共享内存标识码

    • cmd:将要采取的动作(有三个可取值)

      // IPC_ STAT:把shmid_ ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值
      //IPC_ SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
      //IPC_RMID:删除共享内存段
      
    • buf: 指向⼀个保存着共享内存的模式状态和访问权限的数据结构。

  3. 返回值

    • 成功返回 0
    • 失败返回 -1

2. 消息队列( msg )

msg基本概念

消息队列是消息的链表,存放在内核中并由消息队列标识符表示。
  消息队列提供了一个从一个进程向另一个进程发送数据块的方法,每个数据块都可以被认为是有一个类型,接受者接受的数据块可以有不同的类型。
  但是同管道类似,它有一个不足就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数(MSGMNB),系统上消息队列的总数上限(MSGMNI)。可以用cat /proc/sys/kernel/msgmax查看具体的数据。
  内核为每个IPC对象维护了一个数据结构struct ipc_perm,用于标识消息队列,让进程知道当前操作的是哪个消息队列。每一个msqid_ds表示一个消息队列,并通过msqid_ds.msg_first、msg_last维护一个先进先出的msg链表队列,当发送一个消息到该消息队列时,把发送的消息构造成一个msg的结构对象,并添加到msqid_ds.msg_first、msg_last维护的链表队列。在内核中的表示如下:

msg函数

头文件:

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

msgget

int msgget(key_t key, int msgflag);

//key =ftok()       IPC_CREAT|IPC_EXCL|0666
//例:  int msqid = msgget(key, IPC_CREAT | 0666);
  1. 功能:创建和访问一个消息队列

  2. 参数

    • key:某个消息队列的名字,用ftok()产生

    • msgflag:有两个选项 IPC_CREAT和IPC_EXCL,单独使用IPC_CREAT,如果消息队列不存在则创建之,如果存在则打开返回;单独使用IPC_EXCL是没有意义的;两个同时使用,如果消息队列不存在则创建之,如果存在则出错返回。

  3. 返回值:成功返回一个非负整数,即消息队列的标识码,失败返回-1

msgsnd

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

struct msgbuf {
    
    long mtype;       /* 标志,代表接收特定标记的数据块 */
    char mtext[1];    /* message data */
};

//例:
int send_msg(int msqid, int m_type, char *in)
{
                       //#define cilent_type 1
    struct my_msgbuf buf;
    buf.mtype = m_type;
    strncpy(buf.mtext, in, sizeof in);
    int n = msgsnd(msqid, (void *)&buf, sizeof buf.mtext, 0);
    if (n < 0)
    {
    
        cerr << "msg_send error! eoorr:" << errno << " strerror:" << strerror(errno) << endl;
        exit(1);
    }
    return 1;
}
  1. 功能:把一条消息添加到消息队列中

  2. 参数

    • msgid:由msgget函数返回的消息队列标识码
    • msgp:指针指向准备发送的消息
    • msgze:msgp指向的消息的长度(不包括消息类型的long int长整型)
    • msgflg:默认为0
  3. 返回值:成功返回0,失败返回-1

msgrcv

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  1. 功能:是从一个消息队列接受消息
  2. 参数
    • msgid:由msgget函数返回的消息队列标识码
    • msgp:指针指向准备发送的消息
    • msgz:msgp指向的消息的长度(不包括消息类型的long int长整型)
    • msgtyp接收指定标记的数据块
    • msgflg:默认为0
  3. 返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1

msgctl

int msgctl(int msqid, int cmd, struct msqid_ds *buf);: int n = msgctl(msgid, IPC_RMID, nullptr);
  1. 功能:消息队列的控制函数

  2. 参数

    • msqid:由msgget函数返回的消息队列标识码

    • cmd:有三个可选的值,在此我们使用IPC_RMID

    • buf:一般为 nullptr

      //IPC_STAT 把msqid_ds结构中的数据设置为消息队列的当前关联值
      //IPC_SET 在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
      //IPC_RMID 删除消息队列
      
  3. 返回值:成功返回0,失败返回-1

3. 信号量

sem函数

semget

int semget(key_t key, int num_sems, int sem_flags);
  1. 功能:它的作用是创建一个新信号量或取得一个已有信号量。

  2. 参数

    • key:整数值,可以理解为文件目录索引值、不同于id为该文件id名。

    • num_sems: 指定需要的信号量数目,它的值几乎总是1。

    • sem_flags:一组标志,当想要当信号量不存在时创建一个新的信号量 IPC_CREAT|0666

  3. 返回值:成功返回一个相应信号量集ID,失败返回-1.

semop

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
  1. 功能:它的作用是改变信号量的值,进行PV操作的函数。原型为:

  2. 参数

    • sem_id: 是由semget返回的信号量标识符,

    • sem_opa:信号量结构体struct sembuf指针,该指针改变后的信号量

    • num_sem_ops: struct sembuf变量成员数量

//sembuf结构的定义如下:

struct sembuf{
    
    short sem_num;//除非使用一组信号量,否则它为0
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
                 //一个是+1,即V(发送信号)操作。
    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
                  //并在进程没有释放该信号量而终止时,操作系统释放信号量
};

​ 由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv)

     P操作:sem_op= -1 <0,使得共享临界资源上锁,其它进程不得访问

     V操作:sem_op= 1 >0,使得共享临界资源解锁,其它进程可以访问

    semctl在初始化信号量的时候,union semum结构体中val就是信号量的初始化值,一般==1;

进行PV操作时,semop函数把 struct sembuf结构体中设置好的参数储存,此时val+1或-1,从而达到加锁解锁的目的,控制进程访问。

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

semctl函数

int semctl(int sem_id, int sem_num, int cmd, ...);
  1. 功能:该函数用来直接控制信号量信息,初始化和删除信号量
  2. 参数:
    • sem_id:信号量标示符
    • sem_num:信号量集中有多个信号量,表示第几个信号量
    • cmd:SETVAL 用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
      IPC_RMID 用于删除一个已经无需继续使用的信号量标识符。

三、指令操作

  • 查看相关通信方式

    ipcs -m  //查看共享内存
    ipcs -q  //查看消息队列
    ipcs -s  //查看信号量
    

    ① 查看共享内存
    在这里插入图片描述

    ② 查看消息队列
    在这里插入图片描述
    ③ 查看信号量
    在这里插入图片描述

    nattch: 当前有多少进程挂接到该共享内存

  • 删除相关通信方式

    ipcrm -m [shmid]
    ipcrm -q [msgid]
    ipcrm -sv  [semid]
        
    ipcrm -a
    //使用ipcrm -a选项可以删除所有进程间通信资源
    

四、内核IPC结构

结构代码

struct ipc_ids{
    
		int size;   /*entries数组的大小*/
		int in_use;	/*entries数组已使用的元素个数*/
		int max_id;
		unsigned short  seq;
		unsigned short seq_max;
		struct semaphore sem; /*控制对ipc_ids结构的访问*/
		spinlock_t ary; 		/*自旋锁控制对数组entries的访问*/
		
    	struct ipc_id* entries;
     	//柔性数组,可以一直扩大大小
    	//ipc_id_ary [size,*p[0/1/2/3];
      	// *p[0] *p[1] 指向 shmid_kerne  sem_array  msg_queue  这些数据结构
	};

	struct ipc_id{
     struct ipc_perm *p;};

ipc_perm

 struct ipc_perm {
    
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };

shmid_kerne

 struct shmid_ds {
    
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

msg_queue

struct msqid_ds {
    
               struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };


struct msqid_ds {
     
	struct ipc_perm msg_perm; 
	struct msg *msg_first; /* 队列上第一条消息,即链表头*/ 
	struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */ 
	time_t msg_stime; /* 发送给队列的最后一条消息的时间 */ 
	time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */ 
	time_t msg_ctime; /* 最后修改队列的时间*/ 
	ushort msg_cbytes; /*队列上所有消息总的字节数 */ 
	ushort msg_qnum; /*在当前队列上消息的个数 */ 
	ushort msg_qbytes; /* 队列最大的字节数 */ 
	ushort msg_lspid; /* 发送最后一条消息的进程的pid */ 
	ushort msg_lrpid; /* 接收最后一条消息的进程的pid */ 
	};

sem_array

struct semid_ds {
    
	struct ipc_perm sem_perm;	/*IPC权限 */
	long	sem_otime; 		/* 最后一次对信号量操作(semop)的时间 */
	long	sem_ctime;		/* 对这个结构最后一次修改的时间 */
	struct sem		/*sem_base;		/* 在信号量数组中指向第一个信号量的指针 */
	struct sem_queue *sem_pending;		/* 待处理的挂起操作*/
	struct sem_queue **sem_pending_last;	 /	* 最后一个挂起操作 */
	struct sem_undo *undo; 		 /* 在这个数组上的undo 请求 */
	ushort	sem_nsems;	 	/* 在信号量数组上的信号量号 */
};

union semun {
    
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO                                     
    (Linux-specific) */
};

struct semid_ds {
    
	struct ipc_perm sem_perm;  /* Ownership and permissions */
	time_t          sem_otime; /* Last semop time */
	time_t          sem_ctime; /* Last change time */
	unsigned long   sem_nsems; /* No. of semaphores in set */
};
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_72982923/article/details/136282761

智能推荐

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

深度神经网络在训练初期的“梯度消失”或“梯度爆炸”的问题解决:数据标准化(Data Standardization),权重初始化(Weight Initialization),Dropout正则化等_在人工神经网络研究的初始阶段,辛顿针对训练过程中常出现的梯度消失现象, 提供相-程序员宅基地

文章浏览阅读101次。1986年,深度学习(Deep Learning)火爆,它提出了一个名为“深层神经网络”(Deep Neural Networks)的新型机器学习模型。随后几年,神经网络在图像、文本等领域取得了惊艳成果。但是,随着深度学习的应用范围越来越广泛,神经网络在遇到新的任务时出现性能下降或退化的问题。这主要是由于深度神经网络在训练初期面临着“梯度消失”或“梯度爆炸”的问题。_在人工神经网络研究的初始阶段,辛顿针对训练过程中常出现的梯度消失现象, 提供相

kill进程的几种方式_如何kill掉一个进程-程序员宅基地

文章浏览阅读461次。我们会先使用 ps、top 等命令获得进程的 PID,然后使用 kill 命令来杀掉该进程。killall和pkill是相似的,不过如果给出的进程名不完整,killall会报错。当然我们可以向进程发送一个终止运行的信号,此时的 kill 命令才是名至实归。,这样结束掉的进程不会进行资源的清理工作,所以如果你用它来终结掉 vim 的进程,就会发现临时文件 *.swp 没有被删除。命令:pid of xx进程,显示进程的进程号,同上pgrep。这是 kill 命令最主要的用法,也是本文要介绍的内容。_如何kill掉一个进程

随便推点

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

vscode打开markdown文件 不显示图片 预览markdown文件_vscodemarkdown图片无法显示-程序员宅基地

文章浏览阅读3.2k次,点赞3次,收藏4次。vscode打开markdown文件 不显示图片 预览markdown文件_vscodemarkdown图片无法显示

C++的64位扩展_c++ long64-程序员宅基地

文章浏览阅读516次。在做ACM题时,经常都会遇到一些比较大的整数。而常用的内置整数类型常常显得太小了:其中long 和 int 范围是[-2^31,2^31),即-2147483648~2147483647。而unsigned范围是[0,2^32),即0~4294967295。也就是说,常规的32位整数只能够处理40亿以下的数。  那遇到比40亿要大的数怎么办呢?这时就要用到C++的64位扩展了。不同的编译器对6_c++ long64

推荐文章

热门文章

相关标签