IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux进程通信 系统调用总结 -> 正文阅读

[系统运维]Linux进程通信 系统调用总结

1. 进程通信概述

Linux下进程通信的八种方法:

  • 匿名管道(pipe),

  • 命名管道(FIFO),

  • 内存映射(mapped memeory),

  • 消息队列(message queue),

  • 共享内存(shared memory),

  • 信号量(semaphore),

  • 信号(signal)

  • 套接字(Socket)

2. 匿名管道(pipe)

匿名管道用于进程之间通信,且仅限于本地父子进程之间通信,结构简单

  • 只提供单向通信,也就是说,两个进程都能访问这个文件,假设进程1往文件内写东西,那么进程2 就只能读取文件的内容。
  • 只能用于具有血缘关系的进程间通信,通常用于父子进程建通信
  • 管道是基于字节流来通信的
  • 依赖于文件系统,它的生命周期随进程的结束结束(随进程)
  • 其本身自带同步互斥效果

在LINUX编程中匿名管道通常使用pipe系统调用完成。shell命令行中**管道符|**的本质也是匿名管道

2.1 pipe

用法:

int pd[2];
int r = pipe(pd);

pipe()函数在内核中创建一个管道并在传入参数pd[2]中返回两个文件描述符,其中pd[0]用于从管道读取,pd[1]用于从管道写入数据。如果成功返回0,失败返回-1。

需要注意,管道是多进程间通信的概念,因此在一个进程中通过pipe系统调用创建一个管道后并不能直接从中读取数据,会永远等待下去。因为创建管道后该管道中没有数据,此时读取端进程读数据时需要先等待写入端进程写入数据后才能读。然而这里写进程和读进程都是同一个进程,因此会永远等待。因此进程只能是管道的一个读进程或者写进程之一,而不能二者都是。

因此正确方式是创建一个管道后fork复刻一个子进程来共享该管道。由于fork的子进程会继承父进程的所有打开文件描述符,因此子进程也有pd[0](读取端)和pd[1](写入端)。然后将父进程和子进程分别指定为该管道的读取端或者写入端(即为将一个进程指定为管道的读取端,另一个进程指定为该管道的写入端),且父子进程都要关闭掉他不需要的文件描述符。例如父进程指定为写进程,子进程指定为读进程。那么父进程关闭掉pd[0],子进程关闭掉pd[1]。之后就可以父进程向管道写数据,子进程从该管道读取数据。

示例:父进程创建管道并fork子进程,指定父进程为管道写入端、子进程为读取端,进程父子进程间单向通信

main.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(){
    int pd[2];
    int r = pipe(pd);//创建管道
    printf("pipe: read: %d write: %d\n",pd[0],pd[1]);
    int pid = fork();//fork子进程
    if(pid){
        //父进程执行此部分
        close(pd[0]);   //设置父进程为管道的写入端,因此关闭读取端文件描述符pd[0]
        char buf[] = "this is some datas";
        sleep(3);   //父进程sleep三秒
        printf("parent Process begin write datas\n");
        write(pd[1],buf,strlen(buf));   //向管道写入数据
        printf("parent Process write datas done\n");
    }
    else{
        //子进程执行此部分
        close(pd[1]);   //设置子进程为管道的读取端,因此关闭写入端文件描述符pd[1]
        char buf[30];
        int num = read(pd[0],buf,sizeof(buf)-1);    //从管道读取数据
        buf[num] = '\0';	//字符串结尾
        printf("son Process get %d datas : %s\n",num,buf);
    }
}

运行结果

xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ gcc pipe_test.c 
xtark@xtark-vmpc:~/桌面/linux_study/section3/pip test$ ./a.out 
pipe: read: 3 write: 4
parent Process begin write datas
parent Process write datas done
son Process get 18 datas : this is some datas

可见父进程成功将数据写入管道,子进程成功从管道读出数据。由于父进程在写入前sleep了三秒,可以证实管道读取端要读数据时,如果管道为空且存在写进程时会等待写入端写入数据后再读取。

2.2 管道命令

管道符号:|用法:

cmd1 | cmd2

cmd1的输出编程cmd2的输入,这是通过管道实现的。sh通过一个进程运行cmd1,一个进程运行cmd2,它们之间通过一个管道连在一起完成进程间通信。

