标签:
最近在看LDD3,理解了一下,为了加深自己的印象,自己梳理一下。我用的CentOS release 6.6 (Final)系统。
一、编写编译内核模块的Makefile
以下是我用的Makefile
ifneq ($(KERNELRELEASE),) # 第一次被调用时,KERNELRELEASE为空,所以不会被执行 obj-m := scull.o else # 将内核编译用的Makefile路径赋值给KERNELRELEASE KERNELRELEASE ?= /lib/modules/`uname -r`/build # 当前工作目录 PWD := `pwd` default: # 通过内核编译树编译一个模块 $(MAKE) -C $(KERNELRELEASE) M=$(PWD) modules endif clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
Makefile要注意tab键和空格不能互相替换使用。这个Makfile其实会被调用2次,第一次这个Makefile会调用 /lib/modules/`uname -r`/build 目录下的Makefile,也就是linux编译内核用的Makefile并且传入后面参数。linux的内核Makefile会再次调用这个Makefile编译内核模块。
二、编写内核模块
#include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> MODULE_LICENSE("GPL"); static int scull_major = 66; struct scull_dev { struct cdev cdev; }; struct scull_dev scull_device; static ssize_t scull_read(struct file * filep, char __user * buf, size_t count, loff_t *f_pos) { printk(KERN_INFO "scull_read\n"); return 0; } static ssize_t scull_write(struct file *filep, const char __user *buf, size_t count, loff_t *fpos) { printk(KERN_INFO "scull_write\n"); return count; } static int scull_open(struct inode *inode, struct file *filep) { printk(KERN_INFO "scull_open\n"); return 0; } static int scull_release(struct inode *inode, struct file *filep) { printk(KERN_INFO "scull_release\n"); return 0; } struct file_operations scull_fops = { .owner = THIS_MODULE, .read = scull_read, .write = scull_write, .open = scull_open, .release = scull_release }; static void scull_setup_cdev(struct scull_dev *dev, int index) { cdev_init(&(dev->cdev), &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; if (cdev_add(&dev->cdev, MKDEV(scull_major, 0), 1)) { printk(KERN_NOTICE "error adding scull\n"); } } static int scull_init(void) { if (printk_ratelimit()) printk(KERN_INFO "scull loaded.\n"); if (register_chrdev_region(MKDEV(scull_major, 0), 1, "scullc") != 0) { printk(KERN_NOTICE "register chrdev failed.\n"); } scull_setup_cdev(&scull_device, 0); return 0; } static void scull_cleanup(void) { if (printk_ratelimit()) printk(KERN_INFO "scull unloaded.\n"); unregister_chrdev_region(MKDEV(scull_major, 0), 1); } module_init(scull_init); module_exit(scull_cleanup);
在模块的初始化函数scull_init里面注册,字符设备。我这边直接是固定的主设备为66,次设备号为0。注册字符设备函数为register_chrdev_region。另外,在scull_cleanup也就是模块卸载函数里面调用unregister_chrdev_region释放申请主设备号。大家自己要注意主设备号不要和自己机器上已有的设备号重复,否则会注册不成功。注册成功的话,我们可以通过/proc/devices这个文件来看。
从图片中,可以看出来,我们已经注册成功了。注册成功之后,自然是要初始化这个设备,通过使用cdev_init和cdev_add来向内核注册字符设备。同时会将struct file_operations scull_fops赋值给这个设备。也就是说当用到这个驱动的设备打开时,scull_open会被调用,scull_read会在读取设备被调用,scull_write会在写入时被调用。这个后面我有演示。
三、创建设备文件
在第二步中,我们已经编写好模块,并且也向内核注册了,申请了主设备号和次设备号,我们现在只要通过下面命令来创建一个设备文件,对这个设备文件的操作,就会使用我们刚刚写的那个简单字符设备驱动。
mknod /dev/scull0 c 66 0
创建完后,通过命令可以看到
图片中就是我们注册的主设备号为66,次设备号为0.
四、测试字符驱动
insmod scull.ko 加载驱动模块
echo 1 > /dev/scull0 向设备文件写入 1
cat /dev/scull0 输出设备文件内容
rmmod scull.ko卸载驱动
以上四个命令分别对应下图的输出
从图中可以看出来 echo的时候会先打开设备,再写入,最后关闭。cat命令会先打开设备,再读取,最后关闭。后面,我会再模仿LDD3中,把这个驱动继续扩展。
标签:
原文地址:http://www.cnblogs.com/sandals/p/5378973.html