【Linux 内核专栏 18】进程间通信 IPC(下)— Socket·Netlink·fd 三件套·futex·线程同步

📚 Linux 内核专栏 · 持续更新中

本系列从底层原理到源码实战,逐篇拆解内核机制。

👉 想系统追更,戳这里查看完整专栏:

【Linux内核系列】

👉 其他技术系列专栏,欢迎关注、交流、共同成长:

【I2C 技术系列】 【串口技术系列】【SPI 技术系列】【RISC-V 架构系列】


本篇定位 :Linux 内核专题 L2 补充篇 · IPC 全收官 。接 17 上篇(管道/信号/SysV IPC/mmap/文件锁),本篇讲 IPC 下半部:

① Unix Domain Socket (本机高效双向);

② Netlink (内核↔用户态通信主力,BSP 必备);

③ fd 三件套 (eventfd/signalfd/timerfd,fd 化通知配 epoll);

④ epoll (IO 多路复用,跨所有 fd-based IPC);

⑤ futex (用户态快速互斥底层);

⑥ pthread 线程同步 (mutex/cond/rwlock/barrier,与 07 内核态对照);

⑦ 内核↔用户态通信总览 (copy_to_user/ioctl/procfs/sysfs/debugfs/netlink/字符设备,BSP 主场选型 )。

读完你的 Linux IPC 闭环,BSP 驱动↔用户态通信选型不再懵。
嵌入式视角:本篇是 BSP 驱动↔用户态通信的"主战场"

写驱动,核心问题之一是"内核驱动怎么把数据/事件给用户态守护进程,用户态怎么配置驱动"------这正是本篇的主线。

选型矩阵:

① 配驱动参数 → sysfs(简单)/ioctl(精准);

② 驱动→用户事件 → Netlink/uevent(异步)/poll+read(同步)/中断计数;

③ 大量数据 → mmap(零拷贝)/字符设备 read;

④ 调试导出 → debugfs/procfs;

⑤ 用户态间 → Unix socket/eventfd。

Netlink 是 Linux 内核↔用户态的现代标准,做 BSP 必须会。


