您的位置:首页 > 运维架构 > Linux

linux简单字符驱动示例

2014-05-24 19:09 337 查看
#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 ,标记退出代码,对于非模块无效。


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux linux内核 c