苷,又绕回内核开发了 😒 一些自己写驱动时 常用的模板代码汇总,不断更新中.
<>
初始化字符设备
字符设备是相对与 块设备 而言, 处理 字节/字符流的设备, 处理速度快; 向用户层提供了类似 文件的接口;
一个字符设备驱动要有
- 驱动初始化 / 卸载函数
- 默认情况下是:
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) { return 0; }
static int device_release(struct inode *inode, struct file *file) { 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); 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) { struct my_device_data *data = file->private_data; printk(KERN_INFO "fun: %s with pid %d\n", __FUNCTION__, data->pid); kfree(data); 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 下对应的文件
1 2 3 4 5
|
proc_create("def", 0666, dir, &proc_def_1);
|
最后卸载
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
| 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; }
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; }
|