当前位置:首页 > 公众号精选 > IOT物联网小镇
[导读]作 者:道哥,10年嵌入式开发老兵,专注于:C/C、嵌入式、Linux。关注下方公众号,回复【书籍】,获取Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章(PDF格式)。目录混乱的API函数旧的API函数新的API函数代码实操创建驱动程序源文件创建Makefile...

作  者:道哥,10 年嵌入式开发老兵,专注于:C/C 、嵌入式、Linux


关注下方公众号,回复【书籍】,获取 Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章( PDF 格式)。


目录


  • 混乱的 API 函数


  • 旧的 API 函数


  • 新的 API 函数


  • 代码实操


    • 创建驱动程序源文件


    • 创建 Makefile 文件


    • 编译、加载驱动模块


  • 应用程序


    • 打开、读取、写入设备


    • 卸载驱动模块


    • 小结


  • 自动在 /dev 目录下创建设备节点


  • 代码下载


别人的经验,我们的阶梯!


大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【字符设备的驱动程序】


在上一篇文章中,讨论的是Linux系统中,驱动模块的两种编译方式。


我们就继续以此为基础,用保姆级的粒度一步一步操作,来讨论一下字符设备驱动程序的编写方法。


  1. 这篇文章的实际操作部分,使用的是的 API 函数;


  2. 下一篇文章,再来演示新的 API 函数;


混乱的 API 函数

我在刚开始接触Linux驱动的时候,非常的困扰:注册一个字符设备,怎么有这么多的 API 函数啊?


参考的每一篇文章中,使用的函数都不一样,但是执行结果都是符合预期的!


比如下面这几个:


  1. register_chrdev(...);


  2. register_chrdev_regin(...);


  3. cdev_add(...);


它们的功能都是向系统注册字符设备,但是只从函数名上看,初学者谁能分得清它们的区别?!


这也难怪,Linux系统经过这么多年的发展,代码更新是很正常的事情。


但是,我们参考的文章就没法做到:很详细的把文章中所描述内容的背景介绍清楚,往往都是文章作者在自己的实际工作环境中,测试某种方法解决了自己的问题,于是就记录成文。


不同的文章、不同的工作上下文、不同的API函数调用,这往往就苦了我们初学者,特别是我这种有选择障碍症的人!


其实,上面这个几个函数都是正确的,它们的功能都是类似的,它们是Linux系统中不同阶段的产物。


旧的 API 函数

在Linux内核代码2.4版本和早期的2.6版本中,注册、卸载字符设备驱动程序的经典方式是:


注册设备:


int register_chrdev(unsigned int major,const char *name,struct file_operations *fops);
参数1 major:如果为0 - 由操作系统动态分配一个主设备号给这个设备;如果非0 - 驱动程序向系统申请,使用这个主设备号;


参数2 name:设备名称;


参数3 fops:file_operations 类型的指针变量,用于操作设备;


如果是动态分配,那么这个函数的返回值就是:操作系统动态分配给这个设备的主设备号。


这个动态分配的设备号,我们要把它记住,因为在其他的API函数中需要使用它。


卸载设备:


int unregister_chrdev(unsigned int major,const char *name)
参数1 major:设备的主设备号,也就是 register_chrdev() 函数的返回值(动态),或者驱动程序指定的设备号(静态方式);


参数2 name:设备名称;


新的 API 函数

注册设备:


int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
上面这2个注册设备的函数,其实对应着旧的 API 函数 register_chrdev:把参数 1 表示的动态分配、静态分配,拆分成2个函数而已。


也就是说:


register_chrdev_region(): 静态注册设备;


alloc_chrdev_region(): 动态注册设备;


这两个函数的参数含义是:


register_chrdev_region参数:


参数1 from: 注册指定的设备号,这是静态指定的,例如:MKDEV(200, 0) 表示起始主设备号 200, 起始次设备号为 0;


参数2 count: 驱动程序指定连续注册的次设备号的个数,例如:起始次设备号是 0,count 为 10,表示驱动程序将会使用 0 ~ 9 这 10 个次设备号;


参数3 name:设备名称;


alloc_chrdev_region参数:


参数1 dev: 动态注册就是系统来分配设备号,那么驱动程序就要提供一个指针变量来接收系统分配的结果(设备号);


参数2 baseminor: 驱动程序指定此设备号的起始值;


参数3 count: 驱动程序指定连续注册的次设备号的个数,例如:起始次设备号是 0,count 为 10,表示驱动程序将会使用 0 ~ 9 这 10 个次设备号;


参数4 name:设备名称;


补充一下关于设备号的内容:


这里的结构体dev_t,用来保存设备号,包括设备号和设备号。


它本质上是一个32位的数,其中的12位用来表示设备号,而其余20位用来表示设备号。


