字符设备在应用层如何体现

cat /proc/devices 命令可以查看当前系统中所有的字符设备和块设备。


在Linux 中一切接文件,设备也被抽象成文件,在/dev/ 目录下可以查看到字符设备和块设备的对应的文件。
例如这是两个串口设备文件,使用ls -l 查看它的详细信息。
与普通文件不同的是设备文件没有大小,而是被替换成了设备号(主设备号和次设备号),设备号可以与/proc/devices 中的信息相对应。

如何访问一个设备?
既然它被抽象成了一个文件,那么自然就用文件IO (open、read、write 等等) 来访问。

设备号


dev_t dev = MKDEV(major,minor)
major = MAJOR(dev)
minor = MINOR(dev)
设备号由major和minor 组成,总共32位,高12位为major,低20位为minor。每一个设备号都唯一对应着一个cdev结构体。

函数声明参考头文件:include/linux/fs.h
注册字符设备首先要申请设备号,可以一次批量的申请多个设备号(相同的major,依次递增的minor)。
如果没有指定主设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
baseminor:起始的minor值。
count:一共申请几个设备号。申请到的设备号在(MKDEV(major,baseminor) ~ MKDEV(major,baseminor+count)) 范围内。
(自动申请设备号的原理,其实是传递一个major = 0,然后由内核分配一个空闲的设备号返回)

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)

释放设备号:
void unregister_chrdev_region(dev_t from, unsigned count)

字符设备结构体 cdev

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

常用
申请一个cdev 内存:
struct cdev *cdev_alloc(void);
初始化cdev->ops,即cdev->ops = &xxx_file_operation; :
void cdev_init(struct cdev *, const struct file_operations *);
将填充好的cdev 实例,添加到cdev 链表。意味着向内核注册一个字符设备:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
cdev_add 是将cdev 结构体按照dev_t 的值添加到一个哈希表中(数组+链表),一个cdev可以对应多个dev_t,添加完成后可以通过dev_t 找到对应的cdev。
cdev:要注册的cdev;
dev:设备号其实值;
count:需要绑定的设备号个数;

从内核删除一个字符设备:
void cdev_del(struct cdev *);

不常用
增加cdev 调用计数:
void cdev_put(struct cdev *p);

总结:注册字符设备的主要流程就是,申请设备号dev_t,创建一个cdev、初始化cdev (cdev->ops)、向内核添加 cdev(同时会绑定cdev 与dev_t)。

如果你嫌这些步骤太麻烦的话,内核还提供了一个函数可以一步到位的注册字符设备——__register_chrdev
它会申请多个设备号,创建cdev并初始化它,然后用这多个设备号绑定同一个cdev 注册。

还有一个register_chrdev,它是对__register_chrdev 的封装,次设备号从基值0开始,直接申请了256 个次设备号。
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }

struct file_operations

字符设备在/dev/ 目录下创建设备文件,并通过struct file_operations 向应用层提供控制接口。应用层对应的open、read 等函数会调用到file_operations 对应的函数。

//include/linux/fs.h
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 (*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 (*mremap)(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 **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endif
};

copy_to_user() 与 copy_from_user()
为了安全考虑,应用进程不能直接访问内核数据,需要借助这两个函数拷贝:
static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n)
static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n)
返回非0 表示错误。

自动创建设备文件

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件include/linux/device.h 里面。

使用 class_create 创建一个类:

extern struct class * __must_check __class_create(struct module *owner,const char *name,struct lock_class_key *key);#define class_create(owner, name)               \
({                                              \static struct lock_class_key __key;     \__class_create(owner, name, &__key);    \
})

使用class_destroy 摧毁一个类:
extern void class_destroy(struct class *cls);

struct class {const char              *name;struct module           *owner;struct class_attribute          *class_attrs;const struct attribute_group    **dev_groups;struct kobject                  *dev_kobj;int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, umode_t *mode);void (*class_release)(struct class *class);void (*dev_release)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct kobj_ns_type_operations *ns_type;const void *(*namespace)(struct device *dev);const struct dev_pm_ops *pm;struct subsys_private *p;
};

在创建完类后,要创建一个设备,使用 device_create创建一个设备:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

摧毁一个设备:
extern void device_destroy(struct class *cls, dev_t devt);

创建类会在/sys/class/ 目录下生成一个新的文件夹,其中包含属于此类的设备文件夹。

IS_ERR 和 PTR_ERR

IS_ERR 可以判断一个指针是否为空,PTR_ERR 将指针转化为数值返回。

static inline long __must_check PTR_ERR(const void *ptr)
{return (long) ptr;
}static inline long __must_check IS_ERR(const void *ptr)
{return IS_ERR_VALUE((unsigned long)ptr);
}

代码示例

