本文共 6018 字,大约阅读时间需要 20 分钟。
一个LINUX 内核模块编程的手记, 未写完不断更新中…
一 相关命令 0 查看系统装载了哪些 内核模块 lsmod modulename
1 加载内核模块 insmod modulename
2 卸载内核模块 rmmod modulename
3 创建设备文件 mknod filename dev_type major_number minor_number 说明: 参数 filename 创建的设备文件名称 dev_type 设备文件的类型, c character device (字符设备) b block device (块设备) 两种文件类型的主要区别, block device 有 buffer major_number 主要号码 用于标记那个驱动可以使用此设备(每一个驱动只有唯一的 ) minor_number 镜像号码 the driver to distinguish between the various hardware it controls(不能准确翻译)
——————————————— 例: ls -l /dev/hda[1-3] brw-rw—- 1 root disk 3, 1 Jul 5 2000 /dev/hda1 brw-rw—- 1 root disk 3, 2 Jul 5 2000 /dev/hda2 brw-rw—- 1 root disk 3, 3 Jul 5 2000 /dev/hda3
b 表示设备类型
3 表示 major number 1,2,3 表示 minor number ———————————————-
4 查看程序执行过程( 一般用于 检查程序用了哪些系统调用) strace ./app
5 自动加载模块(调试好了再用, 要不系统容易挂) depmod -a (检查内核 依赖关系是否正确) 生成 /lib/modules/version/modules.dep 文件 modprobe 在 insmod之前挂载 (内核模块也有依赖性~~) 可以编辑 /etc/modules.conf 让系统启动时加载 重要的,比如驱动 modprobe -a msdos (加载 于msdos有依赖关系的所有模块)
二 相关文件 1 /proc/modules. 存放加载内核的信息。 lsmod 就是读的这个文件
2/etc/modules.conf 让系统启动时加载 重要的,比如驱动
3/lib/modules/version/modules.dep 内核依赖关系 ( 猜的, 没看)
4/var/log/message 我是用turbo 10 桌面版本 作的测试 所以 printk() 函数 输出时不能显示在我的kde的终端上 这个问题曾经郁闷 我 20 分钟, 后来 情急之下 查看 message文件 才发现都printk输出信息都记录在 这里了, 我也够笨的:( 当知道怎么创建使用 "字符设备文件" 之后 就能写自己的 printx函数了。也知道为什么不能显示在 kede的终端上了 :)
5 /proc/devices 已经使用的设备文件 里面数据的结构是 major number device name
三 用户空间 VS 内核空间 | 系统调用 (有趣) 内核可以访问所有的资源,用户程序也是如此, 只不过,要借助驱动来访问。 内核要管理所有的资源,他是用户程序于硬件的一个桥梁。一个用户程序运行过程当中, 他把通过系统调用,把数据传送给内核空间, 然后内核处理这个请求, 之后又把结果返回给用户空间。内核可以捕获到所有的这些信息, 你可以替换到原来的系统调用, 你可一 让open打不开文件, 你可以让mkdir建立不了目录!!
三 该死的MakeFile!!! 一开始看的所有相关的文章,几乎都是基于2.4版本的,照着2。4版本的makefile写,但是我的内核是2.6的, 所以我百试不爽,编译完了不能加载,郁闷了好久,差一点没换掉内核,后来找到了2。6 的手册才知道问题所在。
2.6 版本内核模块的MakeFile 编译成.ko文件 ———————————————————— ifneq ($(KERNELRELEASE),) obj-m:= test.o #模块名称 else KDIR:= /lib/modules/$(shell uname -r)/build #内核路径 PWD:= $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules endif ————————————————————
2.4 版本内核模块的MakeFile 编译成.o文件 ———————————————————— ARGET =test WARN =-W -Wall -Wstrict-prototypes -Wmissing-prototypes INCLUDE=-isystem /lib/modules/2.6.0-1/build/include #内核路径 CFLAG =-O2 -DMODULE -D__KERNEL__ ${WARN} ${INCLUDE} CC =gcc
${TARGET}.o: ${TARGET}.c ${CC} ${CFLAG} -c ${TARGET}.c .PHONY: clean
clean: rm -rf ${TARGET}.o ————————————————————
四 内核模块程序结构
————————————————- /* * hello-1.c - 简单的hello the world 程序摘自手册 */ #include <linux/module.h>/* 所有的内核模块都需要的 */ #include <linux/kernel.h>/* Needed for KERN_ALERT */
//初始化程序,加载模块时执行 int init_module(void) { printk("<1>Hello world 1.n"); //本函数输出信息, 相当于用户空间的printf
/* * 返回非0值,表示出错,内核不能被加载 */ return 0; }
//清除函数,卸载模块时执行 void cleanup_module(void) { printk(KERN_ALERT "Goodbye world 1.n"); } ——————————————————
2.6 版本有更多的书写方法,比如加入加载参数, 加入内核说明等等,可以参考相关文档 http://www.tldp.org/LDP/lkmpg/2.6/html/index.html
五 应用字符设备文件
5.1 数据结构 1 file_operations 在<linux/fs.h> 中定义
说明: 这个结构体中包含对设备操作的函数指针, 可以通过他的配置和实现操作函数来完成对设备的操作 把你不使用的入口 设为NULL。
—————————————————————————————- struct file_operations { struct module *owner; loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int,unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t(*readv) (struct file *, const struct iovec *, unsigned long,loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,void __user *); ssize_t(*sendpage) (struct file *, struct page *, int, size_t,loff_t *, int); unsigned long (*get_unmapped_area) (struct file *, unsigned long,unsigned long, unsigned long,unsigned long); }; —————————————————————————————-
靠,这家伙也太难记了,我曾经3次看到这,就去论坛灌水了,不想往下看了。 今儿耐着性子把他看完,才发现自己是个笨蛋!
在新的驱动程序中,你可能会看到一更简单,更友好的给file_operations 赋值的方式,看下面的这个结构,手册上说这玩意是 gcc extension gcc 扩展 (GNU c), 是这个意思吧, 反正我理解的就是,gcc在编译它的时候 先把下面这个玩意翻译正标准的c,然后再编译。 哀,可怜的 c标准!不过话又说回来,这东西除了linux别的也没什么用处。
—————————————— struct file_operations fops = { read: device_read, //对应 相应的函数 write: device_write, open: device_open, release: device_release }; ——————————————
手册上说 下面的这个是 C99 特性写法!(这玩意BSD 也用不了吧? 为什么还要这么乱!) 注意:低版本的 gcc 可能会不正常编译地!因为它超出了GNU C 标准 (http://www-900.ibm.com/developerWorks/cn/linux/l-c99/index.shtml C99 标准参考)
—————————————— struct file_operations fops = { .read = device_read, //对应 相应的函数 .write = device_write, .open = device_open, .release = device_release }; ——————————————
俺选择 的是第一种写法!
2 *file* 结构 在 <linux/fs.h> 中定义 说明: * 它不同于FILE (glibc 中定义的) * 它是内核级的结构体 * 它表示抽象的 'file' * 它不是磁盘上的那个 file
* 驱动用结构体来给这个东西赋值,****** 完善理解 ******* * 指向它的指针类型 为 filp , 也可以直接写成 struct file file
5.2 相关函数
1 注册设备函数 <linux/fs.h> int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
还记得之前写的创建设备文件的命令吧, 参数说明: major: 它就是 major number 对应唯一的驱动,记住! name: 它就是 filename 可以在 /proc/devices 里面找到它(叫device name 更好点) fops: file_operations 结构体, 这个可以理解为操作设备的 内核入口,可以用上面说的两种方法去填充它
函数返回: 1 major 为 0: 动态分配的 major number 值 2 major 为 指定值: 3 major 为特殊数值:
特殊说明: 因为一个驱动只能有一 个 major值,所以有以下三种创建方法 1 先创建设备文件,你可以分配一个固定的major值 2 动态分配 major值, 读取 /proc/devices 文件 找到major值, 用脚本创建(在用户空间创建设备文件) 3 动态分配 major值,使用系统调用函数,直接创建设备文件 (在内核空间执行)
第 1 种方法不可取, 因为你不可预知 是否将来要 用到这个major number 第 2 种方法,需要在用户空间创建 第 3 种方法最好,可以动态的创建,销毁设备文件,不过麻烦
2 取消注册设备函数 关于:counter
转载地址:http://pbemb.baihongyu.com/