热门标签 | 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


推荐阅读
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 本文深入探讨了C++对象模型中的一些细节问题,特别是虚拟继承和析构函数的处理。通过具体代码示例和详细分析,揭示了书中某些观点的不足之处,并提供了更合理的解释。 ... [详细]
  • 在多线程编程环境中,线程之间共享全局变量可能导致数据竞争和不一致性。为了解决这一问题,Linux提供了线程局部存储(TLS),使每个线程可以拥有独立的变量副本,确保线程间的数据隔离与安全。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入理解Redis的数据结构与对象系统
    本文详细探讨了Redis中的数据结构和对象系统的实现,包括字符串、列表、集合、哈希表和有序集合等五种核心对象类型,以及它们所使用的底层数据结构。通过分析源码和相关文献,帮助读者更好地理解Redis的设计原理。 ... [详细]
  • 本题旨在通过给定的评级信息,利用拓扑排序和并查集算法来确定全球 Tetris 高手排行榜。题目要求判断是否可以根据提供的信息生成一个明确的排名表,或者是否存在冲突或信息不足的情况。 ... [详细]
  • 本文介绍如何在Ubuntu环境下为OpenWrt系统构建并安装首个'Hello World'应用程序的IPK包。文章不仅涵盖了基本的环境搭建,还详细说明了代码编写、Makefile配置及最终的IPK包生成与安装过程。 ... [详细]
  • 本文详细介绍了如何通过修改Lua源码或使用动态链接库(DLL)的方式实现Lua与C++之间的高级交互,包括如何编译Lua源码、添加自定义API以及在C++中加载和调用Lua脚本。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 使用Vultr云服务器和Namesilo域名搭建个人网站
    本文详细介绍了如何通过Vultr云服务器和Namesilo域名搭建一个功能齐全的个人网站,包括购买、配置服务器以及绑定域名的具体步骤。文章还提供了详细的命令行操作指南,帮助读者顺利完成建站过程。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 本实验主要探讨了二叉排序树(BST)的基本操作,包括创建、查找和删除节点。通过具体实例和代码实现,详细介绍了如何使用递归和非递归方法进行关键字查找,并展示了删除特定节点后的树结构变化。 ... [详细]
  • 本文详细介绍了C语言中链表的两种动态创建方法——头插法和尾插法,包括具体的实现代码和运行示例。通过这些内容,读者可以更好地理解和掌握链表的基本操作。 ... [详细]
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社区 版权所有