字符设备

Linux中设备常见分类是字符设备,块设备、网络设备,其中字符设备也是Linux驱动中最常用的设备类型。因此开发Linux设备驱动肯定是要先学习一下字符设备的抽象的。在内核中使用struct cdev来描述一个字符设备如下

struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};

其中kobj是内核对于所有设备的共性抽象,ops是字符设备操作接口,dev为设备号共32位其中高12位为主设备号,低20位为次设备号。设备号相关的提供了如下的工具宏

//从设备号获取主设备号
MAJOR(dev_t dev);
//获取次设备号
MINOR(dev_t dev);
//由主、次设备号创建设备号
MKDEV(int major,int minor);

最后也是最重要的就是字符设备的操作接口,这是Linux设备可以作为文件直接使用的关键;用户进程调用的一系列系统调度最后都是调用的这个接口中的对应接口完成处理,具体定义如下

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

其中驱动开发人员最关键最常用就是如下几个接口 :

//读取数据
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//写数据
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//多路IO 轮叙时会调用
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//驱动缓冲区映射到用户空间 常用于显示缓冲区
int (*mmap) (struct file *, struct vm_area_struct *);
//open 系统调用
int (*open) (struct inode *, struct file *);
//close 系统调用
int (*release) (struct inode *, struct file *);

字符设备驱动的使用也离不开内核提供的接口调用,这里主要记录一下常用接口函数

//初始化一个字符设备对象,并绑定文件操作接口
void cdev_init(struct cdev *, const struct file_operations *);
//从内核申请一个字符对象内存块
struct cdev *cdev_alloc(void);
//释放上面申请到的内存
void cdev_put(struct cdev *p);
//添加字符设备到内核
int cdev_add(struct cdev *, dev_t, unsigned);
//删除字符设备从内核
void cdev_del(struct cdev *);

然后是设备号它是Linux 内核给予设备的唯一ID。设备文件通过设备号结合虚拟文件系统找到具体设备进而找到文件操作接口。设备的设备号注册有两个主要接口 register_chrdev_region 和 alloc_chrdev_regin。这两个接口的都可以用来向系统申请注册设备号,但是他二者的机制是不同的。从入口参数上看register_chrdev_region  是在已经定义或已知主设备号的的前提下向系统注册设备号,而第二个接口则是未知设备号的情况下向系统申请的设备号该接口会返回申请到的设备号放在dev中。

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next; for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
alloc_chrdev_region
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}

通过对比实现可以发现两个接口最终都是通过调用同样的接口__register_chrdev_region 来完成设备号的注册的,只是第一个接口传入了主设备号他就会有一个风险就是设备号冲突,第二个接口设备号是由系统分配则就没有这个问题。最后就是字符设备的使用,这里参考宋宝华的书中给的使用框架。

//file open operation function
static int char_drv_open(struct inode *inode , struct file *filp)
{
return 0;
}
//file read operation function
static int char_drv_read(struct file *filp , char __user *buf , size_t cnt , loff_t *offt)
{
return 0;
}
//file write operation function
static int char_drv_write(struct file *filp,const char __user *buf , size_t cnt , loff_t *offt)
{
return 0;
}
//file close operation function
static int char_drv_release(struct inode *inode , struct file *filp)
{
return 0;
} //file operation function struct
static struct file_openration my_test_fop=
{
.owner = THIS_MODULE,
.open = char_drv_open,
.read = char_drv_read,
.write = char_drv_write,
.release = char_drv_release }; //module init function
static int __init char_drv_test_init(void)
{
int ret;
//apply device num
ret = register_chrdev(1314,"chartest",&my_test_fop);
if(ret <0)
{
return -1;
}
return 0;
}
//module uninstall function
static void __exit char_drv_test_exit(void)
{
//this num can use alloc_chrdev_region to apply an device num
unregister_chrdev(1314,"chartest",&my_test_fop);
} //module function band
module_init(char_drv_test_init);
module_exit(char_drv_test_exit); //license and author
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Smile");

这种使用方式有个问题就是设备注册成功后不会自动在/dev目录下生成设备文件,需要在执行insmod安装模块后执行mknod手动生成设备文件。所以就有了新的使用框架,通过借助设备类就可以实现设备节点的自动创建。基本框架如下

