热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

嵌入式Linux设备驱动程序开发指南8(字符设备驱动)——读书笔记

字符设备驱动八、字符设备驱动8.1概述8.2helloworld字符设备模块8.2.1helloworld_char_driver.c代码8.2.2Makefile8.2.3ioc


字符设备驱动

  • 八、字符设备驱动
    • 8.1 概述
    • 8.2 helloworld字符设备模块
      • 8.2.1 helloworld_char_driver.c代码
      • 8.2.2 Makefile
      • 8.2.3 ioctl_char_test.c
    • 8.3 将模块添加到内核
    • 8.4 将模块添加到内核构建
    • 8.5 class字符设备
      • 8.5.1 使用设备文件系统创建设备文件
      • 8.5.2 class字符设备模块
    • 8.6 杂项字符设备


八、字符设备驱动


8.1 概述

操作系统被设计为了对设备使用者隐藏底层硬件细节。Linux需要一种能够将数据从内核态传递给用户态的机制。这种传递通过设备节点来处理,即虚拟文件(/dev/xxx)。当用户获取设备节点时候,内核将底层数据流拷贝到应用程序的内存空间,当用户写设备节点时候,内核将应用程序提供的数据拷贝到数据缓存区,这些数据最终送到底层硬件,这些虚拟文件可以被用户应用程序通过标准的系统调用打开、读取、写入。
Linux应用程序分为三类:字符设备、块设备、网络设备

字符设备:
是常见的设备,这种设备的读写是直接进行,无需缓冲区,如键盘、鼠标、LED灯、显示器、串口、打印机等;
块设备:
读写以块大小为单位,一次读写整数倍的块大小,如512或者1024字节。
网络设备:
通过BSD套接字接口和网络子系统来访问。

从应用的角度看,一个字符设备本质上是一个文件。通过file_operations数据结构来描述的各种操作并注册。其中,file_operations定义在

include/linux.fs.h

其中一种从用户态和内核态实现数据交互的函数是:

copy_from_user()
copy_to_user()

Linux设备读写返回值如果是负数,则表示出错。出错码定义在

linux/errno.h

Linux设备通过两个设备号来标识:

主设备号
从设备号

8.2 helloworld字符设备模块

准备工作:
Linux设备/dev下有很多文件,不管以后你就设备是否存在,软件都会生成对应文件。创建/dev/xxx方式总共有:

静态设备创建;
使用设备文件系统和杂项框架;

静态设备创建,一般是由MAKEDEV(mknod程序)完成,也可以手动创建如:

mknod /dev/mydev c 202 108 //备注:主设备号202 从设备号108

本文将通过ioctl_test应用程序和helloworld驱动调试。
在内核中,cdev数据结构表示一个字符类型的设备,该数据结构用于将设备注册到系统中。
字符设备的注册/注销是通过主从设备号来实现。dev_t用来保存设备的标识信息(主从设备号),主从设备号通过MKDEV宏来获取.
静态分配和释放一个设备标识:

#include register_chrdev_region()
unregister_chrdev_region()

动态分配和释放一个设备标识:
分配一组字符设备编号,

alloc_chrdev_region()

分配完字符设备之后,
初始化字符设备:

cdev_init()

字符设备注册到内核:

cdev_add()

通过

/proc/devices

8.2.1 helloworld_char_driver.c代码