#include <linux/fs.h>       //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>  //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件#define DEVICE_CNT 0    //设备号个数struct led_device{dev_t devid;   //设备号int major; //主设备号int minor;    //次设备号char* name = "led";    //驱动名struct cdev led_dev;   //cdev 结构体struct class *class;  /* 类    */struct device* device;    //设备
};struct led_device led;static int led_open(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/* 设备操作函数 */
static struct file_operations led_fo = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,
};static int _init led_init()
{/*注册字符设备驱动*//*1.注册设备号*/led.major = 0; //由内核自动分配主设备号if(led.major)  //如果分配了的话就注册{led.devid = MKDEV(led.major,0);   register_chrdev_region(led.devid,DEVICE_CNT,led.name);  //将驱动注册到内核中}else{       //如果没有分配的话//从0号(次设备号)开始申请alloc_chrdev_region(&led.devid,0,DEVICE_CNT,led.name);       //申请设备号led.major = MAJOR(led.devid);   //获取主设备号led.minor = MANOR(led.devid);  //获取次设备号}printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);    /*2.初始化 cdev 结构体*/led.led_dev.woner = THIS_MODULE;cdev_init(&led.led_dev,&led_fo); //将操作函数初始化到cdev结构体/*3.应该是向链表中添cdev*/cdev_add(&led.led_dev,led.devid,DEVICE_CNT);    /*4.创建节点*/led.class = class_create(THIS_MODULE,led.name);      //先创建一个类led.device = device_create(led.class,NULL,led.devid,NULL); //创建设备return 0;}static void _exit led_exit()
{/* 注销字符设备驱动 */cdev_del(&newchrled.cdev);/*  删除cdev */unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */device_destroy(newchrled.class, newchrled.devid);class_destroy(newchrled.class);
}/*注册字符设备入口与卸载入口*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhoujianghong");

应用open到file_operations->open 的调用原理

Linux 字符设备相关推荐

  1. ()shi linux字符设备,Linux字符设备驱动基础(三)

    Linux字符设备驱动基础(三) 6 创建设备节点 6.1 手动创建设备节点 查看申请的设备名及主设备号: cat /proc/devices # cat /proc/devices Characte ...

  2. linux设备模型 字符设备,Linux 字符设备驱动模型之框架解说

    一.软件操作硬件设备模型 在进行嵌入式开发的过程中,在常做的事情就是驱动配置硬件设 备,然后根据功能需求使用硬件设备,实现功能的逻辑.如下图为其 相互之间的关系. 如上图所示: 驱动程序:主要作为操作 ...

  3. linux字符设备文件的打开操作,Linux字符设备驱动模型之字符设备初始化

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为: 所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:[unsigned int count;].[de ...

  4. linux生成驱动编译的头文件,嵌入式Linux字符设备驱动——5生成字符设备节点

    嵌入式Linux字符设备驱动开发流程--以LED为例 前言 留空 头文件 #include 查看系统设备类 ls /sys/class 设备类结构体 文件(路径):include/linux/devi ...

  5. linux字符设备驱动的 ioctl 幻数

    在Linux字符设备驱动入门(一)中,我们实现了字符设备的简单读写字符功能,接下来我们要在这个基础上加入ioctl功能.首先,我们先来看看3.0内核下../include/linux/fs.h中fil ...

  6. Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

    前面在 Linux 字符设备驱动开发基础 (三)-- 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另 ...

  7. linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!

    原标题:从点一个灯开始学写Linux字符设备驱动! [导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一 ...

  8. linux 字符设备驱动cdev

    linux字符设备是最为常见的驱动,比如 serial drivers, audio drivers, video drivers, camera drivers, and basic I/O dri ...

  9. Linux字符设备驱动

    /*Linux字符设备驱动源代码scdd.c*/ #include <linux/init.h>   /*模块头文件*/ #include <linux/module.h> # ...

  10. Linux字符设备驱动中container_of宏的作用

    Linux字符设备驱动中container_of宏的作用 首先看看这个宏的原型: container_of(ptr,type,member) 功能:根据一个结构体变量中的一个成员变量的指针来获取指向整 ...

最新文章

  1. 如果asp.net mvc中某个action被执行了两次,请检查是不是以下的原因
  2. lua C交互函数注释
  3. 数据库——MongoDB的安装
  4. java 网络序_Java使用网络字节序进行数据传输
  5. halcon file_exists 检查文件是否存在
  6. .NET Core微服务之路:让我们对上一个Demo通讯进行修改,完成RPC通讯
  7. Shiro 实现免密登陆
  8. 计算机管理在哪个里面,在计算机中哪个子系统是其他子系统的管理者
  9. Data Binding 使用教程一
  10. 浅析GDAL库C#版本支持中文路径问题(续)
  11. unittest框架(惨不忍睹低配版)
  12. wekan 工具配置
  13. 1.UFS3.1 — Power Mode
  14. 铀球(235U)的临界半径计算(1d,S8)
  15. 2019计蒜之道初赛第一场 A 商汤的AI伴游小精灵(DFS)
  16. C4D学习笔记3-动画-动画渲染流程案例
  17. 如何安装配置JDK(保姆级完美解决教程+附百度网盘链接)
  18. 1K2G保驾护航,无穷小真理放光芒
  19. 我的编程之路(三) 蜗居
  20. ARM STMFD, STMFA, STMED, STMEA, LDMFD, LDMFA, LDMED, LDMEA

热门文章

  1. mysql的check语言_check在SQL语句中的意思是什么?
  2. 人生必读十大启迪故事
  3. ISIS之LSP详解
  4. 全球及中国安防电源行业竞争状况及供需前景预测报告(新版)2022-2027
  5. 热图(Heatmap)绘制(matplotlib与seaborn)
  6. Vue的快速入门通俗易懂(学习笔记)
  7. 没穿越的小伙伴,来领个随身老爷爷
  8. 决策树算法原理(python)
  9. 打不开eclipse 由于它来自身份不明的开发者
  10. JS 为按钮绑定多个点击事件(兼容性代码)