tee系统调用用法详解
tee系统调用用法
tee()
是 Linux 特有的系统调用,用于在两个管道之间高效复制数据(零拷贝),不消耗原始数据。以下是详细用法:
函数原型
1 |
|
参数说明
参数 | 说明 |
---|---|
fd_in |
源管道的读取端文件描述符(必须是管道) |
fd_out |
目标管道的写入端文件描述符(必须是管道) |
len |
要复制的最大字节数 |
flags |
控制行为的标志位(常用 SPLICE_F_MOVE 、SPLICE_F_NONBLOCK ) |
关键特性
- 零拷贝:数据在内核管道缓冲区移动,避免用户空间复制。
- 非消耗性:源管道
fd_in
的数据在复制后仍可被读取。 - 仅限管道:
fd_in
和fd_out
必须是管道(通过pipe()
创建)。
常用标志位
标志 | 说明 |
---|---|
SPLICE_F_MOVE |
尝试移动内存页(实际效果有限) |
SPLICE_F_NONBLOCK |
非阻塞操作(立即返回,不等待数据) |
SPLICE_F_MORE |
提示后续有更多数据(用于网络优化) |
SPLICE_F_GIFT |
未使用(保留) |
返回值
- 成功:复制的字节数(可能小于
len
)。 - 失败:返回
-1
,并设置errno
(常见错误见下表)。
常见错误码
错误码 | 说明 |
---|---|
EAGAIN |
非阻塞模式下无数据可用 |
EINVAL |
文件描述符不是管道/无效标志 |
ENOMEM |
内存不足 |
ESPIPE |
文件描述符不支持 splice 操作 |
使用示例
以下代码演示如何将标准输入复制到两个管道(类似 tee
命令):
1 |
|
典型应用场景
- 数据分路:将输入同时转发到多个处理流程。
1
2
3
4
5graph LR
A[输入源] -->|splice| B[管道1]
B -->|tee| C[管道2]
B --> 处理流程1
C --> 处理流程2 - 零拷贝代理:高效转发网络数据(需结合
splice
)。 - 日志记录:在数据处理过程中同步复制日志流。
注意事项
- 必须使用管道:非管道文件描述符会返回
EINVAL
。 - 数据未真正复制:内核仅传递缓冲区引用,需确保后续读取。
- 结合
splice()
使用:完整零拷贝链通常需组合splice
和tee
。 - 阻塞行为:默认阻塞直到有数据,使用
SPLICE_F_NONBLOCK
可非阻塞。
💡 提示:实际开发中应检查所有系统调用的返回值,并处理
EINTR
(信号中断)。
personality系统调用
personality()
是 Linux 特有的系统调用,用于设置或获取进程的执行域(execution domain),控制进程的二进制接口(ABI)行为、地址空间布局等特性。以下是详细说明:
函数原型
1 |
|
参数说明
参数 | 说明 |
---|---|
persona |
要设置的个性标志(或 0xffffffff 表示获取当前值) |
返回值
- 成功:返回之前设置的个性值
- 失败:返回
-1
并设置errno
常见个性标志(Personality Flags)
1. 执行域(ABI 控制)
标志 | 说明 |
---|---|
PER_LINUX |
标准 Linux ABI(默认) |
PER_LINUX32 |
32 位 Linux ABI |
PER_SVR4 |
System V Release 4 兼容模式 |
PER_BSD |
BSD 兼容模式 |
PER_MASK |
掩码(0x0fffffff )用于提取基础域 |
2. 行为控制标志
标志 | 说明 |
---|---|
ADDR_NO_RANDOMIZE |
禁用 ASLR(地址空间随机化) |
MMAP_PAGE_ZERO |
映射第 0 页(传统行为) |
ADDR_COMPAT_LAYOUT |
使用兼容的虚拟地址空间布局 |
READ_IMPLIES_EXEC |
读权限隐含执行权限(传统行为) |
STICKY_TIMEOUTS |
select() 调用不修改超时参数 |
核心功能
切换 ABI 环境
允许在 64 位系统中运行 32 位程序(通过PER_LINUX32
)。禁用 ASLR
通过ADDR_NO_RANDOMIZE
固定内存地址,便于调试:1
personality(personality(0xffffffff) | ADDR_NO_RANDOMIZE);
修改内存布局
控制mmap()
等系统调用的行为(如MMAP_PAGE_ZERO
)。模拟其他操作系统
支持 BSD、SVR4 等兼容模式。
使用示例
示例 1:获取当前个性值
1 | unsigned long current_persona = personality(0xffffffff); |
示例 2:临时禁用 ASLR
1 |
|
示例 3:切换到 32 位 ABI 环境
1 | // 在 64 位系统运行 32 位程序前设置 |
错误处理
错误码 | 说明 |
---|---|
EPERM |
无权限修改个性(非 root) |
EINVAL |
无效的 persona 值 |
典型应用场景
调试器
禁用 ASLR 以稳定复现内存地址问题。兼容层
实现 Linux 运行其他系统二进制文件(如 Wine、BSD 兼容层)。安全加固
强制启用 ASLR(清除ADDR_NO_RANDOMIZE
)。嵌入式系统
定制内存布局以适应资源限制。
注意事项
权限要求
修改个性通常需要CAP_SYS_ADMIN
能力(即 root 权限)。继承性
个性设置通过fork()
继承,但execve()
会重置为默认值(除非使用AT_FLAGS
特殊标记)。架构依赖
某些标志(如PER_LINUX32
)仅在 x86、ARM 等特定架构有效。内核版本
部分标志(如ADDR_COMPAT_LAYOUT
)需内核 ≥ 2.6.12。
⚠️ 警告:随意修改个性可能破坏程序稳定性!仅用于特定兼容/调试场景。
Linux根据dentry找到文件名称
在 Linux 内核中,通过 dentry
(目录项)结构获取文件名是一个常见的操作。以下是详细说明和实现方法:
核心数据结构
1 | struct dentry { |
获取文件名的三种方法
方法 1:直接访问 d_name
字段(最常用)
1 |
|
特点:
- 直接访问内核数据结构
- 不处理路径,只返回当前目录项的文件名
- 文件名不以空字符结尾,需结合长度使用
方法 2:使用 dentry_path_raw()
(获取完整路径)
1 |
|
特点:
- 返回从根目录开始的完整路径
- 需要手动分配/释放缓冲区
- 路径以空字符结尾
方法 3:递归构建路径(内核链表操作)
1 | static void reverse_path(struct dentry *dentry, struct list_head *list) { |
关键注意事项
非空终止字符串
dentry->d_name.name
不是以\0
结尾的 C 字符串,必须结合长度使用:1
2
3printk(KERN_INFO "Filename: %.*s\n",
dentry->d_name.len,
dentry->d_name.name);锁机制
在访问dentry
时通常需要持有d_lock
自旋锁:1
2
3spin_lock(&dentry->d_lock);
// 安全访问 dentry 字段
spin_unlock(&dentry->d_lock);引用计数
确保dentry
在使用期间不会被释放:1
2
3dget(dentry); // 增加引用计数
// 操作 dentry...
dput(dentry); // 减少引用计数负目录项(Negative Dentries)
即使文件不存在(负 dentry),d_name
仍然有效:1
2
3
4
5if (dentry->d_inode) {
// 文件存在
} else {
// 负 dentry(文件不存在)
}
实际应用示例
1 |
|
性能优化技巧
避免完整路径构建
除非必要,否则优先使用dentry->d_name
而非完整路径使用
d_path()
替代dentry_path_raw()
更安全的路径构建函数:1
char *path = d_path(&dentry->d_path, buffer, PATH_MAX);
RCU 安全访问
在 RCU 临界区中访问:1
2
3
4
5
6rcu_read_lock();
struct dentry *d = __d_lookup_rcu(parent, &name);
if (d) {
// 安全访问 d->d_name
}
rcu_read_unlock();
典型应用场景
文件系统监控
在 inotify/fanotify 中记录访问路径调试工具
在内核模块中打印文件访问信息安全模块
在 LSM(Linux Security Module)中检查文件路径文件系统实现
在自定义文件系统中处理路径逻辑
⚠️ 警告:直接操作
dentry
是内核级开发,不当使用可能导致系统崩溃。建议在开发环境中充分测试。