#include /* add header files to support character devices */
#include
#include /* define mayor number */
#define MY_MAJOR_NUM 202 //主设备号static struct cdev my_dev;static int my_dev_open(struct inode *inode, struct file *file)
{pr_info("my_dev_open() is called.\n");return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("my_dev_close() is called.\n");return 0;
}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);return 0;
}/* declare a file_operations structure */
//创建一个my_dev_fops的文件file_operations数据结构体
//定义:
// *打开
// *读取
// *写入
static const struct file_operations my_dev_fops = {.owner = THIS_MODULE,.open = my_dev_open,.release = my_dev_close,.unlocked_ioctl = my_dev_ioctl,
};static int __init hello_init(void)
{int ret;dev_t dev &#61; MKDEV(MY_MAJOR_NUM, 0);pr_info("Hello world init\n");/* Allocate device numbers */ret &#61; register_chrdev_region(dev, 1, "my_char_device");if (ret < 0){pr_info("Unable to allocate mayor number %d\n", MY_MAJOR_NUM);return ret;}/* Initialize the cdev structure and add it to the kernel space */cdev_init(&my_dev, &my_dev_fops);ret&#61; cdev_add(&my_dev, dev, 1);if (ret < 0){unregister_chrdev_region(dev, 1);pr_info("Unable to add cdev\n");return ret;}return 0;
}static void __exit hello_exit(void)
{pr_info("Hello world exit\n");cdev_del(&my_dev);unregister_chrdev_region(MKDEV(MY_MAJOR_NUM, 0), 1);
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");

8.2.2 Makefile

obj-m &#43;&#61; helloworld_sam_char_driver.oKERNEL_DIR ?&#61; $(HOME)/my-linux-samall:make -C $(KERNEL_DIR) \ARCH&#61;arm CROSS_COMPILE&#61;arm-poky-linux-gnueabi- \SUBDIRS&#61;$(PWD) modulesclean:make -C $(KERNEL_DIR) \ARCH&#61;arm CROSS_COMPILE&#61;arm-poky-linux-gnueabi- \SUBDIRS&#61;$(PWD) cleandeploy:scp *.ko root&#64;10.0.0.10:

8.2.3 ioctl_char_test.c

/** A simple application to call helloworld character driver&#39;s ioctl.**/
#include
#include
#include
#include int main(void)
{/* First you need run "mknod /dev/mydev c 202 0" to create /dev/mydev */int my_dev &#61; open("/dev/mydev", 0);if (my_dev < 0) {perror("Fail to open device file: /dev/mydev.");} else {ioctl(my_dev, 100, 110); // cmd &#61; 100, arg &#61; 110.close(my_dev);}return 0;
}

8.3 将模块添加到内核

insmod helloworld_char_driver.ko
cat /proc/devices //查看申请的主设备号202 "my_char_device"
ls -l /dev
mknod /dev/mydev c 202 0 //在/dev下&#xff0c;创建mydev
./ioctl_test
rmmod helloworld_char_driver.ko

8.4 将模块添加到内核构建

驱动构建成为可加载的内核模块。将驱动作为内核代码树的一部分构建到内核的二进制镜像中。在内核目录新增helloworld_char_driver.c&#xff1a;

/drivers/char/

编辑~/my-linux-imx/drivers/char/Kconfig&#xff0c;并在末尾添加&#xff1a;

config HELLOWORLDtristate "My helloworld driver"default nhelpThe simplest driver.

编辑~/my-linux-imx/drivers/char/Makefile&#xff0c;并在末尾添加&#xff1a;

obj-$(CONFIG_HELLOWORLD) &#43;&#61; helloworld_char_driver.o

修改完Kconfig和Makefile之后&#xff0c;helloworld_char_driver将成为内核的一部分&#xff0c;而不是一个可加载模块。
接下来是构建内核镜像步骤&#xff1a;
打开menuconfig窗口&#xff0c;选择main munu -> Device Driver -> Character devices -> My simple helloworld driver 选择*&#xff0c;然后保存退出GUI.
检查&#xff1a;

make menuconfig ARCH&#61;arm

打开内核根目录的.config文件将看到CONFIG_HELLOWORLD符号以及被添加进去了。
编译新镜像&#xff1a;

source /opt/fsl-imx-x11/4.9.11-1.0.0/environment-setup-cortexa7hf
make zImage
cp /arch/arm/boot/zImage /var/lib/tftpboot/

测试调试&#xff0c;启动系统后查看&#xff1a;

cat /proc/devices
mknod /dev/mydev c 202 0 //申请主设备号
./ioctl_test

8.5 class字符设备


8.5.1 使用设备文件系统创建设备文件

在Linux2.6之后&#xff0c;增加了sysfs即虚拟文件系统。sysfs的任务是方便用户态使用查看系统硬件。sysfs是通过Linux内核配置CONFIG_SYSFS来打开并准备的。内核通过设备文件系统创建设备之后&#xff0c;会给udevd发送一个uevent.


8.5.2 class字符设备模块

在/sys/class目录下&#xff0c;有一个类别名&#xff0c;而每个设备都有一个设备名。
当驱动register_chrdev_region()函数执行之后&#xff0c;并没有在/class/sys/生成设备文件&#xff0c;而是使用如下&#xff1a;
驱动使用内核API创建/销毁类别&#xff1a;

#include class_create() //在/sys/class/目录下创建自己的类别名&#xff0c;如&#xff1a;/sys/class/hello_class
class_destroy()

创建设备节点内核API&#xff1a;

#include device_create() //在/dev下创建对应device node
device_destory()

helloworld_class_driver.c代码&#xff1a;


#include
#include
#include
#include #define DEVICE_NAME "mydev"
#define CLASS_NAME "hello_class"static struct class* helloClass;
static struct cdev my_dev;
dev_t dev;static int my_dev_open(struct inode *inode, struct file *file)
{pr_info("my_dev_open() is called.\n");return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("my_dev_close() is called.\n");return 0;
}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{pr_info("my_dev_ioctl() is called. cmd &#61; %d, arg &#61; %ld\n", cmd, arg);return 0;
}/* declare a file_operations structure */
static const struct file_operations my_dev_fops &#61; {.owner &#61; THIS_MODULE,.open &#61; my_dev_open,.release &#61; my_dev_close,.unlocked_ioctl &#61; my_dev_ioctl,
};static int __init hello_init(void)
{int ret;dev_t dev_no;int Major;struct device* helloDevice;pr_info("Hello world init\n");/* Allocate dynamically device numbers */ret &#61; alloc_chrdev_region(&dev_no, 0, 1, DEVICE_NAME);if (ret < 0){pr_info("Unable to allocate Mayor number \n");return ret;}/* Get the device identifiers */Major &#61; MAJOR(dev_no);dev &#61; MKDEV(Major,0);pr_info("Allocated correctly with major number %d\n", Major);/* Initialize the cdev structure and add it to the kernel space */cdev_init(&my_dev, &my_dev_fops);ret &#61; cdev_add(&my_dev, dev, 1);if (ret < 0){unregister_chrdev_region(dev, 1);pr_info("Unable to add cdev\n");return ret;}/* Register the device class */helloClass &#61; class_create(THIS_MODULE, CLASS_NAME);if (IS_ERR(helloClass)){unregister_chrdev_region(dev, 1);cdev_del(&my_dev);pr_info("Failed to register device class\n");return PTR_ERR(helloClass);}pr_info("device class registered correctly\n");/* Create a device node named DEVICE_NAME associated a dev */helloDevice &#61; device_create(helloClass, NULL, dev, NULL, DEVICE_NAME);if (IS_ERR(helloDevice)){class_destroy(helloClass);cdev_del(&my_dev);unregister_chrdev_region(dev, 1);pr_info("Failed to create the device\n");return PTR_ERR(helloDevice);}pr_info("The device is created correctly\n");return 0;
}static void __exit hello_exit(void)
{device_destroy(helloClass, dev); /* remove the device */class_destroy(helloClass); /* remove the device class */cdev_del(&my_dev);unregister_chrdev_region(dev, 1); /* unregister the device numbers */pr_info("Hello world with parameter exit\n");
}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");