其具体实现逻辑大致如下:

  • sh进程获取命令行cmd1 | cmd2,复刻出一个子进程sh并等待子进程sh终止

  • 该子进程sh执行以下代码:需要注意exec更改进程执行映像并不会关闭原先进程已经打开的文件描述符,因此exec之前打开的文件描述符在更改执行映像后还能继续使用(除非该文件描述符是FD_CLOEXEC)。

    close-on-exec标志(FD_CLOEXEC):内核为每个文件描述符提供了执行时关闭标志,当exec()执行成功之后,会自动关闭设置了FD_CLOEXEC标志的文件描述符,如果exec()调用失败,文件描述符依然会保持打开状态

    int pd[2];
    pipe(pd);//创建管道
    int pid = fork();//复刻一个子进程
    if(pid){
        //父进程执行此部分
        close(pd[0]);//指定该进程为写入端
        close(1);//关闭fd1(stdout)
        dup(pd[1]);//I/O重定向,fd1与pd[1]代指同一个文件
        close(pd[1]);//关闭pd[1],此时只有fd1代指管道的写入端口
        exec(cmd1);//更改执行映像为cmd1
    }
    else{
        //子进程执行此部分
        close(pd[1]);//指定该进程为读取端
        close(0);//关闭fd0(stdin)
        dup(pd[0]);//I/O重定向,fd0与pd[0]代指同一个文件
        close(pd[0]);//关闭pd[0],此时只有fd0代指管道的读取端口
        exec(cmd2);//更改执行映像为cmd2
    }
    

    通过以上代码,管道写进程经过I/O重定向,使得文件描述符fd=1代指该管道写入端;管道读进程经过I/O重定向,使得文件描述符fd=0代指该管道读取端,从而完成两个进程之间的管道连接。

3. 命名管道

命名管道又称为FIFO,它们有名称,在文件系统中以特殊文件的形式存在。它们会一直存在直到被显式删除(如rm命令)。命名管道不局限于相关进程间(如父子进程)通信,而是可以用于两个不相关进程间通信。进程可以像访问普通文件(如open系统调用)一样访问命名管道文件。命名管道支持不同进程之间,支持可靠的、单向或双向的数据通信

  1. 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件。

  2. 有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

  3. 一旦打开了 FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用了(如read()、write()和close())。与管道一样,FIFO 也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的。FIFO 的名称也由此而来:先入先出。

  4. 有名管道(FIFO)和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:

    • FIFO 在文件系统中作为一个特殊文件存在,但 FIFO 中的内容却存放在内存中。
    • 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
    • FIFO 有名字,不相关的进程可以通过打开有名管道进行通信。

LINUX编程中常使用mkfifo来创建命名管道

3.1 mkfifo

创建一个命名管道文件并指定其权限

int mkfifo(const char * pathname,mode_t mode);

参数:

  • pathname:文件名,改文件必须不存在
  • mode:文件权限

返回值:

  • 若成功则返回0,否则返回-1,错误原因存于errno中。

需要注意

  1. 当使用O_NONBLOCK 标志时, 打开FIFO 文件来读取的操作会立刻返回, 但是若还没有其他进程打开FIFO 文件来读取, 则写入的操作会返回ENXIO 错误代码.
  2. 没有使用O_NONBLOCK 标志时, 打开FIFO 来读取的操作会等到其他进程打开FIFO 文件来写入才正常返回. 同样地, 打开FIFO 文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回.

示例代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
    char buffer[80];
    int fd;
    unlink(FIFO);
    mkfifo(FIFO, 0666);//创建命名管道文件
    if(fork() > 0)//创建子进程
    {
        char s[] = "hello!\n";
        fd = open(FIFO, O_WRONLY);//写入端打开文件
        write(fd, s, sizeof(s));
        close(fd);
    }
    else
    {
        fd = open(FIFO, O_RDONLY);//读取端打开文件
        read(fd, buffer, 80);
        printf("%s", buffer);
        close(fd);
    }
}
//打印 hello!

4. 内存映射

内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

LINUX编程中通常用mmap和munmap来进行内存映射

