#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/mm.h> #include <linux/device.h> #include <asm/uaccess.h> #include<linux/slab.h> #define FREMAKS_DEVICE_CLASS_NAME "fremaks_class" #define FREMAKS_DEVICE_FILE_NAME "fremaks_file"//注意在dev目录下显示的是这个名字 #define FREMAKS_DEVICE_NODE_NAME "fremaks_node" #define MEM_SIZE 4*104 //通常这样定义一个设备结构体,借用了面向对象编程中的封装思想 typedef struct _fremaks_reg_dev { struct cdev cdev; unsigned char mem[MEM_SIZE]; int val; }fremaks_reg_dev; //static char *dev_name = "fremaks";//not use static int fremaks_major = 250; static int fremaks_minor = 0; fremaks_reg_dev *fremaks_dev = NULL; struct class *fremaks_class = NULL; static struct file_operations fremaks_fops; static int fremaks_setup_dev(fremaks_reg_dev *dev) { int err; dev_t devno = MKDEV(fremaks_major, fremaks_minor); memset(dev, 0, sizeof(fremaks_reg_dev)); cdev_init(&(dev->cdev), &fremaks_fops); dev->cdev.owner = THIS_MODULE; err = cdev_add(&(dev->cdev), devno, 1);//ok 0, error -1 if(err != 0) { return err; } dev->val = 0; return 0; } static int fremaks_open(struct inode* inode, struct file* filp) { fremaks_reg_dev *dev = NULL; dev = container_of(inode->i_cdev, fremaks_reg_dev, cdev);//通过结构中的某个变量获取结构本身的指针 filp->private_data = dev; return 0; } static int fremaks_release(struct inode* inode, struct file* filp) { return 0; } static ssize_t fremaks_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = -1; fremaks_reg_dev *dev = filp->private_data; if(count < sizeof(dev->val)) { goto out; } if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) { err = -EFAULT; goto out; } err = sizeof(dev->val); out: return err; } static ssize_t fremaks_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) { ssize_t err = -1; fremaks_reg_dev * dev = filp->private_data; if(count != sizeof(dev->val)) goto out; if(copy_from_user(&(dev->val), buf, count)) { err = -EFAULT; goto out; } err = sizeof(dev->val); out: return err; } static struct file_operations fremaks_fops = { .owner = THIS_MODULE, .open = fremaks_open, .release = fremaks_release, .read = fremaks_read, .write = fremaks_write, }; static int __init fremaks_init(void) { int err = -1; dev_t devno =MKDEV(fremaks_major, fremaks_minor); struct device *fremaks_class_dev = NULL; if(fremaks_major) err = register_chrdev_region(devno, 1, FREMAKS_DEVICE_NODE_NAME); else { err = alloc_chrdev_region(&devno, 0, 1, FREMAKS_DEVICE_NODE_NAME); //动态获得主设备号 } fremaks_major = MAJOR(devno); fremaks_minor = MINOR(devno); if(err < 0) { printk(KERN_ALERT"Failed to alloc char dev region.\n"); goto fail; } /* 1、查看当前控制台的打印级别 cat /proc/sys/kernel/printk 4 4 1 7 其中第一个“4”表示内核打印函数printk的打印级别,只有级别比他高的信息才能在控制台上打印出来,既 0-3级别的信息 2、修改打印 echo "新的打印级别 4 1 7" >/proc/sys/kernel/printk 3、不够打印级别的信息会被写到日志中可通过dmesg 命令来查看 4、printk的打印级别 #define KERN_EMERG "<0>" system is unusable #define KERN_ALERT "<1>" action must be taken immediately #define KERN_CRIT "<2>" critical conditions #define KERN_ERR "<3>" error conditions #define KERN_WARNING "<4>" warning conditions #define KERN_NOTICE "<5>" normal but significant condition #define KERN_INFO "<6>" informational #define KERN_DEBUG "<7>" debug-level messages */ fremaks_dev = (fremaks_reg_dev *)kmalloc(sizeof(struct _fremaks_reg_dev), GFP_KERNEL);//kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了 if(!fremaks_dev) { err = -ENOMEM; printk(KERN_ALERT"Failed to alloc freg device.\n"); goto unregister; } err = fremaks_setup_dev(fremaks_dev); if(err) { printk(KERN_ALERT"Failed to setup freg device: %d.\n", err); goto cleanup; } /* 我们在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为 我们提供了一组函数,可以用来在模块加载的时候自动在 /dev目录下创建相应设备节点,并在卸载模块时删除该 节点,当然前提条件是用户空间移植了udev。内核中定义了struct class结构体,顾名思义,一个struct class 结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于 sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。 这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。 */ fremaks_class = class_create(THIS_MODULE, FREMAKS_DEVICE_CLASS_NAME); if(fremaks_class == NULL) { err = PTR_ERR(fremaks_class); printk(KERN_ALERT"Failed to create freg device class.\n"); goto destroy_cdev; } fremaks_class_dev = device_create(fremaks_class, NULL, devno, "%s", FREMAKS_DEVICE_FILE_NAME); if(IS_ERR(fremaks_class_dev)) { err = PTR_ERR(fremaks_class_dev); printk(KERN_ALERT"Failed to create freg device.\n"); goto destroy_class; } printk(KERN_ALERT"Succedded to initialize fremaks device.\n"); return 0; //这种层层出错返回的方法值得学习 destroy_device: device_destroy(fremaks_class, devno); destroy_class: class_destroy(fremaks_class); destroy_cdev: cdev_del(&(fremaks_dev->cdev)); cleanup: kfree(fremaks_dev); unregister: unregister_chrdev_region(MKDEV(fremaks_major, fremaks_minor), 1); fail: return err; } static void __exit fremaks_exit(void) { dev_t devno = MKDEV(fremaks_major, fremaks_minor); if(fremaks_class) { device_destroy(fremaks_class, devno); class_destroy(fremaks_class); } if(fremaks_dev) { cdev_del(&(fremaks_dev->cdev)); kfree(fremaks_dev); } unregister_chrdev_region(devno, 1); } module_init(fremaks_init); module_exit(fremaks_exit); //module_param(dev_name, charp, S_IRUGO); module_param(fremaks_major, int, S_IRUGO); module_param(fremaks_minor, int, S_IRUGO); //module_param()的作用就是让那些全局变量对insmod 可见,使模块装载时可重新赋值。 //最后的 module_param(S_IRUGO,UG0为USR,GRP,OTH的缩写) 字段是一个权限值,表示此参数在sysfs文件系统中所对应的文件节点的属性,,类似文件操作中的mode MODULE_AUTHOR("fremaks_2014_05_24<fremaks@163.com>"); MODULE_DESCRIPTION("A Register Driver"); MODULE_LICENSE("GPL");//如果不声明LICENSE,模块被加载时将受到内核被污染的警告
1.执行make menuconfig时,编译系统会读取arch/$(ARCH)目录下的Kconfig文件,其中$(ARCH)指向cpu体系架构,然后通过“source “drivers/Kconfig””找到drvier目录下的Kconfig文件,然后通过source一级级递进。我们假设将此驱动放入char设备下,那么修改char目录下的Kconfig文件(加入source "drivers/char/fremaks/Kconfig",注意路径要写全不然无法打开文件),使得编译系统能找到驱动程序fremkas的Kconfig文件。而对于Makefile文件而言只需要写上obj-$(CONFIG_FREMAKS) += fremaks.o,然后在char目录下的Makefile中加入obj-$(CONFIG_FREMAKS) += fremaks/就可以找到fremaks下的Makefile文件了,当然也可以直接在char下的Makefile文件中写上整个路径,如:obj-$(CONFIG_FREMAKS) += fremaks/fremaks.o,这样fremaks目录下的Makefile文件也不用写了。 还有一种方法是单独编译fremaks,可以将之前的Makefile文件改为obj-m += fremaks.o在当前目录下执行make -C /home/kernel_path/ M=$(pwd) modules。 2.宏定义__init,用于告诉编译器相关函数或变量的仅用于初始化。编译器将标有__init(包括__initdata->用于变量)的所有代码存在特殊的内存段中,初始化结束后就释放这段内存(当然只有将此模块编译进内核才有意义)它的宏定义是这样的: #define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))。__exit ,标记退出代码,对于非模块无效。