测试调试&#xff1a;

insmod helloworld_class_driver.ko //加载模块
ls /sys/class/
ls /sys/class/hello_class/my_dev
ls -l /dev
./ioctl_test
rmmod helloworld_clsss_driver.ko

8.6 杂项字符设备

Linux允许模块注册其各自的从设备编号。官方分配给杂项驱动的主设备编号是10。
杂项设备文件创建目录如下&#xff1a;

/sys/class/misc

注册杂项设备编号&#xff1a;

include/linux/miscdevice.h

杂项驱动导出的两个函数&#xff1a;

#include
misc_register()
misc_deregister()

misc_sam_driver.c代码&#xff1a;

#include
#include
#include static int my_dev_open(struct inode *inode, struct file *file)
{pr_info("my_dev_open() is called.\n");return 0;
}static int my_dev_close(struct inode *inode, struct file *file)
{pr_info("my_dev_close() is called.\n");return 0;
}static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{pr_info("my_dev_ioctl() is called. cmd &#61; %d, arg &#61; %ld\n", cmd, arg);return 0;
}static const struct file_operations my_dev_fops &#61; {.owner &#61; THIS_MODULE,.open &#61; my_dev_open,.release &#61; my_dev_close,.unlocked_ioctl &#61; my_dev_ioctl,
};/* declare & initialize struct miscdevice */
static struct miscdevice helloworld_miscdevice &#61; {.minor &#61; MISC_DYNAMIC_MINOR,.name &#61; "mydev",.fops &#61; &my_dev_fops,
};static int __init hello_init(void)
{int ret_val;pr_info("Hello world init\n");/* Register the device with the Kernel */ret_val &#61; misc_register(&helloworld_miscdevice);if (ret_val !&#61; 0) {pr_err("could not register the misc device mydev");return ret_val;}pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);return 0;
}static void __exit hello_exit(void)
{pr_info("Hello world exit\n");/* unregister the device with the Kernel */misc_deregister(&helloworld_miscdevice);}module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is the helloworld_char_driver using misc framework");

