浅析内核与用户层通信的四种方法
方法列表:
1.系统调用
2.虚拟文件系统
proc文件系统
sysfs文件系统
debugfs文件系统
3.ioctl接口
4.netlink
调试方法:
https://blog.csdn.net/gatieme/article/details/68948080
一:系统调用
1.简介
- 优秀博客:
https://blog.csdn.net/gatieme/article/details/50779184
https://blog.csdn.net/liduxun/article/details/48119849
https://www.cnblogs.com/zl1991/p/6543634.html
- 内核提供了用户进程与内核进行交互的一组接口。
- 让应用程序受限地访问硬件设备,提供了创建新进程并与已有进程进行通信的机制,也提供了申请操作系统其它资源的能力。
- 作用:
1.为用户空间提供了一种硬件的抽象接口。
2.系统调用保证了系统的稳定性和安全。
3.系统调用是用户空间访问内核的唯一手段:除异常和陷入外,它们是内核唯一的合法入口。实际上,其它的像设备文件和/proc之类的方式,最终也还是要通过系统调用进行访问的。
- API、POSIX和C库
1.一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程。(好处:更大的兼容性,而不管系统调用的实现。)
2.在unix世界中,最流行的的应用编程接口是基于POSIX标准的。POSIX是由IEEE的标准组成,其目的是提供一套大体上基于Unix的可移植操作系统标准。POSIX是说明API和系统调用之间关系的一个极好例子。
3.C库实现了Unix系统的主要API,包括标准C库函数和系统调用接口。C库提供了POSIX的绝大部分API。
4.Unix的系统调用抽象出了用于完成某种确定的目的的函数。“提供机制而不是策略”
- 要访问系统调用(在Linux中常称作syscall),通常通过C库中定义的函数调用来进行。
- 系统调用最终具有一种明确的操作。如:gitpid()系统调用,根据定义它会返回当前进程的PID.
在内核中的实现:
SYSCALL_DEFINE0(getpid) { return task_tgid_vnr(current); //return current->tgid }
- SYSCALL_DEFINE0:只是一个宏,它定义一个无参数的系统调用(因此这里为数字0),展开后代码:
asmlinkage long sys_getpid(void)
- 如何定义系统调用:
1.注意函数声明中 asmlinkage限定词,这是一个编译指令,通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。
2.函数返回long。为保证32位和64位系统的兼容,系统调用在用户空间和内核空间有不同的返回类型,在用户空间为int,在内核空间为long。
3.注意系统调用get_pid()在内核中被定义乘sys_getpid()。这是linux中所有系统调用都应该遵守的命名规则,系统调用bar()在内核中也实现为sys_bar()函数。
- 系统调用号:在linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就用来指明到底是要执行哪个系统调用;进程不会提及系统调用的名称;系统调用号相当重要,一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃;内核记录了系统调用表中的所有已注册的系统调用的列表,存储在sys_call_table中。 如:x86-64中,定义在arch/i386/kernel/syscall_64.c
- 系统调用处理程序:因为用户空间的程序无法直接执行内核代码。所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序在内核空间执行系统调用。
- 通知内核的机制是靠软中断实现的:通过引发一个异常来促使系统切换到内核态去执行异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。如:x86系统上预定义的软中断是中断号128,通过int $0x80指令触发该中断。最近x86处理器增加了sysenter的指令。
- x86上,系统调用号是通过eax寄存器传递给内核的:在陷入内核之前,用户空间就把相应系统调用所对应的号放入eax中。这样系统调用处理程序一旦运行就可以从eax中得到数据。
- 参数传递:像传递系统调用号一样,这些参数也是通过寄存器传递。给用户空间的返回值也是通过寄存器传递。
- 内核在执行系统调用的时候处于进程上下文。current指针指向当前任务,即引发系统调用的那个进程。在进程上下文中,内核可以休眠,并且可以被抢占。
当系统调用返回时,控制权仍然在system_call()中,它最终会负责切换到用户空间,并让用户进程继续执行下去。
2.应用接口
- x86架构:
1.编写一个系统调用;
2.在系统调用表的最后加入一个表项;
3 对于所支持的各种体系架构,系统调用号都必须定义域 asm/unistd.h中
4.系统调用必须被编译进内核映象(不能被编译成模块)。这只要把它放进kernel/下的一个相关文件中就可以了,比如sys.c,它包含了各种各样的系统调用。
- arm架构
1.编写一个系统调用
2.系统调用表内增加条目。 /arch/arm/kernel/calls.S内添加
3.应用实例
/*1.编写foo()系统调用。我们把它放入kernel/sys.c文件中。*/ #include <asm/page.h> /* *sys_foo:每个人喜欢的系统调用 *返回每个进程的内核栈大小 */ asmlinkage long sys_foo(void) { return THREAD_SIZE; } /*2.加入系统调用表 entry.s*/ ENTRY(sys_call_table) .long sys_restart_syscall /* 0 */ .long sys_exit .long sys_fork .long sys_read .long sys_write .long sys_open /* 5 */ ... .long sys_eventfd2 .long sys_epoll_create1 /* 330 */ .long sys_dup3 .long sys_pipe2 .long sys_inotify_init1 .long sys_preadv .long sys_pwritev /* 335 */ .long sys_rt_tgsigqueueinfo .long sys_perf_event_open .long sys_recvmmsg .long sys_setns .long sys_foo /338 myself syscall/
/* 3.定义系统调用号 asm/unistd.h */
#define __NR_foo 338
arm架构: 内核层
/*1.添加系统调用函数 在/kernel/sys.c内添加*/ asmlinkage long sys_foo(void) { return THREAD_SIZE; } /*2.更新unistd.h 目录:arch/arm/include/asm/unistd.h (不是这个) arch/arm/include/uapi/asm/unistd.h 注:只能添加在所有系统调用号的最后面*/ #define __NR_foo (__NR_SYSCALL_BASE+387) /*3.系统调用表内增加条目 在/arch/arm/kernel/calls.S内 注:只能添加在所有CALL的最后面,并且与(2)的调用号相对应。否则一定使系统调用表混乱。*/ CALL(sys_foo)
用户层测试:
#include <unistd.h> #include <sys/syscall.h> #include<stdio.h> int main() { int mem_size = 0; mem_size = syscall(__NR_foo); printf("the process mem size:%d bytes", mem_size); return 0; }
二:虚拟文件系统
2.1 proc文件系统
1.简介
- 优秀博客:
https://www.ibm.com/developerworks/cn/linux/l-proc.html
https://blog.csdn.net/sty23122555/article/details/51638697
seq操作:
http://www.cnblogs.com/Wandererzj/archive/2012/04/16/2452209.html#commentform
- 在linux系统中,proc文件系统被内核用于向用户导出信息。“/proc”文件系统是一个虚拟文件系统,通过它可以在Linux内核空间和用户空间之间进行通信。在/proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,与普通文件不同的是,这些虚拟文件的内容都是动态创建的。
- “/proc”下的绝大多数文件都是只读的,以显示内核信息为主。但是“/proc”下的文件也并不是完全只读的,若节点可写,还可用于一定的控制或配置目的。例如:/proc/sys/kernel/printk可以改变printk()的打印级别。
2.应用接口
Linux 3.9以及之前的内核版本:
/*1.创建proc文件*/ //创建目录 struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent); /*作用:用于创建"/proc"节点 参数: name:为"/proc"节点的名称 parent/base:为父目录的节点,如果为NULL,则指"/proc"目录。 返回值: create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在 create 时发生了错误)。 然后就可以使用这个返回的指针来配置这个虚拟文件的其他参数,例如在对该文件执行读操作时应该调用的函数。 注:当read()系统调用在"/proc"文件系统中执行时,它映象到一个数据产生函数,而不是一个数据获取函数。 */ struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode, struct proc_dir_entry *parent ); struct proc_dir_entry { ...... const struct file_operations *proc_fops; <==文件操作结构体 struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; <==读回调 write_proc_t *write_proc; <==写回调 ...... }; /*2. 删除proc文件/目录*/ /*要从 /proc 中删除一个文件,可以使用 remove_proc_entry 函数。要使用这个函数,我们需要提供文件名字符串, 以及这个文件在 /proc 文件系统中的位置(parent)。*/ void remove_dir_entry(const char *name, struct proc_dir_entry *parent); /*3. /proc节点的读写函数*/ /* proc文件实际上是一个叫做proc_dir_entry的struct(定义在proc_fs.h),该struct中有int read_proc和int write_proc 两个元素,要实现proc的文件的读写就要给这两个元素赋值。但这里不是简单地将一个整数赋值过去就行了,需要实现两个回调函数。 在用户或应用程序访问该proc文件时,就会调用这个函数,实现这个函数时只需将想要让用户看到的内容放入page即可。 在用户或应用程序试图写入该proc文件时,就会调用这个函数,实现这个函数时需要接收用户写入的数据(buff参数)。 */ static int (*proc_read)(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_write_foobar(struct file *file, const char *buffer, unsigned long count, void *data);
Linux 3.10及以后的内核版本:
“/proc”的内核API和实现架构变更较大,create_proc_entry()、create_proc_read_entry()之类的API都被删除了,取而代之的是直接使用proc_create()、proc_create_data() API。同时,也不再存在read_proc()、write_proc()之类的针对proc_dir_entry的成员函数了,而是直接把file_operations结构体的指针传入proc_create()或者proc_create_data()函数中。
static inline struct proc_dir_entry *proc_create( const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops) { return proc_create_data(name, mode, parent, proc_fops, NULL); } struct proc_dir_entry *proc_create_data(const char *name, umode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops, void *data)
3.实例
Linux 3.9以及之前的内核版本:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> static unsigned int variable; static struct proc_dir_entry *test_dir, *test_entry; static int test_proc_read(char *buf, char **start, off_t off, int count,int *eof, void *data) { unsigned int *ptr_var = data; return sprintf(buf, "%u\n", *ptr_var); } static int test_proc_write(struct file *file, const char *buffer,unsigned long count, void *data) { unsigned int *ptr_var = data; *ptr_var = simple_strtoul(buffer, NULL, 10); return count; } static __init int test_proc_init(void) { test_dir = proc_mkdir("test_dir", NULL); if (test_dir) { test_entry = create_proc_entry("test_rw", 0666, test_dir); if (test_entry) { test_entry->nlink = 1; test_entry->data = &variable; test_entry->read_proc = test_proc_read; test_entry->write_proc = test_proc_write; return 0; } } return -ENOMEM; } module_init(test_proc_init); static __exit void test_proc_cleanup(void) { remove_proc_entry("test_rw", test_dir); remove_proc_entry("test_dir", NULL); } module_exit(test_proc_cleanup); MODULE_AUTHOR("Barry Song <baohua@kernel.org>"); MODULE_DESCRIPTION("proc example"); MODULE_LICENSE("GPL v2");
Linux 3.10及以后的内核版本:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/version.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> static unsigned int variable; static struct proc_dir_entry *test_dir, *test_entry; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) //other code #else static int test_proc_show(struct seq_file *seq, void *v) { unsigned int *ptr_var = seq->private; seq_printf(seq, "%u\n", *ptr_var); return 0; } static ssize_t test_proc_write(struct file *file, const char __user *buffer,size_t count, loff_t *ppos) { struct seq_file *seq = file->private_data; unsigned int *ptr_var = seq->private; *ptr_var = simple_strtoul(buffer, NULL, 10); return count; } static int test_proc_open(struct inode *inode, struct file *file) { return single_open(file, test_proc_show, PDE_DATA(inode)); } static const struct file_operations test_proc_fops = { .owner = THIS_MODULE, .open = test_proc_open, .read = seq_read, .write = test_proc_write, .llseek = seq_lseek, .release = single_release, }; #endif static __init int test_proc_init(void) { test_dir = proc_mkdir("test_dir", NULL); if (test_dir) { #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) //other code #else test_entry = proc_create_data("test_rw", 0666, test_dir, &test_proc_fops, &variable); if (test_entry) { return 0; } #endif } return -ENOMEM; } module_init(test_proc_init); static __exit void test_proc_cleanup(void) { remove_proc_entry("test_rw", test_dir); remove_proc_entry("test_dir", NULL); } module_exit(test_proc_cleanup); MODULE_AUTHOR("Barry Song <baohua@kernel.org>"); MODULE_DESCRIPTION("proc example"); MODULE_LICENSE("GPL v2");
Makefile:
ifneq ($(KERNELRELEASE),) obj-m := proc_test.o else PWD := $(shell pwd) KDIR := /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.order Module.symvers endif
2.2:sysfs文件系统
1.简介
资料:
http://www.wowotech.net/linux_kenrel/dm_sysfs.html
- sysfs被看成是与proc、devfs和devpty同类别的文件系统,该文件系统是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级试图,与提供进程和状态信息的proc文件系统十分类似。
- sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以有用户空间存取,向用户空间导出内核数据结构以及他们的属性。sysfs的一个目的就是展示设备驱动模型中各组件的层次关系,其顶级目录包括block、bus、dev、devices、class、fs、kernel、prwer和firmware等。
block:包含所有的块设备。
devices:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构。
bus:包含系统中所有的总线类型。
class:包含系统中的设备类型(如网卡设备、声卡设备、输入设备等)。
- 总线、驱动和设备最终都会落实为sysfs中的一个目录,进一步追踪代码会发现,它们实际上都可以认为是kobject的派生类,kobject可看做是所有总线、设备和驱动的抽象基类,1个kobject对应sysfs中的一个目录。
2.应用接口
- 总线、设备和驱动中的各个attribute则直接落实为sysfs中的一个文件,attribute会伴随着show()和store()这两个函数,分别用于读写该attribute对应的sysfs文件。
- sysfs中的目录来源于bus_type、device_driver、device,而目录中的文件则来源于attribute。
/* _name:名称,也就是将在sys fs中生成的文件名称。 _mode:上述文件的访问权限,与普通文件相同,UGO的格式。 _show:显示函数,cat该文件时,此函数被调用。 _store:写函数,echo内容到该文件时,此函数被调用。 */ /*1.driver: sysfs interface for exporting driver attributes */ struct driver_attribute { struct attribute attr; ssize_t (*show)(struct device_driver *driver, char *buf); ssize_t (*store)(struct device_driver *driver, const char *buf, size_t count); }; #define DRIVER_ATTR(_name, _mode, _show, _store) \ struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store) #define DRIVER_ATTR_RW(_name) \ struct driver_attribute driver_attr_##_name = __ATTR_RW(_name) #define DRIVER_ATTR_RO(_name) \ struct driver_attribute driver_attr_##_name = __ATTR_RO(_name) #define DRIVER_ATTR_WO(_name) \ struct driver_attribute driver_attr_##_name = __ATTR_WO(_name) /*2.devices: interface for exporting device attributes */ struct device_attribute { struct attribute attr; ssize_t (*show)(struct device *dev, struct device_attribute *attr, char *buf); ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); }; ssize_t device_show_ulong(struct device *dev, struct device_attribute *attr, char *buf); ssize_t device_store_ulong(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); ssize_t device_show_int(struct device *dev, struct device_attribute *attr, char *buf); ssize_t device_store_int(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); ssize_t device_show_bool(struct device *dev, struct device_attribute *attr, char *buf); ssize_t device_store_bool(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); #define DEVICE_ATTR(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) #define DEVICE_ATTR_RW(_name) \ struct device_attribute dev_attr_##_name = __ATTR_RW(_name) #define DEVICE_ATTR_RO(_name) \ struct device_attribute dev_attr_##_name = __ATTR_RO(_name) #define DEVICE_ATTR_WO(_name) \ struct device_attribute dev_attr_##_name = __ATTR_WO(_name) #define DEVICE_ULONG_ATTR(_name, _mode, _var) \ struct dev_ext_attribute dev_attr_##_name = \ { __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) } #define DEVICE_INT_ATTR(_name, _mode, _var) \ struct dev_ext_attribute dev_attr_##_name = \ { __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) } #define DEVICE_BOOL_ATTR(_name, _mode, _var) \ struct dev_ext_attribute dev_attr_##_name = \ { __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) } #define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \ struct device_attribute dev_attr_##_name = \ __ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) /*3.bus*/ struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *bus, char *buf); ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count); }; #define BUS_ATTR(_name, _mode, _show, _store) \ struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store) #define BUS_ATTR_RW(_name) \ struct bus_attribute bus_attr_##_name = __ATTR_RW(_name) #define BUS_ATTR_RO(_name) \ struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)
3.实例
/*1.定义接口函数*/ static ssize_t bma253_state_show(struct device *dev, struct device_attribute *attr, char *buf) { struct bma253_data *pdata = &bma253_dev; int enable = 0; enable = atomic_read(&pdata->work_mode); return sprintf(buf, "%d\n", enable); } static ssize_t bma253_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct bma253_data *pdata = &bma253_dev; int ret; unsigned long enable; if(kstrtoul(buf, 10, &enable) < 0) return -EINVAL; ret = bma253_change_mode(pdata, enable); if (!ret) { atomic_set(&pdata->work_mode, enable); printk(KERN_INFO "power status =0x%x\n", (int)enable); } return count; } /*2.快捷创建*/ static DEVICE_ATTR(enable, 0666, bma253_state_show, bma253_state_store); /*3.*/ static struct attribute *bma253_attributes[] = { &dev_attr_enable.attr, NULL }; /*4.*/ static const struct attribute_group bma253_attr_group = { .attrs = bma253_attributes, }; /*5.在probe中调用,创建sys调试文件*/ result = sysfs_create_group(&bma253_device.this_device->kobj, &bma253_attr_group); if (result) { printk(KERN_ERR "create device file failed!\n"); result = -EINVAL; goto err_create_sysfs; }
完整实例:
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/ioport.h> #include <linux/errno.h> #include <linux/workqueue.h> #include <linux/platform_device.h> #include <linux/types.h> #include <linux/io.h> typedef struct { int num; } hello_priv; /*1.定义接口函数*/ static ssize_t hello_power_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) { hello_priv* prv = dev_get_drvdata(dev); unsigned long power; if(kstrtoul(buf, 10, &power) < 0) return -EINVAL; prv->num = (int)power; return count; } static ssize_t hello_power_show(struct device *dev, struct device_attribute *attr, char *buf) { hello_priv* prv = dev_get_drvdata(dev); int power = 0; power = prv->num; return sprintf(buf, "%d\n", power); } /*2. 设置DEVICE_ATTR: 名称power_on必须与attribute中的名称对应*/ static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, hello_power_show, hello_power_store); //show接口为空,只有store接口,即只支持写不技持读 /*3. 设置DEVICE_ATTR*/ static struct attribute *hello_attributes[] = { &dev_attr_power_state.attr, //dev_attr_****.attr中间的××××必须与DEVICE_ATTR中的名称对应 NULL }; /*4. 封装到attribute_group中*/ static const struct attribute_group hello_attr_group = { .attrs = hello_attributes, }; static int __init hello_probe(struct platform_device *pdev) { int rc; hello_priv* prv; prv = devm_kzalloc(&pdev->dev, sizeof(hello_priv), GFP_KERNEL); if (!prv) { dev_err(&pdev->dev, "failed to allocate hello\n"); return -ENOMEM; } prv->num = 2; platform_set_drvdata(pdev, prv); /*5.调用sysfs_create_group创建/sys下的文件*/ rc = sysfs_create_group(&pdev->dev.kobj, &hello_attr_group); if (rc) { dev_err(&pdev->dev,"failed to create hello sysfs group\n"); goto fail; } return 0; fail: return rc; } struct platform_driver hello_driver = { .driver = { .name = "hello", .owner = THIS_MODULE, }, .probe = hello_probe,
}; static struct platform_device hello_device = { .name = "hello", .id = -1, }; static int __init hello_init(void) { int ret; ret = platform_device_register(&hello_device); if (ret != 0) printk(KERN_NOTICE "cong: %s:%s[%d]: error", __FILE__,__FUNCTION__, __LINE__); ret = platform_driver_register(&hello_driver); return ret; } static void __init hello_exit(void) { platform_driver_unregister(&hello_driver); } module_init(hello_init); module_exit(hello_exit); MODULE_DESCRIPTION("Hellow test Driver"); MODULE_AUTHOR("vec"); MODULE_LICENSE("GPL");
2.3 debugfs文件系系统
1.简介
- 资料:
https://blog.csdn.net/luckywang1103/article/details/26809843
2.应用接口
3.实例
三:ioctl接口
1.简介
优秀博客:
http://blog.chinaunix.net/uid-25014876-id-59419.html
https://blog.csdn.net/zifehng/article/details/59576539
- 一个字符设备驱动通常会实现常规的打开、关闭、读、写等功能,但在一些细分的情境下,如果需要扩展新的功能,通常以增设ioctl()命令的方式实现,其作用类似于“拾遗补漏”。
2.应用接口
用户空间:
#include <sys/ioctl.h> /* 参数: fd:文件描述符。 cmd:交互协议,设备驱动将根据cmd执行对应操作。 ...:可变参数arg,依赖cmd指定长度以及类型。 返回值: ioctl()执行成功时返回0,失败则返回-1并设置全局变量errorno值 注: 在实际应用中,ioctl出错时的errorno大部分是ENOTTY(error not a typewriter),顾名思义, 即第一个参数fd指向的不是一个字符设备,不支持ioctl操作,这时候应该检查前面的open函数是否 出错或者设备路径是否正确。 */ int ioctl(int fd, int cmd, ...) ;
内核空间:
- 在新版内核中,unlocked_ioctl()与compat_ioctl()取代了ioctl()。unlocked_ioctl(),顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl(),compat全称compatible(兼容的),主要目的是为64位系统提供32位ioctl的兼容方法,也是在无大内核锁的情况下调用。
注:
在字符设备驱动开发中,一般情况下只要实现unlocked_ioctl()即可,因为在vfs层的代码是直接调用unlocked_ioctl()。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
ioctl命令,用户与驱动之间的协议
type设备类型字段:为一个“幻数”,可以是0~0xff的值,内核中的ioctl-number.txt给出了一些推荐的和已经被使用的“幻数”,新设备驱动定义“幻数”的时候要避免与其冲突。
nr序列号字段:8位
dir方向字段:表示数据传送方向,可能的值是 _IOC_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)、_IOC_READ|_IOC_WRITE(双向)。
size数据尺寸字段:表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13或者14位。
宏-辅助生成命令
作用:根据传入的type(设备类型字段)、nr(序列号字段)、size(数据长度字段)和宏名隐含的方向字段移位组合生成命令码。
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size)) #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size)) #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)) /*_IO、_IOR等使用的_IOC宏*/ #define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT)) eg: #define GLOBALMEM_MAGIC 'g' #define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)
3.实例
公用头文件
ioctl-test.h,用户空间和内核空间共用的头文件,包含ioctl命令及相关宏定义,可以理解为一份“协议”文件。
// ioctl-test.h #ifndef __IOCTL_TEST_H__ #define __IOCTL_TEST_H__ #include <linux/ioctl.h> // 内核空间 // #include <sys/ioctl.h> // 用户空间 /* 定义设备类型 */ #define IOC_MAGIC 'c' /* 初始化设备 */ #define IOCINIT _IO(IOC_MAGIC, 0) /* 读寄存器 */ #define IOCGREG _IOW(IOC_MAGIC, 1, int) /* 写寄存器 */ #define IOCWREG _IOR(IOC_MAGIC, 2, int) #define IOC_MAXNR 3 struct msg { int addr; unsigned int data; }; #endif
内核空间:
// ioctl-test-driver.c ...... static const struct file_operations fops = { .owner = THIS_MODULE, .open = test_open, .release = test_close, .read = test_read, .write = etst_write, .unlocked_ioctl = test_ioctl, }; ...... static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { //printk("[%s]\n", __func__); int ret; struct msg my_msg; /* 检查设备类型 */ if (_IOC_TYPE(cmd) != IOC_MAGIC) { pr_err("[%s] command type [%c] error!\n", __func__, _IOC_TYPE(cmd)); return -ENOTTY; } /* 检查序数 */ if (_IOC_NR(cmd) > IOC_MAXNR) { pr_err("[%s] command numer [%d] exceeded!\n", __func__, _IOC_NR(cmd)); return -ENOTTY; } /* 检查访问模式 */ if (_IOC_DIR(cmd) & _IOC_READ) ret= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) ret= !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (ret) return -EFAULT; switch(cmd) { /* 初始化设备 */ case IOCINIT: init(); break; /* 读寄存器 */ case IOCGREG: ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg)); if (ret) return -EFAULT; msg->data = read_reg(msg->addr); ret = copy_to_user((struct msg __user *)arg, &msg, sizeof(my_msg)); if (ret) return -EFAULT; break; /* 写寄存器 */ case IOCWREG: ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg)); if (ret) return -EFAULT; write_reg(msg->addr, msg->data); break; default: return -ENOTTY; } return 0; }
用户空间:
// ioctl-test.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include "ioctl-test.h" int main(int argc, char **argv) { int fd; int ret; struct msg my_msg; fd = open("/dev/ioctl-test", O_RDWR); if (fd < 0) { perror("open"); exit(-2); } /* 初始化设备 */ ret = ioctl(fd, IOCINIT); if (ret) { perror("ioctl init:"); exit(-3); } /* 往寄存器0x01写入数据0xef */ memset(&my_msg, 0, sizeof(my_msg)); my_msg.addr = 0x01; my_msg.data = 0xef; ret = ioctl(fd, IOCWREG, &my_msg); if (ret) { perror("ioctl read:"); exit(-4); } /* 读寄存器0x01 */ memset(&my_msg, 0, sizeof(my_msg)); my_msg.addr = 0x01; ret = ioctl(fd, IOCGREG, &my_msg); if (ret) { perror("ioctl write"); exit(-5); } printf("read: %#x\n", my_msg.data); return 0; }
四. netlink
1.简介
借鉴博客:
https://blog.csdn.net/stone8761/article/details/72780863
- netlink使用32位端口寻址,称为pid(与进程号没有关系),其中内核的pid地址为0,。netlink主要特性如下:
- 支持全双工、异步通信(当然同步也支持)
- 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
- 在内核空间使用专用的内核API接口
- 支持多播(因此支持“总线”式通信,可实现消息订阅)
- 在内核端可用于进程上下文与中断上下文
2.应用接口
内核层操作
/*1.创建socket 参数: net: 一般直接填&init_net unit:协议类型,可自定义,如#define NETLINK_TEST 25 cfg:配置结构 */ static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg); /*2.单播发送接口 参数: ssk:为函数 netlink_kernel_create()返回的socket。 skb:存放消息,它的data字段指向要发送的netlink消息结构,而 skb的控制块保存了消息的地址信息,宏NETLINK_CB(skb)就用于方便设置该控制块。 portid:pid端口。 nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;而如果为0,该函数在没有接收缓存可利用定时睡眠。 */ extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock); /*3.多播发送接口 参数: group:接收消息的多播组,该参数的每一个位代表一个多播组,因此如果发送给多个多播组; allocation:内存分配类型,一般地为GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用于原子的上下文(即不可以睡眠),而GFP_KERNEL用于非原子上下文。 */ extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid, __u32 group, gfp_t allocation); /*4.释放socket 参数: */ extern void netlink_kernel_release(struct sock *sk);
用户层操作
/*1.创建socket 参数: nlmsghdr结构常见操作: NLMSG_SPACE(len): 将len加上nlmsghdr头长度,并按4字节对齐; NLMSG_DATA(nlh): 返回数据区首地址; */ int netlink_create_socket(void) { //create a socket return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); } /*2.bind 参数: */ int netlink_bind(int sock_fd) { struct sockaddr_nl addr; memset(&addr, 0, sizeof(struct sockaddr_nl)); addr.nl_family = AF_NETLINK; addr.nl_pid = TEST_PID; addr.nl_groups = 0; return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl)); } /*3.发送接收*/ //使用sendmsg、recvmsg发送接收数据 ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); //使用sendto、recvfrom发送接收数据 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
3.实例
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <linux/netlink.h> #include <stdint.h> #include <unistd.h> #include <errno.h> #include <sys/poll.h> static void die(char *s) { write(2, s, strlen(s)); exit(1); } int main(int argc, char *argv[]) { struct sockaddr_nl nls; struct pollfd pfd; char buf[512]; //open hotplug event netlink socket memset(&nls, 0, sizeof(struct sockaddr_nl)); nls.nl_family = AF_NETLINK; nls.nl_pid = getpid(); nls.nl_groups = -1; pfd.events = POLLIN; pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (pfd.fd == -1) die("Not root\n"); //listen to netlink socket if (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl))) die("Bind failed\n"); while(-1 != poll(&pfd, 1, -1)) { int i, len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT); if (len == -1) die("recv\n"); //print the data to stdout i = 0; while (i < len) { printf("%s\n", buf + i); i += strlen(buf + i) + 1; } } die("poll\n"); return 0; }
编译:
gcc netlink_test.c -o netlink_test
————————————————