📚 Linux 内核专栏 · 持续更新中
本系列从底层原理到源码实战,逐篇拆解内核机制。
👉 想系统追更,戳这里查看完整专栏:
👉 其他技术系列专栏,欢迎关注、交流、共同成长:
本篇定位 :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(内核↔用户态主力)
2.1 Netlink 是什么
Netlink = Linux 专用的内核↔用户态 异步通信机制,基于 socket(AF_NETLINK)。它是用户态配置/监听内核的现代标准:路由表、网卡、uevent(设备热插拔)、防火墙、CPU 频率......都用 Netlink。
| Netlink | ioctl | sysfs/procfs | |
|---|---|---|---|
| 方向 | 双向(内核↔用户) | 单向(用户→内核为主) | 单向(读为主) |
| 模式 | 异步(内核主动推事件) | 同步(用户主动调) | 同步(用户读) |
| 数据 | 结构化消息(nlmsghdr) | 任意(命令+参数) | 文本 |
| 多对多 | 支持(多播组) | 1对1 | 1对1 |
| 适合 | 事件/配置/大数据 | 简单命令 | 简单参数/状态 |
2.2 Netlink 协议族
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(单调,不受系统时间调整影响) vsCLOCK_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, ®s_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-Vamoswap.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 实战
9.1 驱动→用户态:功能安全告警(genetlink 多播)
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 全收官
✨ 感谢你的阅读 ✨
技术之路漫漫,分享是为了更好地交流。
如果本文的内容对你有启发,希望能得到你的支持:
👍 点赞 | ⭐ 收藏 | 👀 关注
如果你在调试过程中遇到了其他问题,
欢迎在 评论区 💬 留言,我们一起探讨。
关注我,一起交流底层开发的那些事儿!🚀