Linux 进程通信(IPC)一站式笔记:概念 → 常用方式 → 函数原型与参数详解 - 指南

Linux 进程通信(IPC)一站式笔记:概念 → 常用方式 → 函数原型与参数详解(含示例)

这篇就是“看一眼就能想起来怎么用”的那种。覆盖:为什么要通信、IPC 概念、常见 7 大方式、信号完整用法(含屏蔽/排队/挂起/恢复)、关键系统调用的原型与参数解释、易踩坑与实战建议


1. 进程为什么要通信?什么是 IPC?

  • 进程彼此隔离(独立地址空间),安全但“互相看不到”。
  • IPC(Inter-Process Communication)进程间通信:为协作而在受控前提下交换数据/事件
  • 典型场景:多进程服务、日志采集、前后台任务协作、GUI 与后端守护进程交互、生产者-消费者等。

2. 常用通信方式概览(按“用途/特点”记忆)

方式适合场景主要特点难度
信号 (signal)事件通知、打断系统调用轻量、带语义;传统信号不排队
信号量 (semaphore)同步/互斥计数/锁;不传数据
匿名管道 (pipe)亲缘进程单向字节流简单、内核缓冲、半双工
命名管道 (FIFO)非亲缘进程字节流文件路径可见、半双工
共享内存 (shm)大量数据高性能共享最快;需配合互斥同步
消息队列 (msg queue)结构化消息、排队有消息边界、内核队列
本地套接字 (Unix Domain Socket)客户端/服务器模型通用可靠、双向、支持传句柄中偏高

本文重点把信号讲细(你给的素材覆盖最全),其它方式给“记忆点+调用小抄”。


3. 信号(Signals):事件通知的万能扳手

3.1 快速上手示例

#include <stdio.h>
  #include <unistd.h>
    #include <signal.h>
      void signal_handler(int sig) {
      printf("Received signal: %d\n", sig);
      }
      int main(void) {
      signal(SIGINT, signal_handler);    // Ctrl+C → SIGINT
      int count = 1;
      while (1) {
      printf("working... %d\n", count++);
      sleep(1);
      }
      return 0;
      }

运行后按 Ctrl + C,会触发 SIGINT,进入处理函数。


3.2 信号分类与默认动作

  • 非实时信号(1–31)不排队,同类信号可能合并(丢数目,只记“来过”)。
  • 实时信号(32–64,宏 SIGRTMINSIGRTMAX支持排队,不丢。

常见非实时信号(摘要):

信号默认说明
SIGHUP1Term终端挂起;常用作“重载配置”
SIGINT2TermCtrl+C
SIGQUIT3CoreCtrl+\
SIGILL4Core非法指令
SIGABRT6Coreabort()
SIGFPE8Core算术异常
SIGKILL9Term不可捕获/忽略
SIGSEGV11Core访问越界
SIGPIPE13Term向已关闭管道写
SIGALRM14Termalarm() 定时
SIGTERM15Term建议的“优雅退出”
SIGCHLD17Ign子进程状态变化
SIGSTOP19Stop不可捕获/忽略,暂停
SIGTSTP20StopCtrl+Z
SIGCONT18Continue从停止恢复

默认动作简记:Term 终止、Ign 忽略、Core 终止并产生 core、Stop/Cont 停/继续。


3.3 发送信号

A) 自动发送
  • 终端 Ctrl+C → SIGINTCtrl+Z → SIGTSTPfg/bg 恢复会发 SIGCONT
B) 系统调用 kill(2)
#include <sys/types.h>
  #include <signal.h>
    int kill(pid_t pid, int sig);   // 返回 0 成功,-1 失败(看 errno)

例:父控子进程暂停/继续/杀死

#include <stdio.h>
  #include <unistd.h>
    #include <sys/wait.h>
      #include <signal.h>
        int main(void){
        pid_t pid = fork();
        if (pid == 0) {           // 子
        int c = 1;
        while (1) { printf("child %d\n", c++); sleep(1); }
        } else {                  // 父
        int n;
        while (scanf("%d",&n)==1){
        if (n==1) kill(pid, SIGSTOP);
        else if (n==2) kill(pid, SIGCONT);
        else if (n==3) { kill(pid, SIGKILL); waitpid(pid, NULL, 0); break; }
        }
        }
        }
C) 命令 kill(1) / pkill(1)
kill -TERM <PID>     # 优雅结束
  kill -KILL <PID>     # 强制结束
    pkill nginx          # 按名字发信号

3.4 设置信号处理

signal(2)(简单)
#include <signal.h>
  typedef void (*sighandler_t)(int);
  sighandler_t signal(int signum, sighandler_t handler);
  // handler 可取:SIG_IGN(忽略)、SIG_DFL(默认)、或函数指针

返回上一次的 handler(或 SIG_ERR)。

