码迷,mamicode.com
首页 > 其他好文 > 详细

LDD3 字符设备驱动简单分析

时间:2016-04-11 18:48:32      阅读:139      评论:0      收藏:0      [点我收藏+]

标签:

最近在看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.c

在模块的初始化函数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中,把这个驱动继续扩展。

LDD3 字符设备驱动简单分析

标签:

原文地址:http://www.cnblogs.com/sandals/p/5378973.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!