linux设备驱动--字符设备模型
linux设备驱动--字符设备模型
最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步。
作者:liufei_learning(转载请注明出处)
email:flying0216@foxmail.com
IT学习交流群:160855096
开发环境:Win7(主机)+ VisualBox + ubuntu10.10(虚拟机) + TQ2440开发板(2.6.30.4内核)
功能: 1.接linux设备驱动--LED驱动 用cdev实现led驱动的编写
2.学习字符设备模型(转载)
目录: 1.cdev实现led驱动的编写
1)实现及源码
2)cdev结构分析
2.学习字符设备模型
1)字符设备模型
2)字符设备的设备号
3)文件系统中对字符设备文件的访问
用cdev实现led驱动的编写
在上一节代码基础上修改的,详细看linux设备驱动--LED驱动
/** tq2440_leds.c** Created on: 2011-11-29* Author: liufei_learning**/#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/kdev_t.h>#define DEVICE_NAME "tq2440_leds" //设备名称
#define LED_MAJOR 231 //主设备号
#define IOCTL_LED_ON 1 //LED亮状态
#define IOCTL_LED_OFF 0 //LED灭状态static led_major = LED_MAJOR;
struct cdev led_cdev;
//控制LED的IO口
static unsigned long led_table[] =
{S3C2410_GPB5,S3C2410_GPB6,S3C2410_GPB7,S3C2410_GPB8,
};//LED IO口的模式
static unsigned int led_cfg_table[] =
{S3C2410_GPB5_OUTP,S3C2410_GPB6_OUTP,S3C2410_GPB7_OUTP,S3C2410_GPB8_OUTP,
};static struct class *leds_class;static int __init leds_open(struct inode *inode, struct file *file)
{return 0;
}static int __init leds_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{//检测是第几个LED,因开发板上只有4个,索引从0开始if(arg < 0 || arg > 3){return -EINVAL;}//判断LED要执行哪种状态switch(cmd){case IOCTL_LED_ON:{s3c2410_gpio_setpin(led_table[arg], ~(IOCTL_LED_ON));break;}case IOCTL_LED_OFF:{s3c2410_gpio_setpin(led_table[arg], ~(IOCTL_LED_OFF));break;}default:{return -EINVAL;}}return 0;
}static struct file_operations led_fops =
{.owner = THIS_MODULE,.open = leds_open,.ioctl = leds_ioctl,
};static int __init leds_init(void)
{int ret;int i;for(i = 0; i < 4; i++){//初始化各IO口为输出模式s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);//由原理图可知LED电路是共阳极的(即各IO口输出低电平0才会点亮)//这里初始化为1,不让LED点亮s3c2410_gpio_setpin(led_table[i], ~(IOCTL_LED_OFF));} dev_t devno = MKDEV(led_major, 0);/* 静态申请设备号*/if (led_major)ret = register_chrdev_region(devno, 2, DEVICE_NAME);else /* 动态分配设备号 */{ret = alloc_chrdev_region(&devno, 0, 2, DEVICE_NAME);led_major = MAJOR(devno);} if (ret < 0)return ret;/*初始化cdev结构*/cdev_init(&led_cdev, &led_fops);led_cdev.owner = THIS_MODULE;led_cdev.ops = &led_fops;/* 注册字符设备 */cdev_add(&led_cdev, MKDEV(led_major, 0), 1);//注册一个类,使mdev可以在/dev/下面建立设备节点leds_class = class_create(THIS_MODULE, DEVICE_NAME);if( IS_ERR(leds_class) ){printk("creat leds_class failed!");return -1;}//创建一个设备节点,节点名字为DEVICE_NAMEdevice_create(leds_class, NULL, MKDEV(led_major, 0), NULL, DEVICE_NAME);printk(DEVICE_NAME "initialized!");return 0;
}static void __init leds_exit(void)
{cdev_del(&led_cdev); /*注销设备*/unregister_chrdev_region(MKDEV(led_major, 0), 2); /*释放设备号*///删除设备节点device_destroy(leds_class, MKDEV(led_major, 0));//注销类class_destroy(leds_class);
}module_init( leds_init);
module_exit( leds_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("liufei_learning");MODULE_DESCRIPTION("tq2440 leds driver");
分析:
Linux/cdev.h
在Linux内核中使用cdev结构体描述字符设备。该结构体是所有字符设备的抽象,其包含了大量字符设备所共有的特性。cdev结构体定义如下:
struct cdev {struct kobject kobj; /*内嵌的kobject结构,用于内核设备驱动模型的管理*/struct module *owner; /*指向包含该结构的模块的指针,用于引用计数*/const struct file_operations *ops; /*指向字符设备操作函数集的指针*/struct list_head list; /*该结构将使用该驱动的字符设备连接成一个链表*/dev_t dev; /*该字符设备的起始设备号,一个设备可能有多个设备号*/unsigned int count; /*使用该字符设备驱动的设备数量*/
};
cdev结构中的kobj结构用于内核管理字符设备,驱动开发人员一般不使用该成员。ops是指向file_operations结构的指针,该结构定义了操作字符设备的函数。dev就是用来存储字符设备所申请的设备号。count表示目前有多少个字符设备在使用该驱动程序。当使用rmmod卸载模块时,如果count成员不为0,那么系统不允许卸载模块。
list结构是一个双向链表,用于将其他结构体连接成一个双向链表。structlist_head {
struct list_head *next, *prev;
};
cdev结构体的list成员连接到了inode结构体i_devices成员。其中i_devices也是一个list_head结构。这样,使cdev结构与inode结点组成了一个双向链表。inode结构体表示/dev目录下的设备文件。
每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应的字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点。这样可以通过inode结点的i_cdev字段找到cdev字符结构体。通过cdev的ops指针,就能找到设备A的操作函数。
可以使用如下宏调用来获得主、次设备号:
MAJOR(dev_t dev)
MINOR(dev_t dev)
一个 cdev一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdevmy_cdev;
cdev_init(&my_cdev,&fops);
my_cdev.owner= THIS_MODULE;
动态内存定义初始化:
struct cdev*my_cdev = cdev_alloc();
my_cdev->ops= &fops;
my_cdev->owner= THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
下面贴出了两个函数的代码,以具体看一下它们之间的差异。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)530{531 memset(cdev, 0, sizeof *cdev);532 INIT_LIST_HEAD(&cdev->list);533 kobject_init(&cdev->kobj, &ktype_cdev_default);534 cdev->ops = fops;535}struct cdev*cdev_alloc(void)512{513 struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);514 if (p) {515 INIT_LIST_HEAD(&p->list);516 kobject_init(&p->kobj,&ktype_cdev_dynamic);517 }518 return p;519}
由此可见,两个函数完成都功能基本一致,只是cdev_init() 还多赋了一个 cdev->ops 的值。
初始化 cdev后,需要把它添加到系统中去。为此可以调用 cdev_add() 函数。传入 cdev 结构的指针,起始设备编号,以及设备编号范围。
intcdev_add(struct cdev *p, dev_t dev, unsigned count)
{p->dev = dev;p->count = count;return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
关于 kobj_map()函数就不展开了,我只是大致讲一下它的原理。内核中所有都字符设备都会记录在一个 kobj_map 结构的 cdev_map变量中。这个结构的变量中包含一个散列表用来快速存取所有的对象。kobj_map() 函数就是用来把字符设备编号和 cdev 结构变量一起保存到cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用 kobj_lookup() 函数,根据设备编号就可以找到 cdev结构变量,从而取出其中的 ops 字段。
当一个字符设备驱动不再需要的时候(比如模块卸载),就可以用cdev_del() 函数来释放 cdev 占用的内存。
voidcdev_del(struct cdev *p)
{cdev_unmap(p->dev, p->count);kobject_put(&p->kobj);
}
其中cdev_unmap() 调用 kobj_unmap() 来释放 cdev_map 散列表中的对象。kobject_put() 释放 cdev 结构本身。
学习字符设备模型
基础数据结构
structcdev {struct kobject kobj; /*内嵌的kobject结构,用于内核设备驱动模型的管理*/struct module *owner; /*指向包含该结构的模块的指针,用于引用计数*/const struct file_operations *ops; /*指向字符设备操作函数集的指针*/struct list_head list; /*该结构将使用该驱动的字符设备连接成一个链表*/dev_t dev; /*该字符设备的起始设备号,一个设备可能有多个设备号*/unsigned int count; /*使用该字符设备驱动的设备数量*/
};
structkobj_map {20 struct probe {21 struct probe *next;22 dev_t dev;23 unsigned long range;24 struct module *owner;25 kobj_probe_t *get;26 int (*lock)(dev_t, void *);27 void *data;28 } *probes[255];29 struct mutex *lock;30};staticstruct char_device_struct {53 struct char_device_struct *next;54 unsigned int major;55 unsigned int baseminor;56 int minorct;57 char name[64];58 struct cdev *cdev; /*will die */59} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
#defineCHRDEV_MAJOR_HASH_SIZE 255
字符设备模型
每个字符驱动由一个 cdev 结构来表示.
在设备驱动模型(device driver model)中, 使用 (kobject mapping domain)来记录字符设备驱动.
这是由 struct kobj_map 结构来表示的. 它内嵌了255个struct probe指针数组
kobj_map由全局变量 cdev_map 引用: static struct kobj_map *cdev_map;
相关函数说明:
cdev_alloc() 用来创建一个cdev的对象
cdev_add() 用来将cdev对象添加到驱动模型中,其主要是通过kobj_map()来实现的.
kobj_map() 会创建一个probe对象,然后将其插入cdev_map中的某一项中,并关联probe->data 指向cdev
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev,int *index)
根据设备号,在cdev_map中查找其cdev对象内嵌的kobject.(probe->data->kobj),返回的是cdev的kobject
2. 字符设备的设备号
字符设备的主,次设备号的分配:
全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 structchar_device_struct的元素.
每一个对应一个相应的主设备号.
如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中.
这样,通过chrdevs数组,我们就可以知道分配了哪些设备号.
相关函数:
register_chrdev_region( ) 分配指定的设备号范围
alloc_chrdev_region( ) 动态分配设备范围
他们都主要是通过调用函数__register_chrdev_region() 来实现的
要注意,这两个函数仅仅是注册设备号! 如果要和cdev关联起来,还要调用cdev_add()
register_chrdev( ) 申请指定的设备号,并且将其注册到字符设备驱动模型中.
它所做的事情为:
1. 注册设备号, 通过调用 __register_chrdev_region() 来实现
2. 分配一个cdev, 通过调用 cdev_alloc() 来实现
3. 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现
4. 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev.由于register_chrdev()是老的接口,这一步在新的接口中并不需要.
3. 文件系统中对字符设备文件的访问
对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL,则说明该设备文件没有被打开.
由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表
首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open.
(最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open.这一系列的调用过程,本文暂不讨论)
int chrdev_open(struct inode * inode, struct file * filp)
chrdev_open()所做的事情可以概括如下:
1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup()来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.
2. 设置inode->i_cdev , 指向找到的cdev.
3. 将inode添加到cdev->list的链表中.
4. 使用cdev的ops 设置file对象的f_op
5. 如果ops中定义了open方法,则调用该open方法
6. 返回.
执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read,write等操作,就会执行cdev的相应操作.
此处3个分析转载自http://blog.csdn.net/cuijianzhongswust/article/details/6887993
linux设备驱动--字符设备模型相关推荐
- 【驱动】linux设备驱动·字符设备驱动开发
Preface 前面对linux设备驱动的相应知识点进行了总结,现在进入实践阶段! <linux设备驱动入门篇>:http://infohacker.blog.51cto.com/6751 ...
- 《Linux内核剖析》(Yanlz+VR云游戏+Unity+SteamVR+云技术+5G+AI+Makefile+块设备驱动+字符设备驱动+数学协处理器+文件系统+内存管理+GDB+立钻哥哥+==)
<Linux内核剖析> <Linux内核剖析> 版本 作者 参与者 完成日期 备注 YanlzLinux_Kernel0.12_V01_1.0 严立钻 2020.02.06 # ...
- 字符设备驱动、平台设备驱动、设备驱动模型、sysfs的比较和关联
参考原文:https://www.kancloud.cn/yueqian_scut/emlinux/106829 对原文笔误地方做了修改.重新排版 目录 字符设备驱动.平台设备驱动.设备驱动模型.sy ...
- LINUX设备驱动之设备模型一--kobject
http://blog.csdn.net/yangzhu1982/article/details/6186016 Linux设备驱动之设备模型一kobject Eric Fang 2010-01-1 ...
- LINUX设备驱动之设备模型一kobject
LINUX设备驱动之设备模型一kobject -------------------------------------------------------------- 转载请注明出处:http:/ ...
- Linux驱动-字符设备驱动
Linux驱动-字符设备驱动 前言 一.预备知识 1.file_operations结构体 2.地址映射 二.涉及的API函数 1.字符设备驱动 1.1.设备号 1.1.1.register_chrd ...
- linux驱动 — 字符设备驱动模板
背景 最近学习字符设备驱动,其大致的框架与流程都基本搞懂了,为了方便以后代码重用,写了一个较为完善的模板, 功能包括自动创建字符设备,/dev下自动创建设备文件,实现了open.read.write. ...
- Linux驱动字符设备(设备号的申请)
在了解Linux字符设备先了解一下Linux设备的分类. Linux设备分类 Linux设备主要分为字符设备.块设备.网络设备. 字符设备:能够像字节流一样被访问且没有缓冲是按顺序访问的设备,当对字符 ...
- linux内核驱动之 设备和模块的分类
目录 字符设备 块设备 网络接口 其他划分方式 以 Linux 的方式看待设备可区分为 3 种基本设备类型. 每个模块常常实现 3 种类型中的 1 种, 因此可分类成字符模块, 块模块, 或者一个网络 ...
最新文章
- linux下jdk/maven/tomcat
- make j4什么意思_为什么天天坚持撸铁 肌肉增长不明显
- .NET Core实战项目之CMS 第七章 设计篇-用户权限极简设计全过程
- Azure Blob Storage 基本用法 -- Azure Storage 之 Blob
- 最短编辑距离问题 : Levenshtein Distance
- 【电脑问题】win10更新后,java环境有问题:Error:missing `server` JVM at `*:\java\jre8\bin\server\jvm.dll`
- c语言变量名由啥组成,一个c语言是由什么构成
- android 使用so库,Android 使用SO库
- 十个个必装的火狐插件
- 安徽省月度降水量分布数据
- PHP高并发高负载下的3种实战场景解决方法
- 电信光猫桥接+ML固件路由器实现ipv6网站访问
- asuswrt 单臂路由_Padavan(老毛子) 最简单臂路由组网 VLAN 设置
- 关于360插件化框架Replugin竖屏修改为横屏解决方案
- python动态柱状图_动态排名柱状图的两种做法
- App Store Connect 上构建的新版本上传后找不到,二进制文件无效
- NVME协议解读(一)
- 3d建模薪资多少?工作后还能转行游戏建模吗?
- 江恩 计算机,《相龙决江恩计算器2018》设计原理及超级功能
- 谁在说谎问题用c语言编辑,C_谁在说谎
热门文章
- 不是Nvidia(英伟达)显卡可以安装CUDA跑深度学习算法吗?
- 国内洗地机品牌排行榜前十名有哪些?洗地机十大品牌排行榜介绍
- JavaScript 习题及面试题 4
- PHP文件绕过后缀执行配置
- MySQL的auto_increment使用
- android小米推送,Android手机端小米推送Demo解析和实现方法
- 孙子定理c语言程序,中国剩余定理(孙子定理)的证明和c++求解
- PDF编辑器:Adobe Acrobat XI Pro
- 在Win32程序中创建OpenGL渲染环境
- web前端不用怕,外卖平台的项目开发流程,大全!!