//file open operation function
static int char_drv_open(struct inode *inode , struct file *filp)
{
return 0;
}
//file read operation function
static int char_drv_read(struct file *filp , char __user *buf , size_t cnt , loff_t *offt)
{
return 0;
}
//file write operation function
static int char_drv_write(struct file *filp,const char __user *buf , size_t cnt , loff_t *offt)
{
return 0;
}
//file close operation function
static int char_drv_release(struct inode *inode , struct file *filp)
{
return 0;
} //file operation function struct
static struct file_openration my_test_fop=
{
.owner = THIS_MODULE,
.open = char_drv_open,
.read = char_drv_read,
.write = char_drv_write,
.release = char_drv_release }; struct cdev test_dev;
struct class *class;
struct devic *device;
dev_t devid; /* 设备结构体 */
struct test_dev
{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
}; #define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "newchrdev" /* 名字 */ struct test_dev test_char_dev; //module init function
static int __init char_drv_test_init(void)
{
//same hardware init //apply device num
alloc_chrdev_region(&test_char_dev.devid, 0, NEWCHRLED_CNT,NEWCHRLED_NAME); test_char_dev.major = MAJOR(test_char_dev.devid); /* 获取主设备号 */
test_char_dev.minor = MINOR(test_char_dev.devid); /* 获取次设备号 */ //init dev struct
cdev_init(&test_char_dev.cdev,&my_test_fop);
//add dev to system
cdev_add(&test_char_dev.cdev ,test_char_dev.devid ,NEWCHRLED_CNT ); //build class
test_char_dev.class = class_create(THIS_MODULE,"test");
if (IS_ERR(newchrled.class))
{
return PTR_ERR(newchrled.class);
}
//build device
test_char_dev.device = device_create(test_char_dev.class,NULL ,devid,NULL,"test");
if (IS_ERR(newchrled.device))
{
return PTR_ERR(newchrled.device);
} return 0;
}
//module uninstall function
static void __exit char_drv_test_exit(void)
{
/* 注销字符设备 */
cdev_del(&newchrled.cdev);/* 删除 cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class);
} //module function band
module_init(char_drv_test_init);
module_exit(char_drv_test_exit); //license and author
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Smile");

这样的驱动编译成模块后执行模块安装后/dev下就会自动创建设备文件使用起来更加方便。接下来再来看一个特殊的字符设备

杂项设备

杂项设备是一种特殊的类似字符设备的设备,他的特性是主设备号固定为10不需要像字符设备一样需要单独创建class后注册设备才能自动创建设备节点,杂项设备统一实现了杂项设备class注册完成后就能自动在dev目录下生成对应的设备文件,使用起来比使用字符设备更加简单。杂项设备抽象的结构体如下

struct miscdevice  {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
umode_t mode;
};

其中主要的几个成员是minor,name、fops,其余的是内核管理相关驱动开发基本很少直接使用,杂项设备的驱动的使用和字符设备基本类似,并且更加简单了一点,先实现file_operations的文件操作接口,然后定义一个杂项设备并将这个ops绑定到这个新定义的设备上。之后调用杂项设备注册接口进行注册。

int misc_register(struct miscdevice * misc)
{
dev_t dev;
int err = 0; INIT_LIST_HEAD(&misc->list); mutex_lock(&misc_mtx); if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
if (i >= DYNAMIC_MINORS) {
err = -EBUSY;
goto out;
}
misc->minor = DYNAMIC_MINORS - i - 1;
set_bit(i, misc_minors);
} else {
struct miscdevice *c; list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
err = -EBUSY;
goto out;
}
}
} dev = MKDEV(MISC_MAJOR, misc->minor); misc->this_device = device_create(misc_class, misc->parent, dev,
misc, "%s", misc->name);
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
err = PTR_ERR(misc->this_device);
goto out;
} /*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}

执行过程很简单判断待注册的新设备是否指定了次设备号,如果是MISC_DYNAMIC_MINOR 则说明是由杂项设备子系统分配次设备号的,次设备号的管理由杂项设备子系统用的位图实现管理通过 find_first_zero_bit 接口获取次设备号;反之如果是指定的次设备号则会判断当前次设备号是否有被使用过,如过被使用过则返回EBUS错误并执行错误处理后返回。反之以上处理过程没有问题的话就继续向下处理,到这里处理过程就像使用字符设备时相同,而所属的类就是杂项设备类即(misc_class),因为通过字符设备创建可以知道在字符设备创建的时候需要创建设备类并将新的字符设备添加到指定设备类,这样字符设备的模块安装才会在/dev目录下自动创建设备文件。回到杂项设备添加的处理最后就是将当前新设备加入到杂项设备子系统的设备list上。底层涉及Linux device相关知识点这一篇是学习这部分时记录的内容可以参考。

最后是杂项设备的注销过程

int misc_deregister(struct miscdevice *misc)
{
int i = DYNAMIC_MINORS - misc->minor - 1; if (WARN_ON(list_empty(&misc->list)))
return -EINVAL; mutex_lock(&misc_mtx);
list_del(&misc->list);
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
mutex_unlock(&misc_mtx);
return 0;
}

执行过程更加简单,直接获取次设备号用于后面清除 bitmap管理的次设备号位图,然后就是调用字符设备驱动中相同的设备注销接口device_destroy 将设备从内核中移除。这里没有像前面的字符设备结合设备类的情形去删除设备类原因很简单,因为杂项设备类要驻留在系统中。

Linux 驱动框架---cdev字符设备驱动和misc杂项设备驱动的更多相关文章

  1. 一步步理解linux字符设备驱动框架(转)

    /* *本文版权归于凌阳教育.如转载请注明 *原作者和原文链接 http://blog.csdn.net/edudriver/article/details/18354313* *特此说明并保留对其追 ...

  2. Linux驱动设计——字符杂项设备

    杂项设备 linux里面的misc杂项设备是主设备号为10的驱动设备,misc设备其实也就是特殊的字符设备,可自动生成设备节点. 定义头文件<linux/miscdevice.h>   杂 ...

  3. Linux驱动框架之misc类设备驱动框架

    1.何为misc设备 (1)misc中文名就是杂项设备\杂散设备,因为现在的硬件设备多种多样,有好些设备不好对他们进行一个单独的分类,所以就将这些设备全部归属于 杂散设备,也就是misc设备,例如像a ...

  4. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  5. linux驱动初探之杂项设备(控制两个GPIO口)

    关键字:linux驱动.杂项设备.GPIO 此驱动程序控制了外接的两个二极管,二极管是低电平有效. 上一篇博客中已经介绍了linux驱动程序的编写流程,这篇博客算是前一篇的提高篇,也是下一篇博客(JN ...

  6. spi驱动框架全面分析,从master驱动到设备驱动

    内核版本:linux2.6.32.2  硬件资源:s3c2440 参考:  韦东山SPI视频教程 内容概括:     1.I2C 驱动框架回顾     2.SPI 框架简单介绍     3.maste ...

  7. Linux 驱动框架---platform驱动框架

    Linux系统的驱动框架主要就是三个主要部分组成,驱动.总线.设备.现在常见的嵌入式SOC已经不是单纯的CPU的概念了,它们都会在片上集成很多外设电路,这些外设都挂接在SOC内部的总线上,不同与IIC ...

  8. 2.1 摄像头V4L2驱动框架分析

    学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤:  一.V4L ...

  9. 驱动框架入门——以LED为例[【转】

    本文转载自;http://blog.csdn.net/oqqHuTu12345678/article/details/72783903 以下内容源于朱有鹏<物联网大讲堂>课程的学习,如有侵 ...

随机推荐

  1. 【UNIAPP】接入导航系统完整版

    // 查询附近/搜索关键词 <template> <view> <!--地图容器--> <map id="myMap" :markers= ...

  2. ORM框架对比以及Mybatis配置文件详解

    ORM框架对比以及Mybatis配置文件详解 0.数据库操作框架的历程 (1) JDBC ​ JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句 ...

  3. java画海报二维码

    package cn.com.yitong.ares.qrcode; import java.awt.BasicStroke;import java.awt.Color;import java.awt ...

  4. 三次握手 四次握手 原因分析 TCP 半连接队列 全连接队列

    小结 1. 三次握手的原因:保证双方收和发消息功能正常: [生活模型] "请问能听见吗""我能听见你的声音,你能听见我的声音吗" [原理]A先对B:你在么?我在 ...

  5. equals()方法和hashCode()方法

    1.equal()方法 2.hasCode()方法 2.1 Object的hashCode() 2.2 hashCode()的作用 3.String中equals()和hashCode()的实现 4. ...

  6. Spark+Kafka实时监控Oracle数据预警

    目标: 监控Oracle某张记录表,有新增数据则获取表数据,并推送到微信企业. 流程: Kafka实时监控Oracle指定表,获取该表操作信息(日志),使用Spark Structured Strea ...

  7. Docker容器管理平台Rancher高可用部署——看这篇就够了

    记得刚接触Rancher时,看了官方文档云里雾里,跟着官网文档部署了高可用Rancher,发现不管怎么折腾都无法部署成功(相信已尝试的朋友也有类似的感觉),今天腾出空来写个总结,给看到的朋友留个参考( ...

  8. shell编程基础一

    1.定义变量 a=1 shell定义变量要注意等号前后不能有空格,不然会报错,请严格按照格式编写. 2.打印输出 echo 1 使用echo打印,后面留一个空格. 3.shell中通过 ${变量名} ...

  9. B - Play on Words

    如果这个图是欧拉路,则每个顶点的出度等于入度.即out[i] = in[i] 如果这个图是半欧拉图,则起点的出度比入度大1,终点的入度比出度大1.其余顶点的出度等于入度.如果满足上述条件,就可以将所有 ...

  10. [AHOI2009] [BZOJ1799] 月之迷 (数位DP)

    给出两个数a,ba,b,求出\([a,b]\)中各位数字之和能整除原数的数的个数. 我们按照模板的做法来想,枚举到第pos位时,要确定这一位的数字,可以更新现在所填数字的和,但对于最终的和无从得知,是 ...