linux简单字符驱动示例

来源:互联网 发布:python matlab 对比 编辑:程序博客网 时间:2024/06/06 07:46
#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 usestatic 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 -1if(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/printk3、不够打印级别的信息会被写到日志中可通过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文件系统中所对应的文件节点的属性,,类似文件操作中的modeMODULE_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 ,标记退出代码,对于非模块无效。

0 0
原创粉丝点击