4.1 mmap

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,**进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。**mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数:

  • addr:映射区(即进程中的一块内存区域)的开始地址,设置为0时表示由系统决定映射区的起始地址。

  • length:映射区的长度(字节)。可以通过stat系统调用获得打开文件的大小信息,然后设置为这个参数

  • prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

    • PROT_EXEC 页内容可以被执行

    • PROT_READ 页内容可以被读取

    • PROT_WRITE 页可以被写入

    • PROT_NONE 页不可访问

  • flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体

    • MAP_PRIVATE:多进程间数据共享,修改不反应到磁盘实际文件,是一个copy-on-write(写时复制)的映射方式。即内存区域的写入不会影响到原文件
    • MAP_SHARED:多进程间数据共享,修改反应到磁盘实际文件中,相当于输出到文件
    • 等等
  • fd:文件描述符,通常是open打开的文件

  • offset:被映射内容的起点(即fd文件内容偏移量)

返回值:

  • 成功返回映射区指针,失败返回-1并设置errno

4.2 munmap

munmap()用来取消参数addr所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述符时不会解除映射。

int munmap(void *addr, size_t length);

参数:

  • addr:映射内存起始地址
  • length:欲取消的内存大小

返回值:

  • 解除映射成功则返回0,否则返回-1,错误原因存于errno中

5. 信号

5.1 什么是信号

信号即为软中断,用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据

信号来源

  • 来自硬件中断的信号:间隔定时器(可以发出信号14、信号26、信号27)、ctrl+c组合键、其他硬件错误等
  • 来自其他进程的信号:kill系统调用,子进程死亡等
  • 来自异常的信号(自己发给自己的信号):用户模式下的进程遇到异常时会陷入内核模式,生成一个信号并发送给自己。如除0、无效地址等

5.2 Linux处理信号方式

当一个信号到来的时候收到这个信号的进程会根据信号的具体情况提供一下三种不同的处理方式:

  • 对于需要处理的信号,指定处理函数,由该函数来处理。

  • 忽略某个信号,对该信号不做任何处理。

  • 对该信号的处理保留系统的默认值,这种默认操作大多数使得进程终止,进程通过系统调用signal函数来指定进程对某个信号的处理行为。

5.3 可靠信号与不可靠信号

可以通过kill -l查看所有信号

xtark@xtark-vmpc:~$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

上图中编号为131的信号为早期Linux所支持的信号,是**不可靠信号**(非实时的),编号为3463的信号时后来扩充的,称为可靠信号(实时信号)。

不可靠信号与可靠信号的区别在于前者不支持排队(相同的信号多次到来会合并为一个,造成后来的信号丢失),可能会造成信号丢失,而后者不会丢失。

5.4 signal系统调用

安装信号捕捉函数,当进程收到指定信号时会调用该信号处理函数。

该函数可以用来修改指定信号的处理函数(除了SIGKILL(9)和SIGSTOP(19)以外,它们的信号处理函数不能修改)

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  • 参数signum:指定的信号

  • 参数handler:

    一个函数指针,且该函数是void (*)(int)类型。该函数int参数即为信号number

    可以传递该参数为SIG_IGN宏,来忽略该信号;也可以传递该参数为SIG_DFL使用该信号的默认处理函数

示例:通过新号处理函数捕获SIGINT(2)

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
//信号处理函数
void func(int sig){
    printf("\nreceive sig %d\n",sig);
    exit(EXIT_SUCCESS);//成功终止进程
}
int main(){
    signal(SIGINT,func);//注册新号处理函数
    while(1);
}

运行结果:在进程运行过程中键入ctrl+c可以看出该进程成功捕获到信号2(SIGINT)并调用信号处理函数。

xtark@xtark-vmpc:~/桌面/linux_study/section8$ gcc test5.c 
xtark@xtark-vmpc:~/桌面/linux_study/section8$ ./a.out 
^C
receive sig 2

5.5 kill系统调用

向任何进程组或进程发送任何信号

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
  • 参数pid:进程识别号pid。

    pid大于0:信号sig将被发送到pid指定的进程

    pid等于0:sig被发送到调用进程的进程组中的每个进程

    pid等于-1:sig被发送到调用进程有权限发送信号的每个进程,除了进程1(init进程)

    pid小于-1:sig被发送给进程组中ID为-pid的每个进程

  • 参数sig:要发送的信号。

    如果sig为0,则不发送信号,但仍会进行错误检查;这可以用来检查是否存在进程ID或进程组ID存在。

  • 返回值:成功时(至少有一个信号被发送)返回0。 错误时返回-1,并设置errno。