系统中定义了3宏,来实现dev_t变量、主设备号、次设备号之间的转换:


MAJOR(dev_t dev): 从  dev_t 类型中获取主设备号;


MINOR(dev_t dev):  从 dev_t 类型中获取次设备号;


MKDEV(int major,int minor): 把主设备号和次设备号转换为 dev_t 类型;


卸载设备:


void unregister_chrdev_region(dev_t from, unsigned count);
参数1 from: 注销的设备号;


参数2 count: 注销的连续次设备号的个数;


代码实操

下面,我们就用旧的API函数,一步一步的描述字符设备驱动程序的:编写、加载和卸载过程


如何使用新的API函数来编写字符设备驱动程序,下一篇文章再详细讨论。


以下所有操作的工作目录,都是与上一篇文章相同的,即:~/tmp/linux-4.15/drivers/。


创建驱动目录和驱动程序

$ cd linux-4.15/drivers/
$ mkdir my_driver1
$ cd my_driver1
$ touch driver1.c
driver1.c文件的内容如下(不需要手敲,文末有代码下载链接):


#include

  • #include

  • #include

  • #include

  • #include

  • #include

  • #include

  • #include

  • #include

  • #include


  • static unsigned int major;

    int driver1_open(struct inode *inode, struct file *file)
    {
    printk("driver1_open is called. \n");
    return 0;
    }

    ssize_t driver1_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    {
    printk("driver1_read is called. \n");
    return 0;
    }

    ssize_t driver1_write (struct file *file, const char __user *buf, size_t size, loff_t *ppos)
    {
    printk("driver1_write is called. \n");
    return 0;
    }

    static const struct file_operations driver1_ops={
    .owner = THIS_MODULE,
    .open = driver1_open,
    .read = driver1_read,
    .write = driver1_write,
    };

    static int __init driver1_init(void)
    {
    printk("driver1_init is called. \n");

    major = register_chrdev(0, "driver1",
  • 本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
    换一批
    延伸阅读

    USB摄像头是一种采用USB接口的视频采集设备,其优点在于即插即用、操作简便,无需额外驱动程序,支持笔记本电脑,并且成本较低,可以支持远程网络观看。

    关键字: usb摄像头 驱动程序

    与两相双极步进电机的驱动电路相比,两相单极步进电机的驱动电路在输入段配置、内部逻辑及控制电路和驱动电路使用双通道方面基本相同,但是输出段的配置不同。

    关键字: 四相步进电机 驱动程序 程序电路

    本文介绍了如何实现嵌入式MICREL网卡的驱动程序开发和设计。首先,我们介绍了MICREL网卡的概述和工作原理。然后,详细探讨了驱动程序的开发流程,包括硬件和软件的配置以及驱动程序的编写和测试。最后,总结了几点注意事项和...

    关键字: 嵌入式 MICREL网卡 驱动程序

    在这篇文章中,小编将对OLED的相关内容和情况加以介绍以帮助大家增进对它的了解程度,和小编一起来阅读以下内容吧。

    关键字: OLED 驱动程序 无源驱动

    近日,英特尔发布了锐炫显卡的新版驱动更新。本次驱动更新涵盖了锐炫A770、A750、A380以及移动端的锐炫GPU,这使得英特尔锐炫整个家族的DX9性能都实现了显著提升。

    关键字: 英特尔 显卡 驱动程序

    摘 要:从硬件与软件方面介绍了基于PXI技术的1553B总线通讯模块的设计,并对PXI总线接口设计、驱动程序的开发、 SDRAM存储器的控制和1553B总线通信协议实现等关键技术进行了详细的阐述,为航空领域测控系统开发P...

    关键字: PXI技术 驱动程序 SDRAM存储器 1553B总线

    PnP全称Plug-and-Play,译文为即插即用。PnP的作用是自动配置低层计算机中的板卡和其他设备,然后告诉对应设备都做了什么。PnP的任务是把物理设备和软件设备驱动程序相配合,并操作设备,在每个设备和它的驱动程序...

    关键字: PnP 驱动程序 操作设备

    作 者:道哥,10年嵌入式开发老兵,专注于:C/C、嵌入式、Linux。关注下方公众号,回复【书籍】,获取Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章(PDF格式)。目录kill命令和信号使用kill...

    关键字: 信号 应用程序 驱动程序

    驱动程序本质上是软件代码,主要作用是计算机系统与硬件设备之间完成数据传送的功能,只有借助驱动程序,两者才能通信并完成特定的功能。

    关键字: 驱动程序 硬件设备 UNIX

    驱动程序(Device Driver)全称为“设备驱动程序”,是一种可以使计算机和设备通信的特殊程序,可以说相当于硬件的接口,操作系统只能通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常...

    关键字: 驱动程序 声卡 设备
    关闭
    关闭