Linux内核中不睡眠获取cmdline的方法和示例

Linux内核中不睡眠获取cmdline的方法

在Linux内核中,获取启动命令行参数(cmdline)通常可以通过以下几种不睡眠(非阻塞)的方法实现:

方法1:直接访问boot_command_line变量

内核在启动过程中会将cmdline存储在全局变量boot_command_line中,可以直接访问:

1
2
3
4
5
6
7
#include <linux/kernel.h>
#include <linux/init.h>

void print_cmdline(void)
{
printk(KERN_INFO "Boot command line: %s\n", boot_command_line);
}

方法2:通过saved_command_line

内核还提供了saved_command_line变量,它是cmdline的另一个副本:

1
2
3
4
5
6
7
#include <linux/kernel.h>
#include <linux/init.h>

void print_saved_cmdline(void)
{
printk(KERN_INFO "Saved command line: %s\n", saved_command_line);
}

方法3:通过/proc/cmdline接口

如果需要从内核模块中读取/proc/cmdline的内容而不睡眠,可以使用以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

static int read_cmdline(char *buffer, int maxlen)
{
int len = strlen(saved_command_line);

if (len >= maxlen)
len = maxlen - 1;

memcpy(buffer, saved_command_line, len);
buffer[len] = '\0';

return len;
}

方法4:解析特定参数

如果只需要获取cmdline中的特定参数,可以使用内核提供的解析函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <linux/kernel.h>
#include <linux/moduleparam.h>

static char *my_param;
module_param(my_param, charp, 0);

static int __init my_init(void)
{
if (my_param)
printk(KERN_INFO "Got my_param: %s\n", my_param);
else
printk(KERN_INFO "my_param not specified\n");
return 0;
}

注意事项

  1. 这些方法都是不睡眠的,可以在原子上下文中使用
  2. boot_command_linesaved_command_line在内核启动早期就已经初始化完成
  3. 如果需要修改cmdline,应该在内核启动早期进行(如setup_arch阶段)
  4. 这些变量和函数都声明在<linux/kernel.h><linux/init.h>

完整示例

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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init cmdline_demo_init(void)
{
printk(KERN_INFO "Boot command line: %s\n", boot_command_line);
printk(KERN_INFO "Saved command line: %s\n", saved_command_line);

// 检查特定参数
if (strstr(saved_command_line, "quiet"))
printk(KERN_INFO "Quiet mode enabled\n");

return 0;
}

static void __exit cmdline_demo_exit(void)
{
printk(KERN_INFO "cmdline demo module unloaded\n");
}

module_init(cmdline_demo_init);
module_exit(cmdline_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Demo for accessing cmdline without sleeping");

这些方法都适用于内核模块或内核代码中需要访问启动参数而不想引入潜在睡眠的场景。

Linux内核中不睡眠获取进程cmdline的方法和示例

Linux内核中不睡眠获取进程cmdline的方法

在Linux内核中,获取进程的命令行参数(cmdline)而不引起睡眠(即在原子上下文中安全使用)有几种方法。以下是常用的技术和示例:

方法1:使用get_task_cmdline()(内核5.3+)

Linux 5.3及以上版本提供了get_task_cmdline()函数:

1
2
3
4
5
6
#include <linux/sched/task.h>

int get_process_cmdline(struct task_struct *task, char *buffer, int buflen)
{
return get_task_cmdline(task, buffer, buflen);
}

方法2:直接访问mm->arg_startmm->arg_end

对于有内存映射的进程(mm不为NULL),可以直接读取参数区域:

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
#include <linux/sched.h>
#include <linux/mm.h>

int get_cmdline_atomic(struct task_struct *task, char *buffer, int buflen)
{
struct mm_struct *mm;
int res = 0;

mm = get_task_mm(task);
if (!mm)
return 0;

if (mm->arg_end) {
unsigned int len = mm->arg_end - mm->arg_start;

if (len > buflen)
len = buflen;

if (copy_from_user_nmi(buffer, (const char __user *)mm->arg_start, len)) {
res = -EFAULT;
} else {
buffer[len] = '\0';
res = len;
}
}

mmput(mm);
return res;
}

方法3:使用proc_pid_cmdline()(需配置CONFIG_PROC_PID_CMDLINE)

1
2
3
4
5
6
#include <linux/proc_fs.h>

int get_proc_cmdline(struct task_struct *task, char *buffer, int buflen)
{
return proc_pid_cmdline(task, buffer, buflen);
}

方法4:访问task->mm->arg_start(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int get_cmdline_simple(struct task_struct *task, char *buf, int buflen)
{
int len = 0;

if (!task->mm)
return 0;

len = task->mm->arg_end - task->mm->arg_start;
if (len > buflen)
len = buflen;

if (copy_from_user_nmi(buf, (const char __user *)task->mm->arg_start, len))
return -EFAULT;

buf[len] = '\0';
return len;
}

完整示例模块

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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/uaccess.h>

#define MAX_CMDLINE 4096

static void print_process_cmdline(pid_t pid)
{
struct task_struct *task;
char cmdline[MAX_CMDLINE] = {0};
int ret;

rcu_read_lock();
task = find_task_by_vpid(pid);
if (!task) {
rcu_read_unlock();
printk(KERN_INFO "Process %d not found\n", pid);
return;
}

get_task_struct(task);
rcu_read_unlock();

ret = get_cmdline_atomic(task, cmdline, MAX_CMDLINE-1);
if (ret > 0) {
printk(KERN_INFO "Process %d cmdline: %s\n", pid, cmdline);
} else {
printk(KERN_INFO "Failed to get cmdline for %d (error %d)\n", pid, ret);
}

put_task_struct(task);
}

static int __init cmdline_init(void)
{
printk(KERN_INFO "Process cmdline module loaded\n");
print_process_cmdline(1); // 获取init进程的cmdline
return 0;
}

static void __exit cmdline_exit(void)
{
printk(KERN_INFO "Process cmdline module unloaded\n");
}

module_init(cmdline_init);
module_exit(cmdline_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Atomic process cmdline reader");

注意事项

  1. 原子上下文安全:上述方法都设计为在原子上下文中使用,不会引起睡眠
  2. 内存保护:使用copy_from_user_nmi()而不是常规的copy_from_user()
  3. 任务引用计数:正确处理任务引用计数(get_task_struct/put_task_struct
  4. RCU保护:使用RCU读取锁保护任务查找过程
  5. 空指针检查:始终检查task->mm是否为NULL(内核线程没有内存映射)

性能考虑

对于频繁调用的场景,可以考虑:

  1. 缓存结果(如果允许)
  2. 限制最大cmdline长度
  3. 使用更高效的字符串处理函数

这些方法适用于内核模块、调试工具或性能分析工具中需要获取进程cmdline而不想引入潜在睡眠的场景。