sigaction(2)(推荐,健壮)
#include <signal.h>
  int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  struct sigaction {
  void (*sa_handler)(int);                 // 简单处理
  void (*sa_sigaction)(int, siginfo_t*, void*); // 带信息处理(需 SA_SIGINFO)
  sigset_t sa_mask;                        // 处理期间需要屏蔽的信号集
  int sa_flags;                            // 行为标志
  void (*sa_restorer)(void);               // 兼容字段,忽略
  };

常用 sa_flags

  • SA_RESTART:被信号打断的可重启系统调用自动重启(如 read)。
  • SA_SIGINFO:启用 sa_sigaction,可获取发送者 PID、UID、附加数据等。
  • SA_NOCLDSTOP/SA_NOCLDWAIT:影响 SIGCHLD 语义(子停/子退是否通知/是否产生僵尸)。

示例:带 SA_RESTART 的可重启 read

#include <stdio.h>
  #include <unistd.h>
    #include <signal.h>
      #include <errno.h>
        void sig_handler(int s){ printf("Received: %d\n", s); }
        int main(void){
        struct sigaction sa = {0};
        sa.sa_handler = sig_handler;
        sa.sa_flags = SA_RESTART;          // 关键
        sigemptyset(&sa.sa_mask);
        sigaction(SIGUSR1, &sa, NULL);
        printf("pid=%d\n", getpid());
        char buf[512];
        while (1) {
        int n = read(STDIN_FILENO, buf, sizeof(buf)-1);
        if (n == -1) {
        if (errno == EINTR) printf("read interrupted\n");
        } else {
        buf[n] = 0; printf("read: %s", buf);
        }
        }
        }

3.5 信号屏蔽字(阻塞/解除/恢复)

#include <signal.h>
  int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  // how: SIG_BLOCK | SIG_UNBLOCK | SIG_SETMASK
  // set: 要加入/移除/覆盖的集合,oldset:返回原屏蔽字

完整流程示例:阻塞 SIGINT → 检查挂起 → 恢复

#include <stdio.h>
  #include <unistd.h>
    #include <signal.h>
      void h(int s){ printf("SIGINT handled: %d\n", s); }
      int main(void){
      struct sigaction sa = {0};
      sa.sa_handler = h; sigemptyset(&sa.sa_mask);
      sigaction(SIGINT, &sa, NULL);
      puts("20s later will block SIGINT");
      sleep(20);
      sigset_t mask, oldmask;
      sigemptyset(&mask);
      sigaddset(&mask, SIGINT);
      sigprocmask(SIG_BLOCK, &mask, &oldmask);   // 开始屏蔽
      puts("blocked SIGINT");
      sigset_t pend; sigpending(&pend);
      if (sigismember(&pend, SIGINT)) puts("SIGINT is pending!");
      puts("20s later will restore");
      sleep(20);
      sigprocmask(SIG_SETMASK, &oldmask, NULL);  // **正确恢复**
      puts("restored");
      while (1) pause();
      }

常见坑:恢复时不要再用 SIG_BLOCK,要么 SIG_UNBLOCK 指定某些信号,要么 SIG_SETMASK 恢复原集。


3.6 补充:信号集操作小抄

int sigemptyset(sigset_t *set);                // 置空
int sigfillset(sigset_t *set);                 // 全 1(包含所有信号)
int sigaddset(sigset_t *set, int signum);      // 加入
int sigdelset(sigset_t *set, int signum);      // 删除
int sigismember(const sigset_t *set, int sig); // 是否成员
int sigpending(sigset_t *set);                 // 取挂起集

4. 其它 IPC 方式的“怎么记、怎么用”(极简可用版)

4.1 匿名管道 pipe(2)(亲缘进程单向字节流)

#include <unistd.h>
  int pipe(int fd[2]);          // fd[0]=读端, fd[1]=写端

fork() 配合:子进程 dup2(fd[1], STDOUT_FILENO),父进程读 fd[0]

4.2 命名管道 FIFO mkfifo(3)(非亲缘也可)

#include <sys/stat.h>
  int mkfifo(const char *path, mode_t mode);
  // 打开用 open(),读写与普通文件一致,但遵循管道语义(阻塞/唤醒)

4.3 共享内存(System V 或 POSIX)

System V

#include <sys/shm.h>
  int shmid = shmget(key_t key, size_t size, int shmflg);
  void* addr = shmat(shmid, const void *shmaddr, int shmflg);
  int shmdt(const void *shmaddr);
  int shmctl(int shmid, int cmd, struct shmid_ds *buf); // 删除等

POSIX(更现代):

#include <sys/mman.h>
  #include <fcntl.h>
    #include <sys/stat.h>
      int fd = shm_open("/name", O_CREAT|O_RDWR, 0600);
      ftruncate(fd, size);
      void* p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
      munmap(p, size); close(fd); shm_unlink("/name");