5.6 raise函数

向调用进程(自己)发送信号,与kill(getpid(), sig);等价

#include <signal.h>
int raise(int sig);

6. 共享内存

共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快。

共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量

使用步骤:

  • 调用shmget() 创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。

  • 使用 shmat() 来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由 shmat()

  • 调用返回的 addr 值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。

  • 调用 shmdt() 来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。

  • 调用 shmctl() 来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会销毁。只有一个进程需要执行这一步。

LINUX共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。

6.1 shmeget

得到一个共享内存标识符或创建一个共享内存对象

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

参数:

  • key:有效地为共享内存段命名,通常情况下,该id值通过ftok函数得到
  • size:以字节为单位指定需要共享的内存容量
  • shmflg:
    • IPC_CREAT:创建一个新的段。如果这个flag没有使用,shmget()会查找key对应的共享内存段,并且检查用户是否有访问该内存段的权限。
    • IPC_EXCL:这个flag 与IPC_CREAT配合使用, 来确保这个调用会创建一个共享内存段。如果该内存段已经存在,则调用失败。

返回值

  • 如果成功,会返回一个共享内存段标识符。如果出错,会返回-1, errno 来标识出错的原因。

6.2 shmat

连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

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

参数:

  • shmid:共享内存段标识符
  • shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
  • shmflg:shm_flg是一组标志位,通常为0。如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写的方式连接此段。

返回值:

  • 调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1并置为errno

6.3 shmctl

完成对共享内存的控制,可以用来删除共享内存段

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

参数:

  • shmid:共享内存段标识符
  • cmd:command是要采取的操作,它可以取下面的三个值 :
    • IPC_STAT:将该共享内存段的信息通过buf参数返回
    • IPC_SET:如果进程有足够的权限,就把共享内存的信息设置为buf结构中给出的值
    • IPC_RMID:删除共享内存段
  • buf:共享内存管理结构体

返回值:

  • 成功返回0,失败返回-1并置为errno

6.4 shmdt

断开共享内存连接。注意,将共享内存分离并不是删除它,只是使当前进程不能再使用该共享内存

int shmdt(const void *shmaddr)

参数:

  • shmaddr:连接的共享内存的起始地址

返回值:

  • 成功返回0失败返回-1并置位errno

7. 信号量

信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享

7.1 信号量原理

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

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

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

  • 在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)

  • 二元信号量:二元信号量(Binary Semaphore)是最简单的一种锁(互斥锁),它只用两种状态:占用与非占用。所以它的引用计数为1。

7.2 semget

创建一个新的信号量集或获取一个已经存在的信号量集。

int semget(key_t key, int nsems, int semflg);

参数:

  • key:key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。相当于信号量的标识符

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

  • semflg:

    一组标志,当想要当信号量不存在时创建一个新的信号量,可以将flag设置为IPC_CREAT与文件权限做按位或操作。设置了IPC_CREAT标志后,即使给出的key是一个已有信号量的key,也不会产生错误。

    IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误

    一般我们会还或上一个文件权限,如IPC_CREAT|IPC_CREAT|0644

返回值:

  • 如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1,errno被设定

7.3 semctl

删除和初始化信号量

int semctl(int semid, int semnum, int cmd, ...);

参数:

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

  • semnum:当前信号量集的哪一个信号量

  • cmd:通常是下面两个值中的其中一个

    • SETVAL:用来把信号量初始化为一个已知的值。通过第四个参数设置。
    • IPC_RMID:用于删除一个已经无需继续使用的信号量标识符,删除的话就不需要缺省参数,只需要三个参数即可。
  • 第四个参数:如有需要第四个参数一般设置为union semnu arg,其中的val成员即为cmd参数为SETVAL时的初始值

    union semun
    { 
        int val;  //使用的值
        struct semid_ds *buf;  //IPC_STAT、IPC_SET 使用的缓存区
        unsigned short *arry;  //GETALL,、SETALL 使用的数组
        struct seminfo *__buf; // IPC_INFO(Linux特有) 使用的缓存区
    };
    

