设备驱动-常用例程

苷,又绕回内核开发了 😒 一些自己写驱动时 常用的模板代码汇总,不断更新中.

  • 资料来源:

    <>

  • 更新

    1
    2023.12.20 初始

初始化字符设备

字符设备是相对与 块设备 而言, 处理 字节/字符流的设备, 处理速度快; 向用户层提供了类似 文件的接口;

一个字符设备驱动要有

  • 驱动初始化 / 卸载函数
    • 默认情况下是: int init_module(void) / void cleanup_module(void)
    • 想自定义函数名,得加上宏定义声明: module_init(my_init); / module_exit(my_exit);
  • struct file_operations 结构体实例
    • 与结构体对应的至少实现 open/release 函数
  • 申请设备号 / 注销设备号: register_chrdev_region / unregister_chrdev_region
    • alloc_chrdev_region : 动态申请设备号,只关心设备运行不关心设备号到底是啥.
  • /sys/class 下 class_create / class_destroy
  • /dev 下 device_create / cdev_del
  • 字符设备: cdev_alloc / cdev_init(cdev, &fops);
  • 最后要有 MODULE_LICENSE("GPL");
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
#include <linux/module.h>  
#include <linux/device.h>

static int device_open(struct inode *inode, struct file *file) {
// do some
return 0;
}

static int device_release(struct inode *inode, struct file *file) {
// do some
return 0;
}

static struct file_operations fops = {
.open = device_open,
.release = device_release,
};
int init_module(void) {
// 省略了很多
if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) {
printk(KERN_ALERT "Failed to register device number\n");
return -1;
}
return 0;
}

void cleanup_module(void) {
unregister_chrdev_region(dev_num, 1);
}

MODULE_LICENSE("GPL");

字符设备私有数据

device_open 的参数 struct file *file 中有一个 private_data 字段可以用来存放每次 open/release 时候的私有数据.

  • 虽然每次 current 都是打开的设备的 task_struct;

存放 pid 的示例

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
struct my_device_data {
pid_t pid;
};

static int device_open(struct inode *inode, struct file *file) {
struct my_device_data *data;
printk(KERN_INFO "fun: %s with pid %d\n", __FUNCTION__,
current->pid);
// save pid
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->pid = current->pid;
file->private_data = data;
return 0;
}

static int device_release(struct inode *inode, struct file *file) {
// load pid
struct my_device_data *data = file->private_data;
printk(KERN_INFO "fun: %s with pid %d\n", __FUNCTION__,
data->pid);
kfree(data); // free data memory
return 0;
}

使用 Ioctl

字符设备驱动需要提供更多的拓展场景下, open/release 不够了,一般会通过 ioctl 拓展新的接口.

用户层 int ioctl (int __fd, unsigned long int __request, …)

1
2
3
4
5
6
7
8
#include <sys/ioctl.h>

file_desc = open(DEVICE, 0);
if (ioctl(file_desc, 0, &arg) < 0) {
printf("ioctl failed\n");
close(file_desc);
return -1;
}

驱动程序 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static long device_ioctl(struct file *file, unsigned int ioctl_num,
unsigned long ioctl_param) {
int ret = 0;

switch (ioctl_num) {
case 0: break;
case 1: break;
default: break;
}
return 0;
}
static struct file_operations fops = {
xx
.unlocked_ioctl = device_ioctl,
};

实际上 ioctl_num 的定义有一套标准,还挺复杂的..

  • 32bit 长度: bit31,30 区别读写; 29,15 ioctl_param 段长度; 20,8 魔数 (幻数),区别不同驱动设备; 7,0 区分序号,同一设备下不同命令.

ioctl.h 提供了快捷的宏定义, switch 直接使用

  • _IO(type,nr) _IOR(type,nr,size) _IOW(type,nr,size) _IOWR(type,nr,size) | read/write 均已包含;
  • type (魔数) 一般传入字符 (串) | nr 区分序号 数值
  • size 传入的是具体类型, 长度具体由 sizeof 决定.
  • #define IOCTL_SAMPLE _IOWR('sample', 1, int)

