chanllenge 06 misc device 2
在上一个文章里面, 提到file_operations 结构体一般只需要前面.owner .read .write三个域,
最开始我只初始化了这三个, 然后我的模块中read和write都没对offset进行特殊处理, 由内部自动处理
read 函数只是简单返回一个字符串"888888888888"
write 函数只是对比一下用户空间的输入是不是888888888888, 然后返回错误码或者是字节
于是, 写了一个测试文件
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> #define BUFSIZE 16 int main(int argc, char *argv[]) { int fd; int ret; char *path = "/dev/this"; char buf[BUFSIZE]; static char *test_str[] = {"888888888888", "8888888888888", "88888888888", "000000000000", "888888888888888888888888", (char *)0, }; char **tmp = test_str; fd = open(path , O_RDWR); if (fd == -1) { printf("open %s error: %s\n", path, strerror(errno)); exit(0); } memset(buf, '\0', BUFSIZE); ret = read(fd, buf, BUFSIZE); if (ret == -1) printf("read error: %s\n", strerror(errno)); printf("return is %s\n", buf); lseek(fd, 0, SEEK_SET); while (*tmp) { ret = write(fd, *tmp, strlen(*tmp)); if (ret == -1) printf("%s error: %s\n", *tmp, strerror(errno)); else printf("%s successfully\n", *tmp); tmp++; lseek(fd, 0, SEEK_SET); } close(fd); return 0; }
这个测试在模块中没有lseek的时候 系统会调用一个默认的lseek函数, 起初以为这个lseek函数确实做了些什么, 但是运行这个测试的时候, 传入匹配字串"888888888888"也返回错误码
于是在源码中找相应的代码, google了一下, 找到了如下
使用ftrace查看内核函数调用过程
# cd /sys/kernel/debug/tracing/ # echo 0 > tracing_on # echo function_graph > current_tracer # echo *seek* > set_ftrace_filter # echo "test_pid"(测试程序的pid) > set_ftrace_pid # echo 1 > tracing_on
之后运行测试程序 结束之后
# cat trace
结果显示, test中调用lseek系统调用, 然后实际调用的默认lseek是no_lseek函数, 这个函数只是简单的返回-ESPIPE, 并没有对文件位置进行操作.
在对普通文件读写的时候, 涉及到相应的文件系统, 比如我用的ext4, 在读写普通文件test.txt时
lseek的调用过程是
SyS_lseek() -> ext4_llseek() -> generic_file_llseek_size()
chanllenge 06 misc device
misc device 模块需要的东西比较少, 两个结构体和几个函数
struct miscdevice, stuct file_operations,
文件操作结构体一般填充前三个域
struct file_operations this_fops = { .owner = THIS_MODULE, .read = this_read, .write = this_write, .llseek = this_lseek, };
另外一个结构体
struct miscdevice this_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "this", /* 会出现在/dev 目录下 */ .fops = &this_fops, };
在模块加载的时候应该调用misc_register, 退出时调用misc_deregister
#include <linux/module.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/string.h> #include <linux/uaccess.h> static const char assigned_id[] = "************"; static const int len = 13; static ssize_t task06_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { return simple_read_from_buffer(buf, size, offset, assigned_id, len); } static ssize_t task06_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { char tmp[len]; int ret; if (size != len-1) return -EINVAL; memset(tmp, '\0', len); ret = simple_write_to_buffer(tmp, len-1, offset, buf, size); if ((ret != len-1) || (strncmp(tmp, assigned_id, len))) return -EINVAL; return ret; } static loff_t task06_lseek(struct file *file, loff_t offset, int a) { loff_t tmp; switch (a) { case SEEK_SET: file->f_pos = (offset < 0) ? 0 : offset; break; case SEEK_CUR: case SEEK_END: tmp = file->f_pos + offset; file->f_pos = (tmp < 0) ? 0 : tmp; break; default: break; } return file->f_pos; } struct file_operations task06_fops = { .owner = THIS_MODULE, .read = task06_read, .write = task06_write, /* .llseek = task06_lseek, */ }; struct miscdevice task06_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "eudyptula", .fops = &task06_fops, }; static int task06_init(void) { int err; err = misc_register(&task06_miscdev); if (err) pr_info("register error"); return err; } static void task06_exit(void) { int err; err = misc_deregister(&task06_miscdev); if (err) pr_info("no need to deregister"); } module_init(task06_init); module_exit(task06_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("task06"); MODULE_AUTHOR("************");
文件操作函数, read/write/llseek/
当对/dev/this 进行读写的时候, 会调用这三个函数,
ssize_t read(struct file *, char __user *, size_t, lofft_t *)
ssize_t write(struct file *, const char __user *, size_t, loff_t *)
lofft_t llseek(struct file *, lofft_t, int)
前两个函数用到用户空间数据, 因此可以调用copy_from_user, copy_to_user, simple_read_from_buffer, simple_write_to_buffer, 等来进行内核与用户空间的数据交换, 其中, 后面两个会调用前面两个函数.
*read和*write会对当前文件位置(loff_t *)进行操作 源文件在fs/read_write.c中. 所以在一个程序中对/dev/this进行读写操作, 应该处理文件位置, 或者:) 操作一次再重新打开
chanllenge 05 usb keyboard
在内核模块中使用MODULE_DEVICE_TABLE即可在编译模块的时候在modules.alias中写入相关信息, 用于在对应的device_id插入拔出时采取对应的动作, 如果模块未被载入, 则被注明的模块会被自动加载
相关文档:
Documentation/usb/hotplug.txt
LDD linux device driver
使用MODULE_DEVICE_TABLE需要一个id_table, 这里只用到usb keyboard
static const struct usb_device_id usb_id_table[] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD)}, {}, };
将编译的ko文件移入/lib/modules/`uname -r`/
首先移除usbhid模块, #rmmod usbhid
这样编译的文件在USB keyboard插入电脑时会载入
在hotplug.txt文件中提到了usb_driver结构, 于是在模块中加入
static struct usb_driver mydriver = { .name = "usbhid", /* important */ .id_table = usb_id_table, .probe = my_probe, .disconnect = my_disconnect, };
probe是在设备接入时被调用, disconnect在设备取出时调用
关键是name域, 这个域应该和device name匹配成功时, 才会调用probe函数
因为当前系统中用了一个usbhid的模块, 所以把这个模块改名, 并且rmmod usbhid
此时, 在模块初始化函数中应该调用 usb_register函数, 退出函数中调用usb_deregister函数
注意, usb_register函数调用不成功的时候, 不应该再调用usb_deregister
#include <linux/module.h> #include <linux/device.h> #include <linux/usb.h> #include <linux/hid.h> static int reg_false = 0; static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id) { dev_info(&intf->dev, "usb probe routine\n"); return 0; } static void usb_disconnect(struct usb_interface *intf) { dev_info(&intf->dev, "usb disconnect routine\n"); return; } static const struct usb_device_id usb_id_table[] = { {USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_KEYBOARD)}, {}, }; MODULE_DEVICE_TABLE(usb, usb_id_table); static struct usb_driver usb_driver = { .name = "usbhid", .id_table = usb_id_table, .probe = usb_probe, .disconnect = usb_disconnect, }; static int usb_init(void) { pr_info("Hello World\n"); if (usb_register(&usb_driver)) { reg_false = 1; return 0; } pr_info("usb_driver registered\n"); return 0; } static void usb_exit(void) { pr_info("exit...\n"); if (reg_false == 0) usb_deregister(&usb_driver); } module_init(usb_init); module_exit(usb_exit);
linux 内核代码格式 Documentation/CodingStyle
tabs为8空格, 这样对结构块的开始结尾比较清晰, 特别当你对着电脑看了很多小时, 大缩进让看代码更容易. 有人会说8缩进让代码太靠右了, 一行里面很难写完, 如果你有了至少四层的缩进, 那只能说你的代码写太差了.
不要在一行里面写多条语句, 除非你想做一些特别的事.
避免奇怪的表达式.
不要用空格代表缩进.
每行最多有80个字符
大括号的用法, 一般控制结构前一个大括号在行尾, 后一个大括号占一行的首非空字符. 函数的时候, 两半大括号都占一行. 在一般只有一条语句的控制结构里面最好不使用{}
比如:
if (condition)
do_this;
或者:
if (condition)
do_this;
else
do_else;
但是如果有一个分支只有一条语句, 每个里面都使用{}
多数关键字后用一个空格, 除了(sizeof, typeof, alignof, __attribute__). 在(后面和)前面不要加多余的空格. 指针的*紧贴着指针名, 像char *pointer; char *match_strdup(substring_t *s); 在下列运算符左右都加一个空格= + - < > * / % | & ^ <= >= == != ? : 但是&(取地址) *(指针) + - ~(取反) !(非) sizeof typeof alignof __attribute__ 后面不加空格. ++ -- 前面不要或后面不要空格.
变量名函数名, 不要使用混合大小写的名字(一些全局的除外), 例如, 全局函数想计算当前活动的用户数, 函数名应该是count_active_users()或者更简单的, 而不是cntusr(). 不要把类型加进变量名或者函数名中. 本地(局部)变量或函数的命名应该简洁明了, 像循环计数i或者一个临时tmp; 一个函数里面不要太多变量以致于自己都容易弄混...
不要给结构体或者指针用typedef. typedef只用于(totally opaque objects, clear integer types, use sparse to literally create a new type for type_checking, new types identical to C99 types, types safe for use in userspace, ==), 一个标准就是永远不要用typedef,
函数应该尽量短, 只做一件事就好. 长短一两页即可(80x24), do one thing and do it well.
当然, 如果你的函数里面有一个很长的switch-case结构. 或者, 你有一个比较复杂的函数(大一的学生都看不懂)you should adhere to the maximum limits all the more closely, 使用恰当名字的函数.
函数的局部变量应该在5-10, 否则你就走太远了.
源码中函数前后用空白行分开. 如果函数需要被导出, EXPORT*宏应该在}的下一行
函数声明中, 参数应该也包括参数名,
适当的使用goto, 当一个函数有多个退出点的时候,
注释, 适当的使用注释, 不要解释你的代码是怎么工作的, 解释你的代码是做什么的, 而不是怎么做的.
尽量不要在一个函数体内部写注释.
内核函数注释有特定的格式, Documentation/kernel-doc-nano-HOWTO.txt and scripts/kernel-doc
linux使用C89格式注释/*...*/, 不要使用C99// ... 注释
多行注释如下
/*
* something
* something
*/
emacs用户
Kconfig文件格式 Documentation/kbuild/kconfig-language.txt
各种数据结构都应该有一个引用计数, 内核中是没有垃圾收集机制的. 如果另外一个线程能使用你的数据结构但是却没有一个引用计数, 很可能就是一个bug了.
宏, 枚举和RTL
避免使用宏的情况: 里面包含控制流的, 如exit return; 宏里面包含了一个局部本地变量的; 宏被当作左值用了; 宏定义时的封装问题
输出内核信息
输出信息要准确明了
函数返回值问题, if the name of a function is an action or an imperative command, the function should return an error-code integer. if the name is a predicate, the function should return a "succeeded" boolean.
不要重复做轮子, include/linux/kernel.h里面包含一些常用的宏变量
不要带有一些编辑器插入的语句
2014-12-17 update
1. 头文件
例子: asm/delay.h => linux/delay.h (https://groups.google.com/forum/#!topic/fa.linux.kernel/yccGyGoT3pQ)
2. printk VS pr_*
(http://stackoverflow.com/questions/22077540/order-of-preference-printk-vs-dev-dbg-vs-netdev-dbg, https://lkml.org/lkml/2014/7/14/174)
Depending on what you are coding you should use a different print style:
printk()
: never
pr_debug()
: always good
dev_dbg()
: prefered when you have a struct device
object
netdev_dbg()
: prefered when you have a struct netdevice
object
[something]_dbg()
: prefered when you have a that something object
3. Documentation/zh_CN/CodingStyle
Eudyptula Challenge task01
=========================
Write a Linux kernel module, and stand-alone Makefile, that when loaded prints to the kernel debug log level, "Hello World!" Be sure to make the module able to be unloaded as well
=========================
obj-m :=task01.o KVER := `uname -r` KDIR = /lib/modules/$(KVER)/build all: make -C $(KDIR) M=`pwd` modules clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers
#include <linux/module.h> static int task01_init (void) { printk(KERN_INFO "Hello World!\n"); return 0; } static void task01_exit (void) { printk(KERN_INFO "task01 exit...\n"); } module_init(task01_init); module_exit(task01_exit); MODULE_AUTHOR(""); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("task01");
这样是不符合要求的, 后来查得, 在内核源码目录include/linux/kernel.h(2.6.32.61), include/linux/kern_levels.h(3.17.0)文件中
#define KERN_EMERG KERN_SOH "0" /* system is unusable */ #define KERN_ALERT KERN_SOH "1" /* action must be taken immediately */ #define KERN_CRIT KERN_SOH "2" /* critical conditions */ #define KERN_ERR KERN_SOH "3" /* error conditions */ #define KERN_WARNING KERN_SOH "4" /* warning conditions */ #define KERN_NOTICE KERN_SOH "5" /* normal but significant condition */ #define KERN_INFO KERN_SOH "6" /* informational */ #define KERN_DEBUG KERN_SOH "7" /* debug-level messages */
共8个级别, 控制台也有一个日志级别console_loglevel, 当printk的输出级别小于console_loglevel时才会显示出来, 详细见http://blog.sina.com.cn/s/blog_4a4832fe0100cvrk.html
把源文件中KERN_INFO改成KERN_DEBUG即可