驱动对一些人来说很难,而对一些人来说很容易。窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间。对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要的——不过,有许多现在的“模板”可供参考,总算不用白手起家。

本文所述的仅是一个独立的、简单的字符型设备驱动。最简单的字符设备当属经典的“Hello World”。而这个比它复杂一点:在用户空间写一字符串到内核空间,再将其写到用户空间。从应用层角度来看,先写一字符串到内核,再读这个字符串。

先说测试程序,代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define device "/dev/bar"

int main(void)
{
        int fd;
        char buf[] = "Late Lee";
        char buf2[30]={0};
        int len;
        fd = open(device, O_RDWR);
        if (fd < 0)
        {
                perror("Open device faile!");
                return -1;
        }
        len = write(fd, buf, sizeof(buf));
        printf("buf: %s %d/n", buf, len);
        len = read(fd, buf2, 25); // 由此指定读取数据,可大可小,但是驱动只读取这个指定的(大者读实际值),并返回
        printf("buf2: %s %d/n", buf2, len);
        close(fd);
        return 0;
}

代码很简单,使用open打开设备,调用write将buf的东西写到由fd指定的设备(文件)中。之后再使用read读到buf2中,打印字符串,最后关闭设备。就这么简单。

实际运行结果如下:

# ./a.out
buf: Late Lee 9
buf2: The voice from hell: Late 25

其中“The voice from hell:”仅仅表明这个字符来自驱动,无实际意义。

驱动程序也很简单,代码如下:

/*************************************************************************
        Late Lee from http://www.latelee.org
简单的字符型设备驱动
从应用层获取一数据,再复制到应用层(在前面添加字符串)。
注册设备号及设备号的几个宏,均系ldd3例子scull。

何处释放data更好?看ldd3,似乎exit中更好。
2011-04-29 & 2011-05-06
*************************************************************************/

#include <linux/module.h>
#include <linux/kernel.h>       /**< printk() */
#include <linux/init.h>

#include <linux/cdev.h>                /**< cdev_* */
#include <linux/fs.h>
#include <asm/uaccess.h>        /**< copy_*_user */

#include <linux/types.h>        /**< size_t */
#include <linux/errno.h>        /**< error codes */
#include <linux/string.h>

#ifdef DEBUG /* define it in Makefile or somewhere */
/* KERN_INFO */
#define debug(fmt, ...) printk(KERN_DEBUG fmt, ##__VA_ARGS__)
#else
#define debug(fmt, ...)
#endif

#define DEV_NAME "foo"

//#define HAVE_MAJOR
#ifdef HAVE_MAJOR
#define DEVICE_MAJOR 248
#else        /* auto alloc */
#define DEVICE_MAJOR 0
#endif /* HAVE_MAJOR */

#define FOO_NR_DEVS 1

struct cdev foo_cdev;
int foo_major = DEVICE_MAJOR;
int foo_minor = 0;
int foo_nr_devs = FOO_NR_DEVS;
dev_t devno;
char *data;

static int foo_open(struct inode *inode, struct file *filp)
{
        debug("in %s()/n", __func__);
        return 0;
}

static int foo_release(struct inode *inode, struct file *filp)
{
        debug("in %s()/n", __func__);
        //kfree(data);
        //data = NULL;
        return 0;
}

static ssize_t foo_read(struct file *filp, char *buf, size_t count, loff_t *f_ops)
{
        int len;
        char *tmp;
       
        if (count < 0)
                return -EINVAL;
        tmp = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL); // ?? here
        sprintf(tmp, "The voice from hell: %s", data);
        len = strlen(tmp);
        if (len < count)
                count = len;
        if ( copy_to_user(buf, tmp, count) )
                return -EFAULT;
        debug("in %s() tmp: %s/n", __func__, tmp);
        debug("in %s() buf: %s/n", __func__, buf);
        debug("in %s() data: %s/n", __func__, data);
        kfree(tmp);
        return count;
}

static ssize_t foo_write(struct file *filp, const char *buf, size_t count, loff_t *f_ops)
{
        if (count < 0)
                return -EINVAL;
        data = (char *)kmalloc(sizeof(char) * (count+1), GFP_KERNEL);
        if (data == NULL)
                return -ENOMEM;
        if (copy_from_user(data, buf, count+1))
                return -EFAULT;
        debug("in %s() buff: %s/n", __func__, buf);
        debug("in %s() data: %s/n", __func__, data);
        return count;
}

