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


推荐阅读
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 文件描述符、文件句柄与打开文件之间的关联解析
    本文详细探讨了文件描述符、文件句柄和打开文件之间的关系,通过具体示例解释了它们在操作系统中的作用及其相互影响。 ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • MySQL 数据库迁移指南:从本地到远程及磁盘间迁移
    本文详细介绍了如何在不同场景下进行 MySQL 数据库的迁移,包括从一个硬盘迁移到另一个硬盘、从一台计算机迁移到另一台计算机,以及解决迁移过程中可能遇到的问题。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • C++: 实现基于类的四面体体积计算
    本文介绍如何使用C++编程语言,通过定义类和方法来计算由四个三维坐标点构成的四面体体积。文中详细解释了四面体体积的数学公式,并提供了两种不同的实现方式。 ... [详细]
  • 本文深入探讨了如何通过调整InnoDB的关键配置参数来优化MySQL的随机IO性能,涵盖了缓存、日志文件、预读机制等多个方面,帮助读者全面提升数据库系统的性能。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本实验主要探讨了二叉排序树(BST)的基本操作,包括创建、查找和删除节点。通过具体实例和代码实现,详细介绍了如何使用递归和非递归方法进行关键字查找,并展示了删除特定节点后的树结构变化。 ... [详细]
  • C++构造函数与初始化列表详解
    本文深入探讨了C++中构造函数的初始化列表,包括赋值与初始化的区别、初始化列表的使用规则、静态成员初始化等内容。通过实例和调试证明,详细解释了初始化列表在对象创建时的重要性。 ... [详细]
  • 本文详细介绍了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社区 版权所有