现在的位置: 首页 > 综合 > 正文

linux简单字符驱动示例

2018年04月03日 ⁄ 综合 ⁄ 共 5812字 ⁄ 字号 评论关闭
#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 ,标记退出代码,对于非模块无效。

抱歉!评论已关闭.