static int foo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
        switch (cmd)
        {
       
        default:
                return -EINVAL;
        }
        return 0;
}

static struct file_operations foo_fops = {
        .owner   = THIS_MODULE,
        .open    = foo_open,
        .release = foo_release,
        .read    = foo_read,
        .write   = foo_write,
        .ioctl   = foo_ioctl,
};

static int __init foo_init(void)
{
        int ret = -1;

cdev_init(&foo_cdev, &foo_fops);
        foo_cdev.owner = THIS_MODULE;
        /* register to who? */
        if (foo_major)
        {
                devno = MKDEV(foo_major, foo_minor);
                ret = register_chrdev_region(devno, foo_nr_devs, DEV_NAME);
        }
        else
        {
                ret = alloc_chrdev_region(&devno, foo_minor, foo_nr_devs, DEV_NAME); /* get devno */
                foo_major = MAJOR(devno); /* get major */
               
        }
        if (ret < 0)
        {
                debug(" %s can't get major %d/n", DEV_NAME, foo_major);
                return -EINVAL;
        }
       
        ret = cdev_add(&foo_cdev, devno, 1);
        if (ret < 0)
        {
                debug(" %s cdev_add failure!/n", DEV_NAME);
                return -EINVAL;
        }
       
        debug("%s init ok!/n", DEV_NAME);
        return 0;
}