最好还是遵循系统的定义 而不是随便传入值… 有些值是不生效的..

使用 Proc

/proc 下是个虚拟文件系统, 提供进程和系统运行时的配置等等;

/proc/gid 下有一堆的进程相关信息,在反推堆栈信息时候非常有用;

  • cmdline: 启动当前进程的命令; bash 脚本就是 运行的命令;
  • maps: 关联到 内存映射

在 proc 下公开一些变量信息,提供修改接口 比系统调用要直接一些;

一个 proc 下的文件 可读 可写都对应到了 struct proc_ops 结构体,

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
static ssize_t read_proc(struct file *file, char __user *buf, size_t count,
loff_t *offset, int *var) {
char temp_buf[32];
size_t len = sprintf(temp_buf, "%d\n", *var);
return simple_read_from_buffer(buf, count, offset, temp_buf, len);
}

static ssize_t write_proc(struct file *file, const char __user *buf,
size_t count, loff_t *offset, int *var) {
char temp_buf[32];
if (count > sizeof(temp_buf) - 1)
return -EINVAL;
if (copy_from_user(temp_buf, buf, count))
return -EFAULT;

temp_buf[count] = '\0';
sscanf(temp_buf, "%d", var);
return count;
}

static ssize_t read_proc_1(struct file *file, char __user *buf,
size_t count, loff_t *offset) {
return read_proc(file, buf, count, offset, &def);
}
static ssize_t write_proc_1(struct file *file,
const char __user *buf, size_t count,
loff_t *offset) {
return write_proc(file, buf, count, offset, &def);
}
static const struct proc_ops proc_def_1= {
.proc_read = read_proc_1,
.proc_write = write_proc_1,
};

根据这个结构体创建 proc 下对应的文件

  • dir 是 NULL 则直接在 proc 下创建
1
2
3
4
5
//struct proc_dir_entry *create_proc_entry( const char *name,  mode_t mode,  
// struct proc_dir_entry *parent );
proc_create("def", 0666, dir, &proc_def_1);
// 也能先创建文件夹 然后传入 proc_create
// dir = proc_mkdir(proc_dir, NULL);

最后卸载

1
2
3
// 按照名字卸载
remove_proc_entry("def",NULL)
remove_proc_subtree("dir",NULL)

查找任意系统调用

详情见 获取任意 Linux Kernel 函数

添加 System Call

依然是参考

https://github.com/alibaba/diagnose-tools

diagnose-tools 是使用 tracepoint 探针, 获取到 sys_enter 这个入口,跳转到具体函数;

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
// SOURCE/module/pupil.c
int pupil_syscall(struct pt_regs *regs, long id){
switch (id) {
case DIAG_PUPIL_TASK_DUMP: break;
case DIAG_PUPIL_TASK_PID: break;
default: break;
}
}

static void diag_cb_sys_enter(void *data, struct pt_regs *regs, long id) {
xxx
ret = pupil_syscall(regs, id);
xxx
}
static void trace_sys_enter_hit(void *__data, struct pt_regs *regs, long id) {
diag_cb_sys_enter(NULL, regs, id);
}

static int sys_enter_hooked = 0;
void diag_hook_sys_enter(void) {
if (sys_enter_hooked)
return;
hook_tracepoint("sys_enter", trace_sys_enter_hit, NULL);
sys_enter_hooked = 1;
}
void diag_unhook_sys_enter(void) {
if (!sys_enter_hooked)
return;
unhook_tracepoint("sys_enter", trace_sys_enter_hit, NULL);
sys_enter_hooked = 0;
}

// SOURCE/module/pub/trace_point.c
int hook_tracepoint(const char *name, void *probe, void *data) {
struct tracepoint *tp;
tp = find_tracepoint(name);
if (!tp)
return 0;
return tracepoint_probe_register(tp, probe, data);
}

int unhook_tracepoint(const char *name, void *probe, void *data) {
struct tracepoint *tp;
int ret = 0;
tp = find_tracepoint(name);
if (!tp)
return 0;
do {
ret = tracepoint_probe_unregister(tp, probe, data);
} while (ret == -ENOMEM);
return ret;
}