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


推荐阅读
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 实现一个通讯录系统,可添加、删除、修改、查找、显示、清空、排序通讯录信息
    本文介绍了如何实现一个通讯录系统,该系统可以实现添加、删除、修改、查找、显示、清空、排序通讯录信息的功能。通过定义结构体LINK和PEOPLE来存储通讯录信息,使用相关函数来实现各项功能。详细介绍了每个功能的实现方法。 ... [详细]
  • 1.      准备工作: 程序:MinGW-3.1.0-1.exe     windows下的gcc,编译c语言的工具下载地址: http:umn.dl.sourceforge. ... [详细]
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社区 版权所有