static void __exit foo_exit(void)
{
        if (data != NULL)
                kfree(data);
        unregister_chrdev_region(devno, 1);
        cdev_del(&foo_cdev);
        debug("%s exit ok!/n", DEV_NAME);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Chiangchin Li");

其中最后两个函数foo_init和foo_exit分别在加载驱动和卸载驱动时执行。如果驱动程序直接编入内核,可以在启动信息中看到,如果是单独加载,在执行insmod时执行,当然,在何处显示与printk打印级别有关系。

我们的驱动中使用KERN_DEBUG,因此可以使用dmesg查看——注意,由于insmod、rmmod等等需要使用root权限,文中统一使用root,shell提示符为“#”。

# insmod GotoHell.ko
# dmesg | tail
……
foo init ok!

当执行应用层的测试程序时,dmesg的显示如下:

# ./a.out
buf: Late Lee 9
buf2: The voice from hell: Late 25
# dmesg | tail
……
foo init ok!
in foo_open()
in foo_write() buff: Late Lee
in foo_write() data: Late Lee
in foo_read() tmp: The voice from hell: Late Lee
in foo_read() buf: The voice from hell: Late
in foo_read() data: Late Lee
in foo_release()

可以看到,当执行close时,驱动中执行foo_release。当卸载模块时,dmesg显示如下:

# rmmod GotoHell.ko
# dmesg | tail     
……
foo init ok!
in foo_open()
in foo_write() buff: Late Lee
in foo_write() data: Late Lee
in foo_read() tmp: The voice from hell: Late Lee
in foo_read() buf: The voice from hell: Late
in foo_read() data: Late Lee
in foo_release()
foo exit ok!

对照测试程序及驱动显示的调试信息,可以看到应用层调用的如open、write、read、close等等系统调用,在驱动中均在对应的函数,它正是通过如下结构体来实现的:

static struct file_operations foo_fops = {
 .owner   = THIS_MODULE,
 .open    = foo_open,
 .release = foo_release,
 .read    = foo_read,
 .write   = foo_write,
 .ioctl   = foo_ioctl,
};

由于程序比较简单,不作注释和解释,一切尽在代码中。本文中的“模块”、“驱动”在某种意义上是同义词。
本文完整的“工程”代码可以在下面地址下载,为确保下载的东西的确是本文所提及的代码,最好确认一下它的md5和:deb16fcfbdc5e7f136ae988a0a26aad3

工程:简单字符型设备驱动例子-来自latelee.org

一个简单字符型设备驱动及其测试相关推荐

  1. linux 字符设备驱动测试,一个简单字符型设备驱动及其测试

    驱动对一些人来说很难,而对一些人来说很容易.窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间.对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要 ...

  2. 通过内存模拟硬盘实现一个简单的块设备驱动

    本文的主要工作是通过硬盘来模拟内存,按照块设备驱动编程的框架实现一个简单的块设备驱动程序. 一.前期的准备工作 1.基本开发环境 Linux内核版本:Linux-3.4.10 开发板 : JZ2440 ...

  3. linux内核定义注册设备,linux字符型设备驱动 一.注册设备并创建设备文件

    1.字符设备 字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系 Linux内核中: a -- 使用cdev结构体来描述字符设备; b -- 通过其成员dev_t来定义设备号(分为主.次设 ...

  4. [设备驱动] 最简单的内核设备驱动--字符驱动

    [设备驱动] 最简单的内核设备驱动--字符驱动  概要: x86平台上(linux-2.6.34.14;Linux debian 3.2.0-3-686-pae)编写一个256字节的字符驱动程序.在/ ...

  5. 《maven实战》笔记(2)----一个简单maven项目的搭建,测试和打包

    参照<maven实战>在本地创建对应的基本项目helloworld,在本地完成后项目结构如下: 可以看到maven项目的骨架: src/main/java(javaz主代码) src/te ...

  6. 非即插即用型设备驱动的加载过程

    非即插即用型设备驱动的加载过程 1. 非PnP总线驱动在系统启动时通过扫描注册表发现非PnP设备的存在,并向OS报告ID信息.(例如根总线驱动通过扫描 HKLM\ SYSTEM\ CurrentCon ...

  7. linux misc device字符杂项设备驱动

    杂项设备也是在嵌入式系统中用得比较多的一种设备驱动.miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同.misc设备其实就是特殊的字符设备,主设备编号采用10,并且可自 ...

  8. 最简单的scull设备驱动

    目录 一. 实验题目:设备管理与驱动编程 二. 实验目的 三. 实验内容 四. 实验过程及结果 1)关键代码 2)实验过程 第一部分 内核外编译: 第二部分 源码树内编译 五. 未解决的问题 附实验源 ...

  9. 使用内核定时器的second字符设备驱动及测试代码

    驱动: #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #inc ...

最新文章

  1. 打工人的一把辛酸泪,网站提现为啥多于800要扣20%的税?我想这些东西你需要明白
  2. 用中断的方式实现delay延时的一点注释
  3. python __repr__
  4. 山寨笔记本电脑风暴要来了
  5. 基站的小区号256变换
  6. 【每晚20点红包雨】2018天猫聚划算99大促欢聚盛典活动介绍
  7. java chmod 777_chmod 777 修改权限
  8. BZOJ 3699 GAL的数组
  9. CSDN 博客被自己误删了怎么办---(联系QQ客服)
  10. ajax请求是宏任务还是微任务_好程序员web前端学习路线分享了解AJAX是什么
  11. 【javaweb简单教程】1.搭建Web环境、初识JSP
  12. Kubernetes kube-proxy 如何与 iptables 完美配合使用
  13. 27. OP-TEE驱动篇----libteec接口在驱动中的实现
  14. 单片机总结及实训QY-BC12
  15. 计算机应用知识试题,《计算机应用基础》试题及答案
  16. 企业如何有效利用PLM系统(上)
  17. CQUPT Java 随堂测试1
  18. 【高精度定位】RTK定位与RTD定位知识科普
  19. 计算机组成原理 赖晓铮,计算机组成原理实验 2.5 运算器 赖晓铮
  20. uni-app getLocation:fail [geolocation:7]KEY错误 解决办法

热门文章

  1. 页面自动刷新,页面自动跳转
  2. 半夜“逃离上海”?阿里副总裁贾扬清回应:正常差旅回去看病 请不要误解
  3. 消息称百度网盘青春版降速23倍:从52MB/s降至2.2MB/s
  4. 转转集团二手双11大促:长沙用户“秒杀”99新iPhone12成首单
  5. 谷歌Pixel 6系列正式发布:搭载自研Tensor SoC 规格超骁龙888
  6. 格罗方德今年有望提高汽车芯片产量,并将投入60亿美元扩大产能
  7. iQOO Neo5搭载66W超快闪充:30分钟回血!
  8. 锤子手机成绝唱了,网友微博喊话罗永浩:赶快买回来吧!
  9. 三星电子预计第四季度营业利润为9万亿韩元 低于分析师预期
  10. Redmi K30S更多细节曝光:骁龙865加持 提供多款配色