管道

  • 匿名管道:在Linux中,使用|来建立一个管道,实质上是创建了两个进程,将左边的输出发送给右边
  • 命名管道:通过mkfifo来创建
mkfifo mypi

echo "hello" > mypi # 将数据写入管道,会阻塞直至为空

cat < mypi # 读取管道数据

也可以使用系统调用实现

int pipe(int fd[2]); // f[0]是读取描述符,f[1]是写入描述符,在父子线程中可用,因为子线程会复制父线程的东西,包括文件描述符

// 在父子线程中,通常会创建两个管道,关闭掉不需要的读写描述符。

通信效率低,不适合频繁

消息队列

通过内核的消息链表实现,会以字节流的形式发送数据,消息队列中的数据被读取,会被内核删除掉。消息队列生命周期随内核,而不是像管道一样随进程。不适合大数据,并且内核态和用户态切换与拷贝开销大。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>  // API函数参考
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 定义消息结构体
struct message_buffer {
    long type;
    char text[100];
};

// 生产者程序
void producer_main(){
    // k < 0 无效,根据路径名和编号,生成一个键,路径名必须存在的路径,编号必须是1-255
    // 路径+编号唯一标识一个键,消息队列双方需要一致
    key_t k = ftok("pathname", 1);  
    message_buffer msg_buf;  // 消息缓冲区

    // 前面是一个键,后面是flag值,flag详细参考 bits/ipc.h 25-38
        // 0,取消息队列标识符;
        // IPC_CREAT,新建消息队列(不存在的话),存在即返回已有的
        // IPC_CREAT|IPC_EXCL,新建消息队列(不存在的话),存在即报错
    int msgfd = msgget(key,  0); // 返回值小于0报错

    int flag = msgsnd(msgfd, msg_buf.text, 100, 0);
}

// 消费者程序
void consumer_main(){
    key_t k = ftok("pathname", 1);  
    message_buffer msg_buf; 
    int msgfd = msgget(key,  0);
    ssize_t size_ = msgrcv(msgfd, msg_buf.text, 100,1,0);
}

共享内存

共享内存就是内核将一块内存空间映射到两个不同内存上,但是需要结合互斥的方式来实现

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <bits/shm.h>  // shm_flag的参考文件

// 同样创建 key_t 参考上面,为了标识唯一IPC资源
key_t k = ftok("pathname", 1);

// 获取共享内存,键,内存大小,flag
int shmget(k, 1024, flag); 

// 关联共享内存,shmget返回的id,虚拟内存地址(传NULL表示自动选择),flag(SHM_RDONLY)
void *shmat(shmid, shmaddr, shmflag);  // 返回地址

// 取消共享内存
int shmdt(shmaddr);  // shmat的返回地址

除此之外,还有一些其他方法,比如:

#include <sys/mman.h>
#include <sys/stat.h>
#innclude <fcntl.h>

// 可以看到有两个文件 /dev/shm/posixsm /run/shm/posixsm
int fd = shm_open("posixsm", 0_CREAT | 0_RDWR, 0666); // 也可以使用文件打开方式 open
ftruncate(fd, 0x400000);
char *p = mmap(NULL, 0x400000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); // 重点是文件映射

// 可以看出重点在于 mmap 函数,以及一个 fd 文件描述符
// 除了可以 shm_open 以外,还可以直接 open 文件,除此之外,还可以使用
int fd = memfd_create("sha", 0);  // 创建一个匿名内存文件的fd

信号量

消耗量主要有PV两个操作,P操作就是-1,V操作就是+1。

#include <sys/wait.h>

sem_open();  // 开启信号量
sem_close(); // 关闭信号量
sem_post(); // V操作
sem_wait(); // P操作

信号

异常状态下,使用信号进行通信,有如下信号:

 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

# 对 pid 进程发送 id 的信号量
kill -id pid

可以在程序中自定义信号捕获行为,但是SIGKILLSIGSTOP不能被捕获。

#include <signal.h>

// __sighandler_t定义为 void functio(int)的函数指针,还可以有两个值,一个是SIG_IGN(忽略),一个是SIG_DFL(默认处理)
// 返回上一次执行的信号处理函数的指针,如果是SIG_ERR,那么表示设置错误
__sighandler_t signal(int signum, __sighandler_t handler);  // 注册捕获信号

// 下面两个信号是自定义的信号
10) SIGUSR1
12) SIGUSR2

int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact); // 失败返回-1
struct sigaction
{
    union {
        __sighandler_t sa_handler;  // 同 signal 中的,没设置 SA_SIGINFO 的话使用这个
        void (*sa_sigaction) (int, siginfo_t *, void *); // 如果设置了 SA_SIGINFO 的话使用这个
    }  __sigaction_handler;  // 信号处理函数

    __sigset_t sa_mask; // 运行处理函数之前,会阻塞掉的信号
    /*
    #define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
    typedef struct
    {
      unsigned long int __val[_SIGSET_NWORDS];
    } __sigset_t;
    */

    int sa_flags; 

    void (*sa_restorer) (void); 
};


// 向 __pid 发送 __sig信号
int kill (__pid_t __pid, int __sig);

socket

socket是多进程中常用的通信方式,可以适用于不同机器上的多进程。