测试调试&#xff1a;

insmod misc_sam_driver.ko
ls /sys/class/misc
ls /sys/class/misc/mydev/
ls /sys/class/misc/mydev/dev
./ioctl_test
rmmod misc_sam_driver.ko

感谢阅读&#xff0c;祝君成功&#xff01;
-by aiziyou


推荐阅读
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 本文探讨了在C++中如何有效地清空输入缓冲区,确保程序只处理最近的输入并丢弃多余的输入。我们将介绍一种不阻塞的方法,并提供一个具体的实现方案。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 本文详细介绍了如何在 Objective-C 中使用 @public 和 @protected 修饰符来控制类成员的访问权限。同时,探讨了点语法和箭头操作符的区别,以及属性声明和实现的自动生成。 ... [详细]
  • 树链问题的优化解法:深度优先搜索与质因数分解
    本文介绍了一种通过深度优先搜索(DFS)和质因数分解来解决最长树链问题的方法。我们通过枚举树链上的最大公约数(GCD),将所有节点按其质因子分类,并计算每个类别的最长链,最终求得全局最长链。 ... [详细]
  • 问题描述:通过添加最少数量的括号,使得给定的括号序列变为合法,并输出最终的合法序列。数据范围:字符串长度不超过100。涉及算法:区间动态规划(Interval DP)。 ... [详细]
  • Python处理Word文档的高效技巧
    本文详细介绍了如何使用Python处理Word文档,涵盖从基础操作到高级功能的各种技巧。我们将探讨如何生成文档、定义样式、提取表格数据以及处理超链接和图片等内容。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • Spring Boot单元测试中Redis连接失败的解决方案
    本文探讨了在Spring Boot项目中进行单元测试时遇到Redis连接问题的原因及解决方法,详细分析了配置文件加载路径不当导致的问题,并提供了有效的解决方案。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
author-avatar
Only-安之若素
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有