目录

  • [一、Unix Domain Socket](#一、Unix Domain Socket)
    • [1.1 AF_UNIX 是什么](#1.1 AF_UNIX 是什么)
    • [1.2 基本用法(流式,服务器/客户端)](#1.2 基本用法(流式,服务器/客户端))
    • [1.3 SCM_RIGHTS:传文件描述符(高阶能力)](#1.3 SCM_RIGHTS:传文件描述符(高阶能力))
    • [1.4 内核实现](#1.4 内核实现)
  • 二、Netlink(内核↔用户态主力)
    • [2.1 Netlink 是什么](#2.1 Netlink 是什么)
    • [2.2 Netlink 协议族](#2.2 Netlink 协议族)
    • [2.3 消息格式:nlmsghdr](#2.3 消息格式:nlmsghdr)
    • [2.4 用户态收 uevent(设备热插拔)实例](#2.4 用户态收 uevent(设备热插拔)实例)
    • [2.5 通用 Netlink(genetlink,自定义协议)](#2.5 通用 Netlink(genetlink,自定义协议))
  • [三、fd 三件套:eventfd / signalfd / timerfd](#三、fd 三件套:eventfd / signalfd / timerfd)
    • [3.1 eventfd(事件通知)](#3.1 eventfd(事件通知))
    • [3.2 signalfd(信号转 fd)](#3.2 signalfd(信号转 fd))
    • [3.3 timerfd(定时器转 fd)](#3.3 timerfd(定时器转 fd))
  • [四、epoll(IO 多路复用)](#四、epoll(IO 多路复用))
    • [4.1 为什么 epoll](#4.1 为什么 epoll)
    • [4.2 epoll API](#4.2 epoll API)
    • [4.3 水平触发(LT) vs 边缘触发(ET)](#4.3 水平触发(LT) vs 边缘触发(ET))
  • 五、futex(用户态快速互斥底层)
    • [5.1 futex 是什么](#5.1 futex 是什么)
    • [5.2 futex 系统调用](#5.2 futex 系统调用)
    • [5.3 futex 实现 mutex 的原理(简化)](#5.3 futex 实现 mutex 的原理(简化))
  • [六、pthread 线程同步](#六、pthread 线程同步)
    • [6.1 线程 vs 进程(再强调)](#6.1 线程 vs 进程(再强调))
    • [6.2 pthread 同步原语](#6.2 pthread 同步原语)
    • [6.3 pthread mutex 底层 = futex](#6.3 pthread mutex 底层 = futex)
    • [6.4 进程间 pthread mutex(跨进程同步)](#6.4 进程间 pthread mutex(跨进程同步))
    • [6.5 与 07 内核态同步对照](#6.5 与 07 内核态同步对照)
  • [七、内核↔用户态通信总览(BSP 主场)](#七、内核↔用户态通信总览(BSP 主场))
    • [7.1 六条路对比](#7.1 六条路对比)
    • [7.2 ioctl(精准命令)](#7.2 ioctl(精准命令))
    • [7.3 sysfs(属性文件)](#7.3 sysfs(属性文件))
    • [7.4 procfs / debugfs](#7.4 procfs / debugfs)
    • [7.5 字符设备 + poll(数据流 + 事件等待)](#7.5 字符设备 + poll(数据流 + 事件等待))
    • [7.6 Netlink(异步事件,见 2)](#7.6 Netlink(异步事件,见 2))
      • [7.7 BSP 选型决策](#7.7 BSP 选型决策)
  • [八、对照 FreeRTOS / RISC-V](#八、对照 FreeRTOS / RISC-V)
    • [8.1 内核↔用户态通信对照(本篇核心)](#8.1 内核↔用户态通信对照(本篇核心))
    • [8.2 线程同步对照](#8.2 线程同步对照)
    • [8.3 RISC-V 视角](#8.3 RISC-V 视角)
  • [九、AEGIX BSP 实战](#九、AEGIX BSP 实战)
    • [9.1 驱动→用户态:功能安全告警(genetlink 多播)](#9.1 驱动→用户态:功能安全告警(genetlink 多播))
    • [9.2 字符设备 + poll:传感器数据流](#9.2 字符设备 + poll:传感器数据流)
    • [9.3 sysfs + ioctl:驱动配置](#9.3 sysfs + ioctl:驱动配置)
    • [9.4 守护进程事件循环(epoll 统一)](#9.4 守护进程事件循环(epoll 统一))
  • [十、IPC 全收官:总决策树与速查](#十、IPC 全收官:总决策树与速查)
    • [10.1 IPC 全机制速查(上下篇合)](#10.1 IPC 全机制速查(上下篇合))
    • [10.2 IPC 选择总决策树(全收官)](#10.2 IPC 选择总决策树(全收官))
    • [10.3 epoll 统一事件循环(现代架构)](#10.3 epoll 统一事件循环(现代架构))
      • [10.4 陷阱速查(下篇)](#10.4 陷阱速查(下篇))
    • [10.5 内核对象速查(下篇)](#10.5 内核对象速查(下篇))
  • 十一、相关笔记

一、Unix Domain Socket

1.1 AF_UNIX 是什么

Unix Domain Socket = 本机进程间的 socket(AF_UNIX/AF_LOCAL)。和 INET socket(14 网络篇,跨主机)同 API,但不走网络栈 (无 IP/TCP),数据在本机内核 buffer 拷贝,所以快。

AF_UNIX(本机) AF_INET(网络)
范围 同主机 跨主机
寻址 路径名(/tmp/sock)或抽象名 IP+端口
协议 无 IP/TCP 开销 经完整网络栈
速度 快(本机内存拷贝) 慢(协议栈开销)
类型 SOCK_STREAM / SOCK_DGRAM / SOCK_SEQPACKET 同

1.2 基本用法(流式,服务器/客户端)

c 复制代码
#include <sys/socket.h>
#include <sys/un.h>

// 服务器
int srv = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strcpy(addr.sun_path, "/tmp/aegix.sock");
unlink("/tmp/aegix.sock");                // 先删旧(防 bind 失败)
bind(srv, (struct sockaddr *)&addr, sizeof(addr));
listen(srv, 5);
int cli = accept(srv, NULL, NULL);
char buf[256]; int n = read(cli, buf, sizeof(buf));   // 收
write(cli, "ok", 2);                                   // 回

// 客户端
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strcpy(addr.sun_path, "/tmp/aegix.sock");
connect(s, (struct sockaddr *)&addr, sizeof(addr));
write(s, "hello", 5);
char buf[256]; read(s, buf, sizeof(buf));
  • SOCK_STREAM:有序可靠流(类似 TCP);SOCK_DGRAM:数据报(类似 UDP,本机仍可靠);SOCK_SEQPACKET:保边界有序;
  • 地址是文件系统路径 (socket 文件,ls -l 显示 s 类型),bind 创建该文件,需 unlink 旧文件;
  • 抽象命名空间 :路径首字节为 0(sun_path[0]='\0'),不落文件系统,随进程消失,无权限问题(适合临时 socket)。

1.3 SCM_RIGHTS:传文件描述符(高阶能力)

Unix socket 的杀手锏 ------能跨进程传 fd(把一个进程打开的 fd 传给另一个进程,内核在目标进程复制一份):

c 复制代码
// 发送端:把 fd_to_send 通过 socket 发出
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof(int))];
msg.msg_control = buf; msg.msg_controllen = sizeof(buf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
*(int *)CMSG_DATA(cmsg) = fd_to_send;
sendmsg(sock, &msg, 0);

// 接收端
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof(int))];
msg.msg_control = buf; msg.msg_controllen = sizeof(buf);
recvmsg(sock, &msg, 0);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
int got_fd = *(int *)CMSG_DATA(cmsg);   // 收到一个新的 fd(指向同一文件)
  • 内核在目标进程 dup 一个新的 fd 指向同一 struct file;
  • 用途:① 权限分离 (特权进程 open 文件,fd 传给非特权进程);② 容器/沙箱(传 fd 进 namespace);③ Android 的 binder 也借鉴此模式;
  • SCM_CREDENTIALS 还能传 PID/UID/GID(发送方身份)。

1.4 内核实现

  • AF_UNIX socket 在内核是 struct unix_sock,挂在 14 篇的 struct socket/struct sock 之上;
  • 数据流:send → 写入对端 sk_receive_queue(sk_buff 链,对接 14)→ 对端 recv;
  • SCM_RIGHTS:内核 __scm_send/__scm_recv,经 fget/fget_raw 引用 struct file,接收方 fd_install 新 fd;
  • 比 pipe 强 :双向、流或数据报、可传 fd、可 select/epoll;比 pipe 慢一点点(更通用),但本机 IPC 首选。

嵌入式视角:Unix socket 是本机 IPC 现代首选

新代码本机双向通信,Unix socket > FIFO + 信号 。原因:

① 双向(管道单向);

② 可传 fd(SCM_RIGHTS,管道不行);

③ 流或数据报可选;

④ 配 epoll 统一事件循环。守护进程和本地服务的双向 RPC,用 Unix socket。


二、Netlink(内核↔用户态主力)

Netlink = Linux 专用的内核↔用户态 异步通信机制,基于 socket(AF_NETLINK)。它是用户态配置/监听内核的现代标准:路由表、网卡、uevent(设备热插拔)、防火墙、CPU 频率......都用 Netlink。

Netlink ioctl sysfs/procfs
方向 双向(内核↔用户) 单向(用户→内核为主) 单向(读为主)
模式 异步(内核主动推事件) 同步(用户主动调) 同步(用户读)
数据 结构化消息(nlmsghdr) 任意(命令+参数) 文本
多对多 支持(多播组) 1对1 1对1
适合 事件/配置/大数据 简单命令 简单参数/状态

AF_NETLINK 下有多个协议(用 socket(AF_NETLINK, SOCK_RAW, protocol) 第三参数选):

协议 用途
NETLINK_ROUTE 路由/网卡/邻居表(ip link/ip route 底层)
NETLINK_KOBJECT_UEVENT 设备热插拔事件(udev 监听)
NETLINK_NETFILTER nfnetlink(防火墙规则)
NETLINK_GENERIC 通用 Netlink(genetlink,自定义内核↔用户协议主力)
NETLINK_SELINUX SELinux 事件
NETLINK_CRYPTO 内核加密框架

2.3 消息格式:nlmsghdr

c 复制代码
struct nlmsghdr {
    __u32 nlmsg_len;     // 整个消息长度(含头)
    __u16 nlmsg_type;    // 消息类型(协议自定义)
    __u16 nlmsg_flags;   // NLM_F_REQUEST/NLM_F_MULTI/NLM_F_ACK...
    __u32 nlmsg_seq;     // 序列号(配请求)
    __u32 nlmsg_pid;     // 发送方端口 ID(通常=pid)
};  // 后跟 payload(可嵌套属性 nlattr)

每条 Netlink 消息 = nlmsghdr + payload;多条消息可拼一个 recv buffer(用 NLMSG_NEXT 遍历);payload 内用 TLV 属性 (struct nlattr:len+type+data),支持嵌套。

2.4 用户态收 uevent(设备热插拔)实例

c 复制代码
#include <linux/netlink.h>
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
struct sockaddr_nl addr = {.nl_family = AF_NETLINK, .nl_groups = 1};  // 1=kernel uevent 组
bind(sock, (struct sockaddr *)&addr, sizeof(addr));

char buf[8192];
for (;;) {
    int n = recv(sock, buf, sizeof(buf), 0);
    // buf 是文本(uevent 是文本格式,如 "ACTION@add\nDEVPATH=/devices/...\nSUBSYSTEM=usb\n")
    printf("uevent: %.*s\n", n, buf);
    // 解析 ACTION(add/remove)/DEVPATH/SUBSYSTEM,触发 udev 规则
}
  • udev 守护进程就是监听 NETLINK_KOBJECT_UEVENT,收到 add 事件 → 按规则建 /dev 节点(对接 【linux内核专栏 10】设备模型);
  • udevadm monitor 看到的就是 Netlink uevent 流;
  • 热插拔(USB 插入/SIM 卡)靠这个。

2.5 通用 Netlink(genetlink,自定义协议)

genetlink 让你定义自己的内核↔用户协议(驱动自定义事件/配置):

c 复制代码
// 内核侧:注册一个 genetlink family
#include <net/genetlink.h>
enum my_cmd { MY_CMD_GET, MY_CMD_SET, __MY_CMD_MAX };
enum my_attr { MY_ATTR_VAL, __MY_ATTR_MAX };

static const struct nla_policy my_policy[__MY_ATTR_MAX] = {
    [MY_ATTR_VAL] = { .type = NLA_U32 },
};
static int my_doit(struct sk_buff *skb, struct genl_info *info) {
    // 用户态发来的消息处理
    if (info->attrs[MY_ATTR_VAL])
        my_val = nla_get_u32(info->attrs[MY_ATTR_VAL]);
    return 0;
}
static const struct genl_ops my_ops[] = {
    { .cmd = MY_CMD_SET, .doit = my_doit },
};
static struct genl_family my_family = {
    .name = "AEGIX", .version = 1, .maxattr = __MY_ATTR_MAX-1,
    .policy = my_policy, .ops = my_ops, .n_ops = ARRAY_SIZE(my_ops),
};
genl_register_family(&my_family);

// 内核主动推事件给用户态(多播)
void my_notify_event(u32 val) {
    struct sk_buff *skb = genlmsg_new(nla_total_size(4), GFP_KERNEL);
    struct nlmsghdr *nlh = genlmsg_put(skb, 0, 0, &my_family, 0, MY_CMD_GET);
    nla_put_u32(skb, MY_ATTR_VAL, val);
    genlmsg_end(skb, nlh);
    genlmsg_multicast(&my_family, skb, 0, 0, GFP_KERNEL);   // 组 0
}
c 复制代码
// 用户态:打开 genetlink,查 family id,收发
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
// 先发 CTRL_CMD_GETFAMILY 查 "AEGIX" 的 family id(动态分配)
// 再用该 id 发 MY_CMD_SET + MY_ATTR_VAL,或加入多播组收事件
  • genetlink 是自定义内核↔用户协议的标准框架(比 ioctl 结构化、可多播、可异步);
  • 驱动要"异步把事件推给用户态守护进程"------genetlink 多播是现代做法。

Netlink 是 BSP 必学:内核↔用户态现代标准

老办法是 ioctl(同步、命令式),新办法是 Netlink(异步、结构化、可多播)。内核主动推事件(热插拔/链路状态/自定义告警)只能 Netlink。你 AEGIX 驱动要实时通知用户态(如功能安全告警),genetlink 多播是首选。ioctl 留给简单同步命令。


三、fd 三件套:eventfd / signalfd / timerfd

Linux 2.6.22+ 引入的"把各种事件源 fd 化 "机制------让事件能塞进 epoll 统一等待。核心思想:一切皆 fd,一切可 epoll。

3.1 eventfd(事件通知)

c 复制代码
#include <sys/eventfd.h>
int efd = eventfd(0, EFD_CLOEXEC);   // 初值 0

// 写(通知方):写一个 64 位整数(累加到内部计数器)
uint64_t one = 1;
write(efd, &one, 8);                 // 计数器 += 1
write(efd, &one, 8);                 // 计数器 += 1(=2)

// 读(等待方):读计数器并清零(或减)
uint64_t v;
read(efd, &v, 8);                    // v=2,计数器清零
  • 内核一个 64 位计数器,write 累加,read 读出并清零(默认)或减 EFD_SEMAPHORE(每次减 1);
  • 可 epoll:计数器>0 时 fd 可读;
  • 比信号好:不异步、可统计、可 epoll 统一等待;
  • 用途:线程/进程间轻量事件通知(替代信号/管道做简单通知);sem_post/sem_wait(POSIX 信号量)底层在某些实现就用 eventfd。

3.2 signalfd(信号转 fd)

c 复制代码
#include <sys/signalfd.h>
sigset_t mask; sigemptyset(&mask);
sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);          // 必须先屏蔽(不让默认投递)
int sfd = signalfd(-1, &mask, SFD_CLOEXEC);

// 不装 handler,改成 read
struct signalfd_siginfo si;
read(sfd, &si, sizeof(si));                   // 阻塞等信号
// si.ssi_signo == SIGINT/SIGTERM,可拿 siginfo(发送方 pid/uid 等)
  • 把信号"fd 化":不再写异步 handler,改 read 同步收;
  • 可 epoll:信号来时 fd 可读;
  • 优势:① 不用担心信号不安全函数;② 信号 siginfo 都拿到;③ 和其他 fd 统一进 epoll 事件循环;
  • 必须先 sigprocmask(SIG_BLOCK) 屏蔽信号(否则既走默认 handler 又走 signalfd)。

3.3 timerfd(定时器转 fd)

c 复制代码
#include <sys/timerfd.h>
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
struct itimerspec spec = {
    .it_interval = {1, 0},    // 周期 1 秒
    .it_value    = {1, 0},    // 首次 1 秒后
};
timerfd_settime(tfd, 0, &spec, NULL);

uint64_t exp;
read(tfd, &exp, 8);           // 1 秒后返回,exp=超时次数(可累计多次)
  • 内核 hrtimer(08 时间篇)到期 → fd 可读;
  • 可 epoll:定时器到时 fd 可读;
  • 优势:把定时器塞进 epoll 事件循环,不用单独 alarm/setitimer+信号;
  • CLOCK_MONOTONIC(单调,不受系统时间调整影响) vs CLOCK_REALTIME(墙钟,可跳变)。

fd 三件套 + epoll = 现代事件循环标配

老 Unix 信号 + alarm + 多 select 是异步地狱。现代做法:一个 epoll,把网络 socket + Unix socket + eventfd(线程通知) + signalfd(信号) + timerfd(定时器)全挂进去,单线程事件循环统一等待,清晰且高效。写守护进程,这是标准架构。


四、epoll(IO 多路复用)

4.1 为什么 epoll

一个进程要同时等很多 fd(网络连接 + IPC + 定时器 + 信号)------不能每个 fd 一个线程阻塞读(线程开销)。IO 多路复用:一个调用同时盯多个 fd,谁就绪返回谁。

机制 性能 触发 用法
select O(n),fd 上限 1024 水平 老
poll O(n),无上限 水平 老改进
epoll O(1)(就绪返回),无上限 水平/边缘 Linux 现代首选

epoll 快的原因:① 内核维护就绪链表,epoll_wait 直接返回就绪 fd(不扫全部);② fd 只 epoll_ctl 注册一次(不像 select 每次重传)。

4.2 epoll API

c 复制代码
#include <sys/epoll.h>
int ep = epoll_create1(EPOLL_CLOEXEC);          // 建 epoll 实例

// 注册 fd
struct epoll_event ev = {.events = EPOLLIN, .data.fd = my_fd};
epoll_ctl(ep, EPOLL_CTL_ADD, my_fd, &ev);       // 加
// EPOLL_CTL_MOD 改 / EPOLL_CTL_DEL 删

// 等待
struct epoll_event events[16];
int n = epoll_wait(ep, events, 16, -1);         // -1 永久等
for (int i = 0; i < n; i++) {
    int fd = events[i].data.fd;
    if (events[i].events & EPOLLIN) {
        char buf[256]; read(fd, buf, sizeof(buf));   // 处理
    }
}

4.3 水平触发(LT) vs 边缘触发(ET)

水平 LT(默认) 边缘 ET(EPOLLET)
就绪通知 只要 fd 可读(没读完)就反复通知 只在状态变化时通知一次
读法 任意大小,读多少算多少 必须循环读到 EAGAIN(否则漏)
编程 简单 难(必须配合非阻塞 fd + 循环读)
效率 略低(可能多次唤醒) 高(少唤醒)
c 复制代码
// 边缘触发必须这样读(非阻塞 fd + 循环到 EAGAIN)
for (;;) {
    int n = read(fd, buf, sizeof(buf));
    if (n > 0) { handle(buf, n); continue; }
    if (n == 0) { close_fd(); break; }      // EOF
    if (errno == EAGAIN) break;              // 读完了,等下次 ET
    if (errno == EINTR) continue;            // 信号打断重试
    handle_error(); break;
}

嵌入式视角:epoll 是 Linux 服务端/守护进程核心

守护进程要同时:听 Unix socket(本机 RPC)+ 听 Netlink(内核事件)+ 听 timerfd(周期采集)+ 听 signalfd(信号)+ 听 eventfd(工作线程通知)------一个 epoll 全挂,单线程或线程池处理。这是 Linux 守护进程的标准架构,对标 FreeRTOS 的"多任务 + 队列"模型。


五、futex(用户态快速互斥底层)

5.1 futex 是什么

futex (Fast Userspace Mutex)是 Linux 线程同步的底层基石------pthread mutex/cond/semaphore(POSIX 线程库,NPTL 实现)都建在 futex 之上。

核心思想:① 无竞争时纯用户态原子操作 (快,不进内核);② 有竞争才 syscall 进内核睡眠/唤醒(慢路径)。

5.2 futex 系统调用

c 复制代码
#include <linux/futex.h>
long syscall(SYS_futex, int *uaddr, int op, int val,
             const struct timespec *timeout, int *uaddr2, int val3);
  • uaddr:用户态对齐 int(锁变量,跨线程共享);
  • op:FUTEX_WAIT(值==val 则睡在这把锁上)/ FUTEX_WAKE(唤醒等在这把锁上的 N 个);
  • 无竞争:FUTEX_WAIT 发现值已变(≠val)立即返回(不睡);
  • 核心:锁的"快速路径"在用户态原子 CAS,只有需要等/唤醒才进内核 ------所以无竞争的加锁解锁零 syscall。

5.3 futex 实现 mutex 的原理(简化)

c 复制代码
// 简化的 futex-based mutex
typedef struct { int val; } futex_mutex;   // 0=空,1=持有

void lock(futex_mutex *m) {
    int c;
    while ((c = __atomic_cmpxchg(&m->val, 0, 1)) != 0) {   // 用户态 CAS 试抢
        if (c == 2) {                                       // 已有人等,我也等
            syscall(SYS_futex, &m->val, FUTEX_WAIT, 2, NULL, NULL, 0);
        } else {
            // c==1,有人持锁但还没人等,我把它设成 2 标记"有人等了"
            if (__atomic_cmpxchg(&m->val, 1, 2) != 1) continue;
            syscall(SYS_futex, &m->val, FUTEX_WAIT, 2, NULL, NULL, 0);
        }
    }
}

void unlock(futex_mutex *m) {
    int prev = __atomic_xchg(&m->val, 0);   // 释放
    if (prev == 2) {                        // 有等待者,唤醒一个
        syscall(SYS_futex, &m->val, FUTEX_WAKE, 1, NULL, NULL, 0);
    }
}
  • 状态 0(空)/1(持有,无人等)/2(持有,有人等);
  • 无竞争:lock 一次 CAS 拿到(0→1),unlock 一次 CAS 释放(1→0),零 syscall;
  • 有竞争:才 FUTEX_WAIT 进内核睡,FUTEX_WAKE 唤醒;
  • 内核维护 futex_hash 桶,按 uaddr 物理地址挂等待队列。

futex 是"用户态快+内核态等"的混合

对照 FreeRTOS:FreeRTOS 信号量 xSemaphoreTake 在单核上每次都进内核 (虽然轻),因为单核任务切换必须经内核。Linux futex 让无竞争的锁操作完全不进内核 (纯用户态原子指令),只有竞争才进内核------这是多核多线程性能的关键。 07 篇的内核态 mutex 内部也用类似思路(快速路径原子,慢路径进睡眠队列),只是内核态不需要 futex 系统调用。


六、pthread 线程同步

6.1 线程 vs 进程(再强调)

进程 线程
地址空间 独立(需 IPC) 共享(直接读写全局变量)
通信 IPC 机制(本两篇) 共享变量 + 同步原语
同步 SysV/POSIX 信号量、进程间 mutex pthread mutex/cond/rwlock
创建开销 大(fork 复制 mm) 小(共享 mm)

线程间通信不需要 IPC 机制 ------同进程线程共享地址空间,直接读写全局变量即可,需要的只是"同步"(防并发访问破坏)。所以"线程同步"本质是"共享变量的并发保护"。

6.2 pthread 同步原语

c 复制代码
#include <pthread.h>
// 1. mutex(互斥,最常用)
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mtx);
// 临界区
pthread_mutex_unlock(&mtx);

// 2. 条件变量(等条件成立,配 mutex)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_lock(&mtx);
while (!ready)                              // 必须 while(防虚假唤醒)
    pthread_cond_wait(&cond, &mtx);         // 原子释放 mtx 并睡,被唤醒重新锁 mtx
// 条件成立,处理
pthread_mutex_unlock(&mtx);
// 另一线程:pthread_mutex_lock(&mtx); ready=1; pthread_cond_signal(&cond); pthread_mutex_unlock(&mtx);

// 3. 读写锁(读多写少)
pthread_rwlock_t rwl;
pthread_rwlock_rdlock(&rwl);   // 多个读者并发
pthread_rwlock_wrlock(&rwl);   // 写者独占
pthread_rwlock_unlock(&rwl);

// 4. 屏障(等 N 个线程都到齐再一起走)
pthread_barrier_t bar;
pthread_barrier_init(&bar, NULL, 4);   // 4 个线程
pthread_barrier_wait(&bar);            // 4 个都到才全放行

// 5. 自旋锁(用户态,短临界区,不睡)
pthread_spinlock_t sp;
pthread_spin_lock(&sp);
// 极短临界区
pthread_spin_unlock(&sp);

6.3 pthread mutex 底层 = futex

  • pthread_mutex_t 内部就一个 int(状态)+ 一点属性;
  • pthread_mutex_lock 快速路径:__atomic_cmpxchg(0→1),成功返回(零 syscall);
  • 竞争时:FUTEX_WAIT 进内核睡;
  • pthread_cond_wait:FUTEX_WAIT 睡在 cond 的 futex 上,pthread_cond_signal 用 FUTEX_WAKE 唤醒;
  • 这就是为什么"无竞争的 pthread 锁极快"------纯用户态原子指令。

6.4 进程间 pthread mutex(跨进程同步)

c 复制代码
// mutex 放共享内存,设为 PTHREAD_PROCESS_SHARED
pthread_mutex_t *m = (void *)shared_mem;   // shared_mem 来自 mmap MAP_SHARED
pthread_mutexattr_t a;
pthread_mutexattr_init(&a);
pthread_mutexattr_setpshared(&a, PTHREAD_PROCESS_SHARED);   // 关键
pthread_mutex_init(m, &a);
// 现在多进程可 lock/unlock(底层 futex 操作共享地址)
  • PTHREAD_PROCESS_SHARED 让 mutex 跨进程可用(mutex 在共享内存里,底层 futex 按物理地址找等待队列);
  • 这是"现代跨进程同步"的首选(比 SysV sem API 干净);
  • cond/rwlock 也支持 PTHREAD_PROCESS_SHARED。

6.5 与 07 内核态同步对照

07 内核态同步 pthread 用户态同步(本篇)
用在哪 内核代码(驱动/子系统) 用户态多线程程序
原语 spinlock/mutex/semaphore/RCU pthread_mutex/cond/rwlock
实现 内核数据结构 + 调度器 用户态原子 + futex syscall
跨进程 spinlock/mutex 可(内核全局) PTHREAD_PROCESS_SHARED
健壮性 lockdep 检测 pthread mutex robust 属性

嵌入式视角:你写驱动用 07,写用户态用 pthread

① 内核驱动 里保护共享数据 → 用 07 的 spinlock/mutex;② 用户态守护进程 多线程 → 用本篇 pthread;③ 跨进程同步(用户态多进程)→ pthread PROCESS_SHARED 或 SysV/POSIX 信号量。三层不要混用------内核态原语不能给用户态,pthread 不能在内核代码里。


七、内核↔用户态通信总览(BSP 主场)

这是BSP 工程师的核心选型。内核↔用户态通信有六条路:

7.1 六条路对比

方式 方向 模式 适合 内核实现
copy_to/from_user 内核↔用户(同步) syscall 内拷贝 read/write 系统调用 06 syscall 篇
ioctl 用户→内核(同步) 命令+参数 精准命令(配置/控制) file_operations.unlocked_ioctl
procfs 内核→用户(读为主) 文本 状态/统计导出 /proc/
sysfs 内核↔用户(读写) 属性文件 设备参数(简单值) /sys/(10 设备模型)
debugfs 内核→用户(调试) 任意 调试导出(开发期) /sys/kernel/debug/
Netlink 内核↔用户(异步双向) 结构化消息 事件推送/配置/大数据 本篇 2
字符设备 read/poll 内核→用户(同步/事件) 字节流 数据流 + poll 等待 11 驱动框架

7.2 ioctl(精准命令)

c 复制代码
// 内核驱动:实现 unlocked_ioctl
long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg) {
    switch (cmd) {
    case MY_CMD_GET_VAL:
        if (copy_to_user((void *)arg, &my_val, sizeof(my_val)))
            return -EFAULT;
        break;
    case MY_CMD_SET_VAL:
        if (copy_from_user(&my_val, (void *)arg, sizeof(my_val)))
            return -EFAULT;
        break;
    }
    return 0;
}
// file_operations: .unlocked_ioctl = my_ioctl

// 用户态
int fd = open("/dev/mydev", O_RDWR);
int v = 42;
ioctl(fd, MY_CMD_SET_VAL, &v);
int got; ioctl(fd, MY_CMD_GET_VAL, &got);
  • cmd 编码:_IO/_IOR/_IOW/_IOWR(魔数+命令号+参数大小),防误调;
  • 适合"精准控制命令"(配置寄存器/启停/设模式);
  • 优点:快、同步、精准;缺点:每个命令要定义、不可异步、不可多播。

7.3 sysfs(属性文件)

c 复制代码
// 内核:用 DEVICE_ATTR 宏导出
static ssize_t val_show(struct device *dev, struct device_attribute *attr, char *buf) {
    return sprintf(buf, "%d\n", my_val);
}
static ssize_t val_store(struct device *dev, struct device_attribute *attr,
                        const char *buf, size_t count) {
    my_val = simple_strtol(buf, NULL, 10);
    return count;
}
static DEVICE_ATTR_RW(val);   // 生成 /sys/.../val
device_create_file(dev, &dev_attr_val);

// 用户态:直接 cat/echo
// cat /sys/devices/.../val       (读)
// echo 42 > /sys/devices/.../val (写)
  • 简单值(一个文件一个值),shell 可直接操作(cat/echo);
  • 对接 10 设备模型(sysfs 是设备树的投影);
  • 适合"简单参数/状态",不适合大数据/高频(每次一个字符串);
  • 规则:一个属性一个值,文本,用 sysfs_emit。

7.4 procfs / debugfs

c 复制代码
// procfs(状态导出,适合只读统计)
static int my_proc_show(struct seq_file *m, void *v) {
    seq_printf(m, "count=%d\n", my_count);
    return 0;
}
proc_create_single("aegix_stat", 0, NULL, my_proc_show);   // /proc/aegix_stat

// debugfs(调试,任意,开发期)
struct dentry *d = debugfs_create_u32("my_val", 0644, NULL, &my_val);
// /sys/kernel/debug/my_val
debugfs_create_file("regs", 0444, NULL, NULL, &regs_fops);
  • procfs:/proc/,历史老,适合"系统级统计"(如 /proc/meminfo);
  • debugfs:/sys/kernel/debug/,调试用(生产可挂载关掉),最灵活(任意类型/格式);
  • AEGIX 开发期 debugfs 导出寄存器/统计,生产摘掉。

7.5 字符设备 + poll(数据流 + 事件等待)

c 复制代码
// 内核:read + poll
static ssize_t my_read(struct file *f, char __user *buf, size_t n, loff_t *off) {
    if (kfifo_is_empty(&my_fifo)) return 0;     // 或 -EAGAIN(非阻塞)
    int cnt = kfifo_to_user(&my_fifo, buf, n, &cnt);   // 出队到用户
    return cnt;
}
static __poll_t my_poll(struct file *f, struct poll_table_struct *pt) {
    poll_wait(f, &my_waitq, pt);
    return kfifo_is_empty(&my_fifo) ? 0 : EPOLLIN;
}
// 中断/工作队列里收到数据:kfifo_in + wake_up_interruptible(&my_waitq)

// 用户态:epoll 等
struct epoll_event ev = {.events = EPOLLIN, .data.fd = fd};
epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev);
epoll_wait(ep, events, 16, -1);   // 数据来时唤醒
read(fd, buf, n);
  • 这是"内核驱动→用户态数据流"的经典模式(对接 11 驱动框架);
  • 中断收数据 → kfifo 缓存 → wake_up → 用户态 poll 唤醒 → read;
  • 适合"持续数据流"(传感器采集/事件流)。

7.6 Netlink(异步事件,见 2)

内核主动推事件(uevent/genetlink 多播),用户态 epoll 收。适合"内核主动通知"(热插拔/告警)。

7.7 BSP 选型决策

复制代码
驱动↔用户态通信需求?
├─ 配置/控制(用户→内核,同步)
│   ├─ 简单参数(一个值)→ sysfs(cat/echo)
│   ├─ 精准命令(多命令/带参)→ ioctl
│   └─ 复杂结构/可扩展 → genetlink
├─ 状态/统计(内核→用户,读为主)
│   ├─ 系统级统计 → procfs
│   ├─ 调试导出 → debugfs
│   └─ 设备属性 → sysfs
├─ 数据流(内核→用户,持续)
│   └─ 字符设备 + read + poll + kfifo(11 框架)
├─ 事件通知(内核→用户,异步)
│   ├─ 通用 → genetlink 多播(首选)
│   ├─ 热插拔 → uevent(NETLINK_KOBJECT_UEVENT)
│   └─ 简单(同主机)→ 字符设备 + poll
└─ 大数据(零拷贝)
    └─ mmap(内核建页,用户映射,对接 04)

八、对照 FreeRTOS / RISC-V

8.1 内核↔用户态通信对照(本篇核心)

通信需求 FreeRTOS(无用户态) Linux BSP
配置驱动参数 直接调驱动函数 sysfs/ioctl/genetlink
驱动事件通知 回调函数/队列 Netlink/poll+read
数据流 直接调驱动 read 字符设备 + poll + kfifo
调试导出 printf procfs/debugfs/sysfs
大数据 共享全局缓冲 mmap

FreeRTOS 没有"内核↔用户态"问题

FreeRTOS 任务全在内核态(无用户态隔离),驱动和任务直接调函数/读写全局缓冲------没有 Linux 那套"经系统调用/文件"的隔阂。这是你转 Linux BSP 的核心冲击:你习惯"直接调",Linux 强制"经文件/syscall/Netlink"。这层隔阂是保护(用户态崩不影响内核),也是开销(每次 syscall 上下文切换)。

8.2 线程同步对照

FreeRTOS pthread(用户态) 07 内核态
互斥 mutex(优先级继承) pthread_mutex(futex) mutex
条件等 任务通知 + while pthread_cond_wait wait_event + condition
读写锁 无(自己用 mutex) pthread_rwlock rwlock(原子)
屏障 无(自己计数) pthread_barrier completion(一次性)
自旋 无(单核) pthread_spinlock spinlock(多核)
快速路径 进内核(单核必须) 用户态原子(无竞争零 syscall) 用户态原子 + 内核睡眠队列

8.3 RISC-V 视角

  • futex:FUTEX_WAIT 在 RISC-V 上经 ecall(06)进内核,内核按 uaddr 物理地址(经页表查 Sv39,04/06b)找等待队列;无竞争的快速路径用 RISC-V amoswap.w/lr.w/sc.w(A 扩展,08 RISC-V)做 CAS;
  • epoll:RISC-V 上 epoll 是纯软件(不依赖架构),但 epoll_wait 的等待队列唤醒经调度器(03);
  • Netlink:RISC-V 上 socket(AF_NETLINK, ...) 走标准 syscall,无架构依赖;
  • Unix socket:本机数据拷贝,经 copy_to_user/copy_from_user(06),RISC-V 上是 ld/sd 跨用户/内核地址(页表切换已配好)。

九、AEGIX BSP 实战

AEGIX 驱动检测到功能安全告警(如过温/电压异常),主动推给用户态监控进程:

c 复制代码
// 内核驱动:注册 genetlink family "AEGIX_ALERT",定义 ALERT_CMD_NOTIFY
void aegix_alert_notify(u32 alert_code, u32 severity) {
    struct sk_buff *skb = genlmsg_new(nla_total_size(8), GFP_ATOMIC);
    struct nlmsghdr *nlh = genlmsg_put(skb, 0, 0, &alert_family, 0, ALERT_CMD_NOTIFY);
    nla_put_u32(skb, ALERT_ATTR_CODE, alert_code);
    nla_put_u32(skb, ALERT_ATTR_SEVERITY, severity);
    genlmsg_end(skb, nlh);
    genlmsg_multicast(&alert_family, skb, 0, ALERT_MC_GRP, GFP_ATOMIC);
}
// 中断/workqueue 里调 aegix_alert_notify(OVERTEMP, HIGH)

// 用户态监控进程
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
// 查 "AEGIX_ALERT" family id,加入 ALERT_MC_GRP 多播组
// epoll 挂 sock,收到时 parse nlmsghdr + nla,按 alert_code 处理
  • 优势:异步、可多播(多个监控进程都收)、结构化(带 code/severity);
  • 比 ioctl(只能用户主动查)强------内核主动推。

9.2 字符设备 + poll:传感器数据流

AEGIX IMU 传感器驱动,中断收数据,用户态 poll 等待:

c 复制代码
// 内核(简化,完整框架见 11 驱动篇)
static DECLARE_WAIT_QUEUE_HEAD(imu_waitq);
static DEFINE_KFIFO(imu_fifo, struct imu_sample, 256);

static irqreturn_t imu_irq(int irq, void *dev) {
    struct imu_sample s = read_imu_hw();          // 读硬件
    kfifo_in(&imu_fifo, &s, 1);                   // 入队
    wake_up_interruptible(&imu_waitq);            // 唤醒等待者
    return IRQ_HANDLED;
}
static ssize_t imu_read(struct file *f, char __user *buf, size_t n, loff_t *off) {
    int cnt; struct imu_sample s;
    if (kfifo_is_empty(&imu_fifo)) return 0;
    kfifo_out(&imu_fifo, &s, 1);
    copy_to_user(buf, &s, sizeof(s));
    return sizeof(s);
}
static __poll_t imu_poll(struct file *f, struct poll_table_struct *pt) {
    poll_wait(f, &imu_waitq, pt);
    return kfifo_is_empty(&imu_fifo) ? 0 : EPOLLIN;
}
// file_operations: .read=imu_read, .poll=imu_poll
c 复制代码
// 用户态
int fd = open("/dev/aegix_imu", O_RDONLY);
struct epoll_event ev = {.events = EPOLLIN, .data.fd = fd};
epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev);
while (1) {
    epoll_wait(ep, events, 16, -1);     // 中断→wake_up→唤醒
    struct imu_sample s;
    read(fd, &s, sizeof(s));
    process(&s);
}
  • 中断(顶半部或 threaded irq,05)收数 → kfifo 缓存 → wake_up → 用户态 epoll_wait 唤醒 → read;
  • 这是 Linux 字符设备数据流的标准模式,对接 05 中断 + 11 驱动框架。

9.3 sysfs + ioctl:驱动配置

c 复制代码
// sysfs:采样率(简单值,shell 可调)
static ssize_t rate_show(struct device *d, struct device_attribute *a, char *b) {
    return sprintf(b, "%d\n", imu_rate);
}
static ssize_t rate_store(struct device *d, struct device_attribute *a,
                          const char *b, size_t c) {
    imu_rate = simple_strtol(b, NULL, 10); imu_hw_set_rate(imu_rate);
    return c;
}
static DEVICE_ATTR_RW(rate);
// echo 100 > /sys/.../rate

// ioctl:校准命令(精准,带参)
#define IMU_IOC_CALIBRATE _IOW('I', 1, struct imu_calib_param)
long imu_ioctl(struct file *f, unsigned int cmd, unsigned long arg) {
    if (cmd == IMU_IOC_CALIBRATE) {
        struct imu_calib_param p;
        copy_from_user(&p, (void *)arg, sizeof(p));
        return do_calibrate(&p);
    }
}
  • sysfs 给"简单可调参数"(shell 一行调);
  • ioctl 给"精准命令"(带结构参数,程序调);
  • 分工:能 sysfs 就 sysfs(易用),复杂才 ioctl。

9.4 守护进程事件循环(epoll 统一)

AEGIX 守护进程一个线程盯所有事件源:

c 复制代码
int ep = epoll_create1(EPOLL_CLOEXEC);
// 1. genetlink 告警
epoll_ctl(ep, EPOLL_CTL_ADD, alert_sock, &(struct epoll_event){EPOLLIN, .data={ALERT_FD}});
// 2. 字符设备数据
epoll_ctl(ep, EPOLL_CTL_ADD, imu_fd,   &(struct epoll_event){EPOLLIN, .data={IMU_FD}});
// 3. Unix socket(本机 RPC)
epoll_ctl(ep, EPOLL_CTL_ADD, rpc_sock, &(struct epoll_event){EPOLLIN, .data={RPC_FD}});
// 4. timerfd(周期采集)
epoll_ctl(ep, EPOLL_CTL_ADD, timer_fd, &(struct epoll_event){EPOLLIN, .data={TIMER_FD}});
// 5. signalfd(信号)
epoll_ctl(ep, EPOLL_CTL_ADD, sig_fd,   &(struct epoll_event){EPOLLIN, .data={SIG_FD}});

struct epoll_event ev[16];
while (1) {
    int n = epoll_wait(ep, ev, 16, -1);
    for (int i = 0; i < n; i++) {
        switch (ev[i].data.u32) {
        case ALERT_FD: handle_alert(); break;
        case IMU_FD:   handle_imu();   break;
        case RPC_FD:   handle_rpc();   break;
        case TIMER_FD: handle_timer(); break;
        case SIG_FD:   handle_sig();   break;
        }
    }
}
  • 一个 epoll 实例盯 5 类事件源,单线程事件循环;
  • 这是 Linux 守护进程标准架构,对标 FreeRTOS 多任务 + 队列(每个任务盯一个队列)。

十、IPC 全收官:总决策树与速查

10.1 IPC 全机制速查(上下篇合)

机制 传什么 方向 范围 模式 典型
pipe 字节流 单向 父子 同步 shell `
FIFO 字节流 单向 任意 同步 命名管道
signal 信号号 单向 任意 异步 kill
SysV msgq 消息 单向 任意 同步 按类型分发
SysV shm 共享页 双向 任意 共享 大数据
SysV sem 同步 --- 任意 --- 资源计数
mmap 共享 共享页 双向 亲缘/任意 共享 现代共享内存
flock/fcntl 文件锁 --- 同文件 --- 文件互斥
Unix socket 流/数据报 双向 同主机 同步/异步 本机 RPC
Netlink 结构化消息 双向 内核↔用户 异步 uevent/路由/告警
eventfd 事件计数 单向 任意 异步 线程通知
signalfd 信号→fd 单向 任意 异步 信号 fd 化
timerfd 定时→fd 单向 自身 异步 定时器 fd 化
epoll 多路等待 --- 自身 同步 事件循环
futex 互斥底层 --- 同主机 --- pthread 基石
pthread 同步原语 --- 进程内/跨进程 --- 多线程
ioctl 命令+参 单向 用户→内核 同步 驱动控制
procfs/sysfs/debugfs 文本 读为主 内核→用户 同步 状态/配置

10.2 IPC 选择总决策树(全收官)

复制代码
谁跟谁通信?
├─ 用户态进程 ↔ 用户态进程
│   ├─ 只通知 → eventfd(推荐)/signal(老)
│   ├─ 流式单向 → pipe(父子)/FIFO(任意)
│   ├─ 流式双向 → Unix socket(首选)/FIFO×2
│   ├─ 消息带边界 → Unix socket 数据报/消息队列
│   ├─ 大数据共享 → mmap MAP_SHARED(首选)/SysV shm(老)
│   └─ 同步 → pthread PROCESS_SHARED mutex(首选)/SysV sem/文件锁
│
├─ 内核 ↔ 用户态(BSP 主场)
│   ├─ 配参数(简单值)→ sysfs
│   ├─ 配参数(精准命令)→ ioctl
│   ├─ 状态/统计 → procfs/debugfs
│   ├─ 数据流 → 字符设备 + read + poll + kfifo
│   ├─ 事件推送(异步)→ genetlink 多播(首选)/uevent(热插拔)
│   └─ 大数据(零拷贝)→ mmap
│
├─ 同进程线程 ↔ 线程
│   ├─ 同步 → pthread mutex/cond/rwlock(底层 futex)
│   └─ 通知 → eventfd/条件变量
│
└─ 跨主机(不是 IPC,是网络)
    └─ INET socket(14 网络篇)

10.3 epoll 统一事件循环(现代架构)

复制代码
所有 fd-based 事件源 → 一个 epoll → 事件循环
  ├─ INET socket(网络连接,14)
  ├─ Unix socket(本机 RPC)
  ├─ Netlink socket(内核事件)
  ├─ 字符设备 fd(驱动数据)
  ├─ eventfd(线程通知)
  ├─ signalfd(信号)
  ├─ timerfd(定时器)
  └─ pipe/FIFO(流式 IPC)

10.4 陷阱速查(下篇)

陷阱 说明
Unix socket 路径残留 bind 前 unlink 旧文件,否则 EADDRINUSE
SCM_RIGHTS 收 fd 漏 CMSG 必须 msg_control 缓冲 + CMSG_SPACE
genetlink family id 动态 先 CTRL_CMD_GETFAMILY 查 id 再用
signalfd 没先屏蔽信号 信号又走默认 handler 又走 signalfd
timerfd 没读计数器 漏读会一直可读(水平)或丢超时(边缘)
epoll ET 不循环读到 EAGAIN 漏数据(ET 只通知一次)
futex uaddr 不对齐 内核拒绝(EFAULT/未定义)
pthread cond 不用 while 虚假唤醒导致条件不成立时误跑
进程间 mutex 没设 PROCESS_SHARED 跨进程加锁无效(各自独立)

10.5 内核对象速查(下篇)

机制 内核结构
Unix socket struct unix_sock + sk_buff(对接 14)
Netlink struct netlink_sock + genl_family/genl_ops
eventfd struct eventfd_ctx
signalfd struct signalfd_ctx(挂 sigqueue)
timerfd struct timerfd_ctx(挂 hrtimer,08)
epoll struct eventpoll + 红黑树(注册)+ 就绪链表
futex futex_hash_bucket + futex_q 等待队列
pthread mutex 用户态 int + futex(内核侧 futex 等待队列)
ioctl file_operations.unlocked_ioctl
sysfs struct attribute/device_attribute(10 设备模型)
procfs struct proc_dir_entry
debugfs struct dentry

十一、相关笔记

  • 【Linux 内核专栏 00】总目录 ------ 专题入口
  • \[17-进程间通信IPC上-管道-信号-SystemV-mmap-文件锁] ------ 上篇(本篇前置)
  • 【linux内核专栏 06】系统调用 ------ copy_to/from_user、futex、epoll_wait 都是 syscall
  • \[14-网络协议栈] ------ socket/sk_buff(AF_UNIX/Netlink 都建在 socket 之上)
  • 【linux内核专栏 07】同步与并发 ------ 内核态同步(对照本篇 pthread 用户态)
  • 【Linux内核专栏 12】VFS 与文件系统 ------ procfs/debugfs/sysfs 的 VFS 根基
  • \[10-设备模型] ------ sysfs/uevent(Netlink KOBJECT_UEVENT 的来源)
  • \[11-驱动框架 platform-char-misc-设备树] ------ 字符设备 read/poll/ioctl(本篇 BSP 主场)
  • \[08-时间与定时器] ------ timerfd 底层是 hrtimer
  • \[05-中断与下半部] ------ 中断→wake_up→poll(本篇 9.2)
  • \[03-进程管理与调度] ------ 线程/进程、等待队列、调度
  • \[FreeRTOS_设计思想与功能模块讲解] ------ 队列/任务通知/信号量对照
  • \[00-RISC-V架构专题总目录] ------ ecall/futex/A 扩展对照

\[00-Linux内核专题总目录] · 上一篇:\[17-进程间通信IPC上-管道-信号-SystemV-mmap-文件锁] · L2 补充篇(IPC 下)· IPC 全收官


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
吴佳浩 Alben1 小时前
一文看懂大模型推理为什么需要多卡:从 TP、DP 到 Runtime 的完整演进
运维·服务器·人工智能·深度学习·语言模型·vllm·sglang
djjjx.1 小时前
Linux进程篇(二):深入浅出 [进程概念与进程父子关系]:从底层原理到实战应用
linux·运维·服务器
爱喝水的鱼丶4 小时前
SAP-ABAP:S/4 HANA ABAP 新特性入门:与 ECC ABAP 的核心差异、架构优势与学习路径规划
运维·数据库·性能优化·sap·abap·hana·开发交流
安易算力10 小时前
昇腾CANN算子开发与MindSpore部署调优实践:从Ascend C到模型推理全链路
网络·架构
贵州诚鑫致达科技有限公司10 小时前
Linux的yum源怎么重启?源不是服务
linux·运维·服务器
尼喃10 小时前
OVP/OCP过压过流保护芯片选型表:2A、3A、5A与10A方案怎么选
嵌入式硬件
遇印记11 小时前
传输层,网络层
运维·服务器·网络·windows·学习
品牌常新11 小时前
景区巡逻机器人怎么选?派宝S5全地形守护解析
运维·人工智能·机器人