返回值:

  • 成功返回正数,失败返回-1并置位errno

7.4 semop

信号量的操作(P/V操作)

int semop(int semid, struct sembuf *sops, size_t nops);

参数:

  • semid:是由semget返回的信号量集标识符

  • sops:结构体数组,每一个元素代表对信号量集中一个信号量的操作

    struct sembuf
    {
    	unsigned short sem_num;
    	short sem_op;
    	short sem_flg;
    };
    
    • sem_num:信号量的下标(在数组中的索引)
    • sem_op:信号量一次操作总需要改变的数值,+1是v操作,-1是p操作
    • sem_flg:信号操作标志,可能的选择有两种
      • IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。
      • SEM_UNDO:如果这个进程在没有释放该信号量的情况下终止(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
  • nops:第二个参数的数组元素个数

返回值:

  • 成功返回0,失败返回-1并置位errno

8. 消息队列

“消息”是在两台计算机间传送的数据单位。消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象。
消息被发送到队列中。“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。

8.1 msgget

创建新的消息队列或获取已有的消息队列

int msgget ( key_t key, int msgflg )

参数:

  • key:key是长整型(唯一非零),系统建立IPC通讯 ( 消息队列、 信号量和 共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到
  • msgflg:一组标志
    • IPC_CREAT:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符
    • IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错

返回值:

  • 成功返回消息队列标识符,失败返回-1并置位errno

8.2 msgctl

获取和设置消息队列的属性

int msgctl(int msgid, int cmd, struct msqid_ds *buf)

参数:

  • msgid:消息队列标识符

  • cmd:指令

    • IPC_STAT:读取消息队列的属性并存储在buf指定的地址中。

    • IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中

    • IPC_RMID:buf指定为NULL将队列从系统内核中删除。

  • buf:消息队列管理结构体

返回值:

  • 成功返回0,失败返回-1并置位errno

8.3 msgsnd/msgrcv

msgrcv()可以从消息队列中读取消息,msgsnd()将一个新的消息写入队列。

8.3.1 msgsnd向消息队列写入新的消息

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

参数:

  • msqid:消息队列标识符

  • msgp:发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下:

    struct msgbuf {
    	long mtype;       /* 消息类型,必须大于零 */
    	char mtext[123];    /* 消息内容 */
    };
    
  • msgsz:要发送信息的长度(字节数),可以用以下的公式计算:msgsz = sizeof(struct msgbuf) - sizeof(long);。

  • msgflg:发送方式

    • 0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
    • IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回
    • IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。

返回值:

  • 成功返回0,失败返回-1并置位errno

8.3.2 msgrcv向消息队列写入新的消息

从标识符为msqid的消息队列读取消息并存于msgp中,读取后把此消息从消息队列中删除

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数:

  • msqid:消息队列标识符

  • msgp:存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同

  • msgsz:要接收消息的大小,不含消息类型long占用的字节数

  • msgtyp:消息类型:

    • 0:接收第一个消息
  • > 0:接收类型等于msgtyp的第一个消息(由msgbuf中的第一个成员mtype消息类型决定)

    • < 0:接收类型等于或者小于msgtyp绝对值的第一个消息
  • msgflg:接收方式

    • 0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待
    • IPC_NOWAIT:不阻塞,如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG
    • IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息
    • IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃

返回值:

  • 成功返回读取到的消息数据长度,失败返回-1并置位errno

9. socket

常用于网络通信

10. ftok

每一个消息队列都有一个对应的键值(key)相关联(共享内存、信号量也同样需要)。

key_t ftok(const char *filename ,int id);

参数:

  • filename:就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:

    key = ftok(".", 1); //这样就是将filename设为当前目录。
    
  • id:是子序号,为0~255之间的一个数值

返回值:

  • 成功返回键值(在一般的UNIX实现中,是将文件的索引节点号inode取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002)。如果要确保key_t值不变,要么确保ftok的文件不被删除,要么不用ftok,指定一个固定的key_t值
  • 出错返回-1。

例如:

key_t key = ftok(/tmp”, 66);
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 12:34:54  更:2021-08-30 12:36:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 11:57:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码