同步:共享内存只共享数据,不共享“顺序”,配合互斥量/信号量

4.4 信号量

POSIX 有名信号量

#include <semaphore.h>
  #include <fcntl.h>
    sem_t* sem = sem_open("/semname", O_CREAT, 0600, 1);
    sem_wait(sem);   // P
    sem_post(sem);   // V
    sem_close(sem); sem_unlink("/semname");

匿名(进程内/共享内存内)

sem_t sem;
sem_init(&sem, 1, 1); // pshared=1 表示可跨进程(需放在共享内存中)

4.5 消息队列(System V)

#include <sys/msg.h>
  int msgid = msgget(key_t key, int msgflg);
  int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

消息结构:

struct msgbuf { long mtype; char mtext[1]; };

4.6 本地套接字(Unix Domain Socket)

流式(SOCK_STREAM) 与 TCP 相同用法,但走内核本地协议、支持传文件描述符

#include <sys/socket.h>
  #include <sys/un.h>
    // server: socket(AF_UNIX, SOCK_STREAM, 0) → bind("/tmp/sock") → listen → accept
    // client: socket → connect("/tmp/sock") → read/write

传 FD 参考 sendmsg/recvmsg + SCM_RIGHTS


5. 实战建议与易踩坑

  • 信号处理函数内只做“异步信号安全”的事:设置标志、write(2);避免 malloc/printf
  • SIGKILL/SIGSTOP 不可捕获/忽略:不要尝试自定义它们的处理。
  • SA_RESTART 与系统调用:不是所有调用都能重启(如 select 有细节);处理好 EINTR
  • 共享内存一定配同步:信号量/互斥量/读写锁,或事件通知 + 序列号。
  • 资源清理:FIFO、消息队列、共享内存段、命名信号量记得 unlink/ctl(IPC_RMID)
  • 安全:Unix 域套接字默认走文件权限模型;共享内存/队列注意创建权限。
  • 调试strace -f -p <PID> 看看谁在阻塞/被信号打断;ipcs/ipcrm 管理 SysV 资源。

6. 函数与参数小抄(可快速回看)

信号收发

int kill(pid_t pid, int sig);
int sigaction(int sig, const struct sigaction *act, struct sigaction *old);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int sig); int sigdelset(sigset_t *set, int sig);
int sigismember(const sigset_t *set, int sig);

管道/FIFO

int pipe(int fd[2]);
int mkfifo(const char *path, mode_t mode);

共享内存(POSIX)

int shm_open(const char *name, int oflag, mode_t mode);
int ftruncate(int fd, off_t length);
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off);
int munmap(void *addr, size_t len);
int shm_unlink(const char *name);

信号量(POSIX)

sem_t* sem_open(const char *name, int oflag, mode_t mode, unsigned value);
int sem_close(sem_t *sem); int sem_unlink(const char *name);
int sem_wait(sem_t *sem); int sem_post(sem_t *sem);
int sem_init(sem_t *sem, int pshared, unsigned value); // 匿名
int sem_destroy(sem_t *sem);

消息队列(SysV)

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

本地套接字(UNIX)

int socket(int domain, int type, int protocol);          // AF_UNIX
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t send(int fd, const void *buf, size_t len, int flags);
ssize_t recv(int fd, void *buf, size_t len, int flags);

7. 读完就能用:两段最小可运行骨架

7.1 “父发信号控制子”的骨架

pid_t pid = fork();
if (pid == 0) {
while (1) { /* 工作 */ sleep(1); }
} else {
kill(pid, SIGSTOP);  // 暂停
sleep(2);
kill(pid, SIGCONT);  // 继续
sleep(2);
kill(pid, SIGTERM);  // 优雅结束
waitpid(pid, NULL, 0);
}

7.2 “匿名管道抓子进程输出”的骨架

int p[2]; pipe(p);
pid_t pid = fork();
if (pid == 0) {
close(p[0]); dup2(p[1], 1);    // 子把 stdout 指向管道写端
execlp("echo", "echo", "hello IPC", (char*)NULL);
_exit(127);
}
close(p[1]); char buf[256]; ssize_t n = read(p[0], buf, sizeof buf);
write(1, buf, n); close(p[0]); waitpid(pid, NULL, 0);

8. 收尾:如何选择 IPC?

  • 事件/唤醒/打断:信号(+ sigwaitinfo/signalfd 也可考虑)
  • 少量字节流:管道/FIFO
  • 大量数据:共享内存(+ 同步原语)
  • 结构化消息/多生产者排队:消息队列/本地套接字
  • 通用 Client/Server:本地套接字(需要传 FD 时更合适)

posted @ 2026-02-02 20:07  clnchanpin  阅读(44)  评论(0)    收藏  举报