Linux 5.10内核如何获取父进程的启动参数信息
在Linux 5.10内核中,获取父进程的启动参数需要访问父进程的task_struct结构,并通过其内存描述符(mm_struct)提取命令行参数。以下是详细步骤和示例代码:
关键步骤
获取父进程的task_struct
通过当前进程的real_parent指针访问父进程的task_struct。
安全引用父进程
使用get_task_struct()增加父进程的引用计数,防止其意外释放。
获取父进程的内存描述符
使用get_task_mm()获取父进程的mm_struct(注意:内核线程无mm_struct)。
提取命令行参数
从mm_struct中读取arg_start和arg_end之间的数据,即命令行参数。
安全读取数据
使用access_process_vm()从父进程的用户空间安全复制数据。
释放资源
递减mm_struct和父进程的引用计数。
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <linux/sched.h> #include <linux/uaccess.h> #include <linux/slab.h> #include <linux/mm.h>
char *get_parent_cmdline(void) { struct task_struct *parent = current->real_parent; char *cmdline = NULL; unsigned long len; struct mm_struct *mm;
get_task_struct(parent);
mm = get_task_mm(parent); if (!mm) { put_task_struct(parent); return NULL; }
len = mm->arg_end - mm->arg_start; if (len <= 0 || len > PAGE_SIZE) { len = PAGE_SIZE; }
cmdline = kmalloc(len, GFP_KERNEL); if (!cmdline) { mmput(mm); put_task_struct(parent); return NULL; }
if (access_process_vm(parent, mm->arg_start, cmdline, len, 0) != len) { kfree(cmdline); cmdline = NULL; }
mmput(mm); put_task_struct(parent);
return cmdline; }
|
注意事项
内核线程处理
内核线程的mm为NULL,需检查get_task_mm()返回值。
用户空间地址
arg_start指向用户空间地址,必须使用access_process_vm()安全访问。
资源管理
- 使用
get_task_struct()/put_task_struct()管理进程引用。
- 使用
get_task_mm()/mmput()管理内存描述符引用。
- 调用者需用
kfree()释放返回的缓冲区。
参数长度限制
命令行参数通常不超过一页(PAGE_SIZE),但需防范溢出。
进程状态变化
父进程可能退出或修改命令行,高可靠性场景需额外同步。
替代方法:通过/proc文件系统
用户空间可直接读取/proc/<pid>/cmdline获取任意进程参数:
1
| cat /proc/$(ps -o ppid= -p $$)/cmdline
|
内核中实现类似功能需解析proc_pid_cmdline()等逻辑,较为复杂。
此方案适用于内核模块开发,需确保代码符合内核同步和安全规范。
Linux 5.10内核如何获取父进程的启动参数信息,要求不使用可能睡眠的函数
在Linux 5.10内核中,获取父进程的启动参数信息且不使用可能睡眠的函数,需要采用不同的策略。由于access_process_vm()可能睡眠(处理缺页异常),我们可以通过直接访问父进程的mm_struct中的参数指针来实现,但需注意这仅适用于父进程与当前进程共享内存映射的场景(如线程组)。以下是优化方案:
原子上下文安全方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <linux/sched.h> #include <linux/string.h>
int get_parent_cmdline(char *buf, size_t buf_size) { struct task_struct *parent = current->real_parent; struct mm_struct *mm; unsigned long len; int ret = 0; get_task_struct(parent); mm = get_task_mm(parent); if (!mm) { ret = -ESRCH; goto out_put_task; } len = mm->arg_end - mm->arg_start; if (len > buf_size - 1) len = buf_size - 1; if (len > 0) { if (mm == current->mm) { memcpy(buf, (char *)mm->arg_start, len); } else { ret = -EACCES; goto out_put_mm; } buf[len] = '\0'; } else { ret = -ENODATA; } out_put_mm: mmput(mm); out_put_task: put_task_struct(parent); return (ret < 0) ? ret : len; }
|
关键说明
免睡眠设计:
- 使用
get_task_struct()和get_task_mm()增加引用计数(原子操作)
- 通过
memcpy()直接复制内存(非睡眠)
- 避免使用
kmalloc/access_process_vm等可能阻塞的函数
使用限制:
1
| if (mm == current->mm) { ... }
|
- 仅当父进程与当前进程共享内存映射时有效(常见于线程组内)
- 适用于父进程是线程组长(
group_leader)的场景
安全边界检查:
- 严格验证
arg_start/arg_end范围
- 限制拷贝长度防止缓冲区溢出
- 添加NULL终止符保证字符串安全
错误处理:
-ESRCH:父进程是内核线程(无mm)
-EACCES:内存映射不匹配(无法直接访问)
-ENODATA:命令行参数长度为0
替代方案(适用于任意上下文)
通过procfs接口获取(无睡眠风险):
1 2 3 4 5 6 7 8
| char buf[256]; struct path p; char *pathname = "/proc/xxxx/cmdline";
kern_path(pathname, LOOKUP_FOLLOW, &p); struct file *f = dentry_open(&p, O_RDONLY, current_cred()); kernel_read(f, buf, sizeof(buf), 0); fput(f);
|
各方案适用场景对比
| 方案 |
原子安全 |
通用性 |
复杂度 |
适用场景 |
| 直接内存访问 |
✓ |
低 |
低 |
线程组内共享内存的进程 |
| procfs接口读取 |
✓ |
高 |
中 |
任意进程(需构造proc路径) |
| access_process_vm |
✗ |
高 |
高 |
非原子上下文 |
注意事项
内存映射一致性:
- 直接访问方案依赖
mm == current->mm条件
- 在进程克隆(
clone(CLONE_VM))或线程场景下有效
- 常规父子进程关系通常不共享内存映射
性能考量:
- 直接内存访问速度最快(无上下文切换)
- procfs方案需文件系统操作,但有内核缓存优化
安全建议:
- 生产环境优先使用procfs方案
- 调试用途可使用直接访问方案
- 确保缓冲区大小足够(建议至少
PAGE_SIZE)
重要提示:在标准父子进程关系中(非线程组),推荐通过/proc/$PPID/cmdline获取参数,这是最安全可靠且符合内核设计的方法。
上述方案替换掉可能睡眠的mmput函数
在Linux 5.10内核的原子上下文中,确实需要避免使用可能睡眠的函数如mmput()。以下是完全无睡眠的解决方案,使用引用计数原子操作:
原子安全实现方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <linux/sched.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/rcupdate.h>
int get_parent_cmdline_atomic(char *buf, size_t buf_size) { struct task_struct *parent; struct mm_struct *mm; unsigned long len; int ret = -ESRCH; rcu_read_lock(); parent = rcu_dereference(current->real_parent); if (!parent || !get_task_struct_rcu_user(parent)) { rcu_read_unlock(); return -ESRCH; } rcu_read_unlock(); mm = get_task_mm(parent); if (!mm) { ret = -ENOENT; goto out_put_task; } len = mm->arg_end - mm->arg_start; if (len == 0) { ret = -ENODATA; goto out_put_mm; } if (len > buf_size - 1) len = buf_size - 1; if (mm == current->mm) { memcpy(buf, (char *)mm->arg_start, len); buf[len] = '\0'; ret = len; } else { ret = -EACCES; }
out_put_mm: mmput(mm); out_put_task: put_task_struct(parent); return ret; }
|
关键优化点
完全无睡眠的引用计数管理:
1 2 3 4 5 6 7
| parent = rcu_dereference(current->real_parent); get_task_struct_rcu_user(parent);
mmput(mm); put_task_struct(parent);
|
安全内存访问保证:
- 通过
mm == current->mm检查确保共享地址空间
- 直接使用
memcpy()避免access_process_vm()的睡眠风险
- 严格长度检查防止越界访问
错误处理代码路径:
-ESRCH: 父进程不可访问
-ENOENT: 父进程是内核线程
-ENODATA: 无命令行参数
-EACCES: 内存空间不共享
>0: 成功复制的字节数
替代方案:完全无引用计数操作
对于需要极致原子性的场景(如中断处理程序):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <linux/sched.h>
int get_parent_cmdline_pure_atomic(char *buf, size_t buf_size) { struct task_struct *parent; struct mm_struct *mm; unsigned long len; rcu_read_lock(); parent = rcu_dereference(current->real_parent); if (!parent || thread_group_leader(parent)) { rcu_read_unlock(); return -ESRCH; } mm = rcu_dereference(parent->mm); if (!mm) { rcu_read_unlock(); return -ENOENT; } len = mm->arg_end - mm->arg_start; if (len == 0 || len > buf_size - 1) { rcu_read_unlock(); return len ? -ENOSPC : -ENODATA; } if (mm == current->mm) { memcpy(buf, (char *)mm->arg_start, len); buf[len] = '\0'; } rcu_read_unlock(); return (mm == current->mm) ? len : -EACCES; }
|
方案对比
| 特性 |
RCU+引用计数方案 |
纯RCU方案 |
| 进程生命周期保证 |
高(引用计数保持) |
中(RCU保护期间安全) |
| 内存访问安全性 |
高 |
中(需快速完成) |
| 适用上下文 |
进程上下文/原子上下文 |
严格原子上下文 |
| 内存消耗 |
有临时引用计数开销 |
零开销 |
| 代码复杂度 |
中 |
低 |
| 睡眠风险 |
无 |
无 |
使用限制说明
共享内存空间要求:
- 仅当
mm == current->mm时有效(同线程组内)
- 典型父子进程通常不满足此条件
- 适用于
pthread_create()创建的线程
原子上下文约束:
1 2 3 4
| rcu_read_lock(); memcpy(); pointer_dereference();
|
实际应用建议:
- 线程间通信:优先使用此方案
- 标准父子进程:通过
proc_pid_cmdline()实现
- 性能敏感场景:预先在进程创建时缓存参数
关键提示:在Linux 5.10中,task_struct->mm和mm_struct字段在RCU读取侧临界区内是稳定的,但操作完成后必须立即释放RCU锁。生产环境建议添加CONFIG_DEBUG_ATOMIC_SLEEP验证。