精华内容
下载资源
问答
  • 常见的字符设备
    2021-04-25 17:48:05

    字符设备和杂项设备的区别

    杂项设备的主设备号10是固定的,分配的是次设备号;在字符设备中要自己申请设备号并注册到系统中。

    字符设备

    字符设备按照复杂程度也可以分为:简单字符设备+分层字符设备(fbmem驱动和v4l2驱动也属于字符设备驱动,并且是分层驱动)

    简单字符设备驱动编译一般步骤:

    1. 申请设备号
    2. 申请cdev内存
    3. 初始化cdev
    4. 向内核注册一个字符类设备

    用到的相关函数:

    #include <linux/fs.h>
    int register_chrdev_region(dev_t from, unsigned count, const char *name);  
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
    
    #include <linux/cdev.h>
    struct cdev *cdev_alloc(void);
    void cdev_init(struct cdev *dev, struct file_operations *fops);
    int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
    void cdev_del(struct cdev *dev);

    这样设备驱动就完成了,但是在用户空间还不能使用。还需要

    用mknod:mknod DEVNAME {b | c} MAJOR MINOR //b为块设备,c为字符设备

    比如:mknod /dev/key_name c 236 1

    在/dev目录中创建字符设备或块设备文件节点,/dev为linux中将设备抽象虚拟为文件。

    在linux系统中,设备文件是特殊的文件类型,主要作用是沟通用户空间程序和内核空间驱动程序。如果驱动程序只是作为内核程序或者为其它内核模块服务,就没必要生成设备文件。

    也可以在程序中自动创建设备:

    1. 创建一个类
    2. 在类中根据需要创建设备
    class_create();
    device_create();

    字符设备分为静态申请和动态申请,静态申请就是主设备号是程序员手动分配了,动态申请是系统给分配.

    int MAJOR(dev_t dev) //从设备号中获取主设备号

    int MINOR(dev_t dev) //从设备号中获取次设备号

    dev_t MKDEV(unsigned int major, unsigned int minor); //用主设备号和次设备号生成设备号

    疑问:为什么在注册多个字符设备时,只使用了一次alloc_chrdev_region()函数?

    注意:alloc_chrdev_region()函数可以分配1个主设备号+多个次设备号

    字符设备不需要进行设备注册+驱动注册的方式。

    新旧字符设备驱动注册

    旧设备驱动的缺陷:需要事先知道哪些主设备号没被使用,以便确定需要使用的设备号;并且会依次占用对应的此设备号。

    旧版字符设备注册

    实际上对申请设备号、申请设备内存、初始化cdev、系统注册步骤的集合。

    注册和注销函数原型如下所示:

    #include <linux/fs.h>

    static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

    static inline void unregister_chrdev(unsigned int major, const char *name)

    新设备号申请

    如果没有指定设备号的话就使用如下函数来申请设备号:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

    如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:

    int register_chrdev_region(dev_t from, unsigned count, const char *name)

    ps:优先选择alloc_chrdev_region 而不是 register_chrdev_region。

    新设备释放

    函数申请的设备号,统一使用如下释放函数:

    void unregister_chrdev_region(dev_t from, unsigned count)

    设备私有数据

    在打开设备时,可以设置私有数据,writereadclose等函数中直接读取private_data即可得到设备结构体。

    struct test_dev{ 
        dev_t devid; /* 设备号 */ 
        struct cdev cdev; /* cdev */ 
        struct class *class; /* 类 */ 
        struct device *device; /* 设备 */ 
        int major; /* 主设备号 */ 
        int minor; /* 次设备号 */ 
    };
    
    static int test_open(struct inode *inode, struct file *filp) 
    { 
        filp->private_data = &testdev; /* 设置私有数据 */ 
        return 0; 
    } 

    说明:

    新版在2.4版本后加入,旧版中在内核中最多能申请256个字符主设备,每个主设备可以有256个次设备,

    但是主设备的所有次设备都公用一个file_operation并且在使用register_chrdev时,会一次申请完0-256个次设备号。

    所以在内核中最多有256种字符设备。

    新版中在一个主设备可以分段申请:比如0-1,它们公用一个file_operation;

    2-3,它们公用一个file_operation.所以,一个主设备就可以包含256种字符设备,内核中最多有256*256中字符设备。

    创建设备节点:

    头文件:include/linux/device.h”

    1.先创建一个类

    class_create(owner, name);

    2.在类中创建设备节点

    extern struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...);

    创建并注册类:

    struct class *class_create(struct module *owner, const char *name); //创建类并注册到内核
    void class_destroy(struct class *cls);

    创建设备并注册:

    struct device *device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)
    void device_destroy(struct class *cls, dev_t devt);

    cat /proc/devices //显示设备及主设备号

    ls /sys/class //显示设备类别

    ls /sys/class/xxx //显示设备类别中的xxx类设备

    ls /dev //显示设备节点

    register_chrdev_region中的设备名字可以在/proc/devices中看到

    class_create中的类名字在/sys/class中看到

    device_create中的名字在/sys/class/xxx/中看到,也会在/dev中生成设备节点

    加载驱动:insmod xxx.ko

    显示驱动:lsmod

    卸载驱动:rmmod xxx //注意没有.ko

    设备类

    Linux 中的class 是设备类,它是一个抽象的概念,没有对应的实体。它是提供给用户接口相似的一类设备的集合。常见的有输入子系统input、usb、串口tty、块设备block 等。

    以4412 的串口为例,它有四个串口,不可能为每一个串口都重复申请设备以及设备节点,因为它们有类似的地方,而且很多代码都是重复的地方,所以引入了一个抽象的类,将其打包为ttySACX,在实际调用串口的时候,只需要修改X 值,就可以调用不同的串口。

    对于本实验中的设备,它有两个子设备,将对应两个设备节点,所以需要用到class 类这样一个概念。

    更多相关内容
  • 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备常见字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l /dev的时候,就能...
  • 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备是字节流形式通讯的I/O设备,绝大部分设备都是字符设备常见字符设备包括鼠标、键盘、显示器、串口等等,当我们执行 ls -l /dev 的时候,能...
  • 字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备常见字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l/dev的时候,就能...
  • 因为Linux字符设备驱动主要依赖于struct cdev结构,原型为:所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:【unsigned int count;】、【dev_t dev;】、【const struct file_...

    因为Linux字符设备驱动主要依赖于struct cdev结构,原型为:

    3e509a2e19851f33c7f66a3bbd07bba0.png

    所以我们需要对所使用到的结构成员进行配置,驱动开发所使用到的结构成员分别为:【unsigned int count;】、【dev_t dev;】、【const struct file_operations *ops;】。

    开发流程如下图:

    564d2d071ce4473eceb811d7632f6f64.png

    一、字符设备驱动初始化

    1、分配设备cdev

    Cdev变量的定义可以采用静态和动态两种方法进行分配。静态方法直接分配内存,而动态方法随机分配内存。

    静态分配cdev:

    定义:struct cdev mdev;

    mdev即代表相应的字符设备空间地址。

    动态分配cdev:

    定义:struct cdev *pdev = cdev_alloc();

    pdev即代表对于的字符设备的空间地址。

    2、初始化设备cdev

    Linux内核中,字符设备struct cdev的初始化使用cdev_init。其原型如下:

    9794af9662db287992b92247846e9f95.png

    参数:

    cdev:待初始化的struct cdev结构

    fops:设备对应的操作函数集

    由原型所要求的参数可知,需要初始化一个字符设备,必须根据申请一个struct cdev结构的空间,然后对其成员进行配置。即实现设备的操作函数集、为设备申请设备号(包括主设备号和次设备号)、指定此类型的字符设备有多少个相同设备。

    3、注册设备cdev

    Linux内核中字符设备的注册使用cdev_add函数来进行完成注册。其原型如下:

    27f40756bc6a73b901f5f496ffe01d48.png

    其相关参数定义为:

    p:待添加到内核中的字符设备结构,即为struct cdev。

    dev:设备号

    count:该类设备的设备数量,各个设备的区别体现为从设备号。

    在开发驱动时,当确定了字符设备的结构,主设备号和从设备号、设备的数量,就可以使用cdev_add函数将相应的字符设备添加到Linux内核驱动中进行注册。

    4、硬件初始化

    关于硬件的初始化就简单了。直接根据所需要操作的字符设备,阅读器Datasheet,然后根据Datasheet进行硬件的配置即可。

    二、设备操作和驱动操作映射

    bf4f87763c4f55f0376f89b8226daafe.png

    从上图基本可知,在用户空间的每一种硬件设备操作函数,在内核空间通用有一个映射操作函数实现。

    •int (*open) (struct inode *, struct file *):打开设备,响应open系统

    •int (*release) (struct inode *, struct file *):关闭设备,响应close系统调用

    •loff_t (*llseek) (struct file *, loff_t, int):重定位读写指针,响应lseek系统调用

    •ssize_t (*read) (struct file *, char __user *, size_t, loff_t *):从设备读取数据,响应read系统调用

    •ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *):向设备写入数据,响应write系统调用

    从以上函数指针操作方法中可以提取出两个重要的参数成员,即为struct file和struct inode。

    【struct file】:在Linux系统中,每一个打开的文件,在内核中都会对应的关联一个struct file结构体,它由内核在打开文件时创建,在文件关闭后释放。

    其非常重要的成员有:

    loff_t f_pos /*文件读写指针*/

    struct file_operations *f_op /*该文件所对应的操作*/

    【struct inode】:每一个存在于文件系统里面的文件都会关联一个inode 结构,该结构主要用来记录文件物理上的信息。因此, 它和代表打开文件的file结构是不同的。一个文件没有被打开时不会关联file结构,但是却会关联一个inode 结构。

    在struct inode结构中,dev_t i_rdev尤其重要,表示设备号。

    三、struct file_operations结构常用设备操作解析

    1.open设备操作

    根据原型可知,open设备函数方法是在进行开始启用操作设备时的初始化工作,与用户空间的open函数对应,通常情况下,open函数实现:

    (1)表明次设备号

    (2)启用设备

    当然,如果在启用设备时,不需要任何的准备操作或者初始化操作,那么open函数可以为空函数,不实现。

    2.release设备操作

    release函数方法的作用正好和open函数方法的作用相反,它在设备关闭时用到,与用户空间的close对应。通常用来进行关闭设备的实现。如果所操作的设备在关闭时不需要其他操作,那么使其为空函数即可。

    3.read设备操作

    read方法主要完成两件事:

    (1)访问硬件操作,从设备中读取数据。

    (2)将从设备端读取到的数据返回给用户空间的应用程序read函数。

    其read方法的具体原型可分析如下:

    ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)

    参数分析:

    filp:与字符设备文件关联的file结构指针, 由内核创建。

    buff : 从设备读取到的数据,需要保存到的位置。由read系统调用提供该参数。

    count: 请求传输的数据量,由read系统调用提供该参数。

    offp: 文件的读写位置,由内核从file结构中取出后,传递进来。

    那么实际上在这里就存在了一个问题,从内核空间如何将数据返回给用户空间???

    在Linux内核中,为从内核空间将数据返回到用户空间提供了函数方法,即为:copy_to_user。其原型如下:

    0506b3dbe369fba2f4480e8948bfc3a0.png

    之所以采用copy_to_user函数的原因是,buff参数来源于用户空间的指针,这类指针在内核空间中不能直接被内核代码直接引用,所以必须采用拷贝的方式进行数据的传递。具体的操作过程如下图表示:

    0ee1012a768aeed341877eed62b7197c.png

    4.write设备操作

    write函数方法也主要完成两件事:

    (1)从应用程序提供的地址中取出数据到内核空间。

    (2)访问硬件设备,将数据写入到设备中。

    write函数方法直接对应于用户空间的write函数。

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *)

    write函数方法的参数基本上和read函数方法类似,在此不再做解释。

    那么实际上在此也存在一个问题,如何将数据从用户空间取出到内核空间使用???

    实际上Linux内核中也提供了相应的方法函数,将用户空间的数据拷贝到内核空间进行使用,其方法为:copy_from_user。原型如下:

    ba1dd9705eb3de2888e1433ad0b12bd6.png

    四、设备驱动注销

    当内核中不再需要使用一个字符设备时,我们可以在内核中将相应的设备驱动程序进行卸载。那么就需要对其字符设备进行注销。Linux内核中为我们提供了cdev_del函数来完成字符设备的注销。其原型如下:

    30fc6c2244cd517a7532bfc8e4bae3d3.png

    展开全文
  • Linux一切皆文件,系统与设备通信之前,要建立一个存放在/dev目录下的设备文件,默认...创建一个名为”linuxcool”的字符设备: [root@linuxcool ~]# mknod linuxcool c 30 1 创建一个名为”linuxcool”的块设备: [roo
  • 字符设备

    千次阅读 2021-02-02 16:00:29
    字符设备0.设备驱动的分类1./dev目录下设备的区分2.字符设备驱动的设计过程3.定义一个字符设备4.定义并初始化一个文件操作集5.给字符设备申请设备号6.初始化设备7.编写应用程序8.使用动态分配设备号9.驱动程序与应用...

    0.设备驱动的分类

    设备驱动分为三大类:字符设备、块设备、网络设备。
    1.字符设备:

    该设备对数据的处理按照字节流的形式进行的,支持顺序访问(是有时间的概念),也可以支持随机访问。
    典型的字符设备:串口、键盘、触摸屏、摄像头、I2C、SPI、声卡、帧缓冲设备…
    顺序访问的设备:串口、键盘、触摸屏
    随机访问的设备:帧缓冲设备

    应用程序,能够使用系统IO函数来就行访问:open、write、read、lseek、close…

    crw--w---- 1 root tty     4,  58 Jan  1  1970 tty58
    crw--w---- 1 root tty     4,  59 Jan  1  1970 tty59
    crw--w---- 1 root tty     4,   6 Jan  1  1970 tty6
    crw--w---- 1 root tty     4,  60 Jan  1  1970 tty60
    crw--w---- 1 root tty     4,  61 Jan  1  1970 tty61
    crw--w---- 1 root tty     4,  62 Jan  1  1970 tty62
    crw--w---- 1 root tty     4,  63 Jan  1  1970 tty63
    crw--w---- 1 root tty     4,   7 Jan  1  1970 tty7
    crw--w---- 1 root tty     4,   8 Jan  1  1970 tty8
    crw--w---- 1 root tty     4,   9 Jan  1  1970 tty9
    crw------- 1 root tty   207,  16 Aug 14 12:44 ttymxc0
    crw------- 1 root root  207,  18 Jan  1  1970 ttymxc2
    crw------- 1 root root   10,  55 Jan  1  1970 ubi_ctrl
    
    

    设备号方便操作系统进行管理,就例如进程,方便给系统管理,就有一个进程号,即pid,使用ps命令可以知道每个进程的id号;
    线程,方便给系统管理,就有一个线程号,即tid,创建线程返回线程id;
    就像每个人都会有一个名字,但是名字容易出现同名,这个时候可以使用身份证号码进行区分。

    2.块设备:
    该设备的处理按照若干个块来进行的。一个块的固定大小512字节、4096字节。这类设备支持随机访问,这种以块和随机访问能够提高数据存储效率。
    块设备往往是面向于存储类的设备:nand flash、SD卡、U盘、eMMC、硬盘…。

    块设备在/dev目录详细表现形式:

    brw-rw---- 1 root disk    1,   0 Jan  1  1970 ram0
    brw-rw---- 1 root disk    1,   1 Jan  1  1970 ram1
    brw-rw---- 1 root disk    1,  10 Jan  1  1970 ram10
    brw-rw---- 1 root disk    1,  11 Jan  1  1970 ram11
    brw-rw---- 1 root disk    1,  12 Jan  1  1970 ram12
    brw-rw---- 1 root disk    1,  13 Jan  1  1970 ram13
    brw-rw---- 1 root disk    1,  14 Jan  1  1970 ram14
    brw-rw---- 1 root disk    1,  15 Jan  1  1970 ram15
    brw-rw---- 1 root disk    1,   2 Jan  1  1970 ram2
    brw-rw---- 1 root disk    1,   3 Jan  1  1970 ram3
    brw-rw---- 1 root disk    1,   4 Jan  1  1970 ram4
    brw-rw---- 1 root disk    1,   5 Jan  1  1970 ram5
    brw-rw---- 1 root disk    1,   6 Jan  1  1970 ram6
    brw-rw---- 1 root disk    1,   7 Jan  1  1970 ram7
    brw-rw---- 1 root disk    1,   8 Jan  1  1970 ram8
    brw-rw---- 1 root disk    1,   9 Jan  1  1970 ram9
    
    

    3.网络设备:
    网络设备是比较特殊的,在/dev没有设备文件,它就是专门针对网络设备的一类驱动,其主要作用是进行网络的数据收发。
    网络类设备:有线网卡、无线WiFi网卡(RT3070)、无线GPRS网卡、无线4G网卡…
    应用程序:
    socket套接字

    1./dev目录下设备的区分

    1.文件类型

    • c:字符设备
    • b:块设备
    • d:目录
    • l:符号链接

    在/dev目录主要集中了字符设备和块设备,字符设备以c作为开头,块设备以b作为开头,详细如下:

    crw-rw----    1 root     root       29,   0 Jan  1  1970 fb0
    brw-rw----    1 root     root      179,   1 Jan  1  1970 mmcblk0p1
    

    2.设备号
    其实设备号等同于身份证号码,方便系统进行管理。设备文件跟普通文件区别在于比普通文件多出了两个数字,这两个数字分别是主设备号和次设备号。

    普通文件:

    root@ATK-IMX6U:~# ls -l 1.mp3
    -rw-r--r--    1 root     root       8941241 Jan  1  2015 1.mp3
    

    设备文件:

    root@ATK-IMX6U:~#  /dev]#ls -l fb0
    crw-rw----    1 root     root       29,   0 Jan  1  1970 fb0
    

    观察fb0设备文件,多出的数字为“29,0”,详细如下:

    • 29:主设备号
    • 0:次设备号

    主设备号: 区分某一类的设备。
    ttySAC这是串口设备,主设备号为204;
    mmcblk0这是电子硬盘属于块设备,主设备号为179。

    次设备号: 用于区分同一类设备的不同个体或不同分区。
    串口设备:串口0串口3,则使用次设备号6467进行区分。
    电子硬盘设备:分区1分区7,则使用次设备号17进行区分。

    身份证: 前六位数字,就代表是某个地区,用于区分不同地方的人。
    后面的其他数字是具体到某个人。

    2.字符设备驱动的设计过程

    1. 定义一个字符设备,struct cdev

    2. 申请设备号
      1)静态注册
      MKDEV
      register_chrdev_region

      2)动态注册
      alloc_chrdev_region

    3. 定义file_operations,初始化打开、关闭、读、写等函数接口。

    4. cdev初始化
      .cdev_init

    5. 将cdev加入到内核
      .cdev_add

    6.创建设备文件
    .手动创建,使用mknod命令去/dev目录进行创建
    .自动创建
    1)class_create
    2)device_create

    3.定义一个字符设备

    1. 描述字符设备的结构体——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;
    };
    

    在linux内核当中,使用cdev来描述一个设备,每个字符设备都有自己一个cdev,设计字符设备的时候,首先定义一个cdev。
    例如:

    static struct cdev imx6ull_led_cdev;
    

    2.cdev的成员

    • struct kobject kobj,内核管理驱动的时候,使用到的一个object,自动调用。
    • struct module owner,cdev是属于哪一个module,默认初始化为THIS_MODULE,指向当前模块,类似于C++ this指针,这个成员可阻止该模块还在被使用时被卸载。【
    • const struct file_operations ops,字符设备文件操作集。【
    • struct list_head list,内核管理字符设备的链表,自动调用。
    • dev_t dev,设备号。【 】
    • unsigned int count,次设备的数量。【*】

    4.定义并初始化一个文件操作集

    1.文件操作集

    #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 *);
         long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
         int (*open) (struct inode *, struct file *);
         int (*release) (struct inode *, struct file *);
    	...........
    };
    

    2.文件操作集的作用
    每个字符设备都必须有一个文件操作集,文件操作集是驱动程序给应用程序访问硬件的一个接口,应用层与驱动程序函数接口的对应关系如下:

    应用层 驱动层
    open open
    close release
    write write
    read read
    lseek llseek
    ioctl unlocked_ioctl

    例子:

     static int led_release(struct inode *inode, struct file *filp)
     {
     return 0;
     }
    
     /* 设备操作函数 */
     static struct file_operations newchrled_fops = {
     .owner = THIS_MODULE,
     .open = led_open,
     .read = led_read,
     .write = led_write,
     .release = led_release,
     };
    

    5.给字符设备申请设备号

    (一)详细了解设备号

    每个设备文件(字符设备 or 块设备)都有一个32位的设备号,相当于设备文件ID信息。
    每个设备号 = 主设备号 + 次设备号

    typedef __u32 __kernel_dev_t;
    typedef __kernel_dev_t		dev_t;
    

    (二)设备号的运算函数
    1.MKDEV

    #define MINORBITS	20
    #define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
    

    设备号 = 高12位的主设备号[31:20] + 低20位的次设备号[19:0] 组成。

    通过设备号来获取主设备号或次设备号

    #define MINORMASK	((1U << MINORBITS) - 1)
    
    
    //获取主设备号
    #define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
    
    
    //获取次设备号
    #define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
    

    (三)如何申请设备号

    1.静态注册,register_chrdev_region

    #include <linux/fs.h>
    
    /**
     * register_chrdev_region() - register a range of device numbers
     * @from: the first in the desired range of device numbers; must include
     *        the major number.
     * @count: the number of consecutive device numbers required
     * @name: the name of the device or driver.
     *
     * Return value is zero on success, a negative error code on failure.
     */
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    
    

    参数说明:
    from,注册的设备号,如果一次要注册多个设备,from就是注册设备号的开始值。
    count,次设备的数量
    name,设备名称,但是该名称不是/dev目录下设备文件的名字,而是在/proc/devices文件当中。

    返回值:
    成功,返回0;
    失败,返回负数的错误码。

    补充说明:
    将该设备号注册到内核当中。如果该设备号在内核已经使用了,注册失败。参考内核源代码的设备号设置的文档\Documentation\devices.txt。

    ......................................................
    
     29 char	Universal frame buffer
    		  0 = /dev/fb0		First frame buffer
    		  1 = /dev/fb1		Second frame buffer
    		    ...
    		 31 = /dev/fb31		32nd frame buffer
    		 
    		 
     31 block	ROM/flash memory card
    		  0 = /dev/rom0		First ROM card (rw)
    		      ...
    		  7 = /dev/rom7		Eighth ROM card (rw)
    		  8 = /dev/rrom0	First ROM card (ro)
    		    ...
    		 15 = /dev/rrom7	Eighth ROM card (ro)
    		 16 = /dev/flash0	First flash memory card (rw)
    		    ...
    		 23 = /dev/flash7	Eighth flash memory card (rw)
    		 24 = /dev/rflash0	First flash memory card (ro)
    		    ...
    		 31 = /dev/rflash7	Eighth flash memory card (ro)
    
    		The read-write (rw) devices support back-caching
    		written data in RAM, as well as writing to flash RAM
    		devices.  The read-only devices (ro) support reading
    		only.
    
      234-239		UNASSIGNED
      240-254 char	LOCAL/EXPERIMENTAL USE
    		............................
    

    静态注册常用的主设备号可以从234开始进行尝试,在当前内核当中,没有分配登记的从234-239。谨记,不同的内核版本,主设备号占用的数值都会有所出入。

    2.动态分配,就是由内核自动分配空闲的设备号

    #include <linux/fs.h>
    
    /**
     * alloc_chrdev_region() - register a range of char device numbers
     * @dev: output parameter for first assigned number
     * @baseminor: first of the requested range of minor numbers
     * @count: the number of minor numbers required
     * @name: the name of the associated device or driver
     *
     * Allocates a range of char device numbers.  The major number will be
     * chosen dynamically, and returned (along with the first minor number)
     * in @dev.  Returns zero or a negative error code.
     */
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
    			const char *name)
    

    参数说明:
    dev,分配后的设备号
    baseminor,次设备号的开始值
    count,次设备的数量
    name,设备名称,但是该名称不是/dev目录下设备文件的名字,而是在/proc/devices文件当中可以查阅

    返回值:
    成功,返回0;
    失败,返回负数的错误码。

    1. 设备号的注销
    #include <linux/fs.h>
    
    /**
     * unregister_chrdev_region() - return a range of device numbers
     * @from: the first in the range of numbers to unregister
     * @count: the number of device numbers to unregister
     *
     * This function will unregister a range of @count device numbers,
     * starting with @from.  The caller should normally be the one who
     * allocated those numbers in the first place...
     */
    void unregister_chrdev_region(dev_t from, unsigned count)
    

    参数说明:
    from,注销的设备号,如果一次要注销多个设备,from就是注销设备号的开始值。
    count,次设备数量

    返回值:
    无。

    6.初始化设备

    (一)字符设备初始化

    #include <linux/fs.h>
    
    /**
     * cdev_init() - initialize a cdev structure
     * @cdev: the structure to initialize
     * @fops: the file_operations for this device
     *
     * Initializes @cdev, remembering @fops, making it ready to add to the
     * system with cdev_add().
     */
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    

    例子:

    cdev_init(&imx6ull_led_cdev,&imx6ull_led_fops);
    

    参数说明:
    cdev,要初始化的字符设备结构体
    fops,为该字符设备添加文件操作集

    返回值:
    无。

    (二)字符设备的加入与删除

    1.字符设备加载到内核

    #include <linux/fs.h>
    /**
     * cdev_add() - add a char device to the system
     * @p: the cdev structure for the device
     * @dev: the first device number for which this device is responsible
     * @count: the number of consecutive minor numbers corresponding to this
     *         device
     *
     * cdev_add() adds the device represented by @p to the system, making it
     * live immediately.  A negative error code is returned on failure.
     */
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    

    参数说明:
    p,初始化好的字符设备
    dev,设备号
    count,次设备的数量

    返回值:
    成功,返回0;
    失败,返回负数的错误码。

    2.将字符设备从内核删除

    #include <linux/fs.h>
    
    /**
     * cdev_del() - remove a cdev from the system
     * @p: the cdev structure to be removed
     *
     * cdev_del() removes @p from the system, possibly freeing the structure
     * itself.
     */
    void cdev_del(struct cdev *p)
    

    参数说明:
    p,初始化好的字符设备

    返回值:

    #demo1操作演示:
    1.将led_drv.ko加载到内核

    [root@GEC6818 /]#insmod led_drv.ko
    [ 0110.216000] gec6818 led init
    

    2.使用lsmod命令查看模块加载状况,该命令等同于cat proc/modules操作。

    [root@GEC6818 /]#lsmod
    led_drv 2244 0 - Live 0xbf004000 
    

    第一列:模块的名称,led_drv
    第二列:模块的大小,2244
    第三列:表示依赖该模块的个数,当前为0;若有一个程序调用会自加1;若有一个模块依赖该模块,也会自加1。
    第四列:模块的运行状态,live。
    第五列:模块的加载地址,0xbf004000。

    3.使用cat命令查看/proc/devices,能够找到注册成功的设备名字。

    
    [root@GEC6818 /]#cat /proc/devices 
    Character devices:
    ......
    239 gec6818_leds
    ......
    

    4.为该设备在/dev目录下创建设备文件

    [root@GEC6818 /]#mknod /dev/gec6818_leds c 239 0
    

    说明:
    mknod 设备文件 设备类型 主设备号 次设备号

    补充说明:
    1.手动创建设备文件的时候,主设备号和次设备号不能写错,否则应用程序打开设备文件的时候,会出现以下错误信息:

    [root@GEC6818 /]#./led_test
    open /dev/gec6818_leds:: No such device or address
    

    2.若此前的设备文件已经存在,再次创建相同名字的设备文件(若该设备文件是设备号正确的),会出现以下错误:

    [root@GEC6818 /]#mknod /dev/gec6818_leds c 244 0
    mknod: /dev/gec6818_leds: File exists
    

    解决方法:先使用rm命令删除原来的设备文件,再使用mknod命令重新创建。

    [root@GEC6818 /]#rm /dev/gec6818_leds 
    [root@GEC6818 /]#mknod /dev/gec6818_leds c 244 0
    

    3检查是否创建设备文件成功

    [root@GEC6818 /]#ls -l /dev/gec6818_leds
    crw-r--r--    1 root     root      239,   0 Jan  1 05:39 gec6818_leds
    
    6.  若没有创建设备文件,会提示以下错误信息:
    
    [root@GEC6818 /]#./led_test
    open: No such file or directory
    

    7.编写应用程序

    8.使用动态分配设备号

    使用静态注册设备号,有可能导致注册失败,因为linux内核不允许两个设备使用相同的设备号,为了杜绝该问题发生,可以由内核分配空闲的设备号给我们使用。

    关键代码:

    static int __init gec6818_led_init(void)
    {
    	int rt=0;
    
    	//动态申请设备号
    	rt=alloc_chrdev_region(&led_num,0,1,"gec6818_leds");
    	
    	if(rt < 0)
    	{
    		printk("alloc_chrdev_region fail\n");
    		
    		return rt;
    	}
    	
    	printk("led_major = %d\n",MAJOR(led_num));
    	printk("led_minor = %d\n",MINOR(led_num));	
    
    ......
    }
    
    

    2.操作演示

    [root@GEC6818 /]#insmod led_drv.ko
    [10329.957000] led_major = 244
    [10329.957000] led_minor = 0
    [10329.957000] gec6818 led init
    

    9.驱动程序与应用程序进行数据交换

    在用户空间和内核空间,它们数据交换是不能直接访问的,必须通过内核提供的函数实现数据的交换。

    #include <linux/uaccess.h>
    

    1.copy_to_user
    将内核空间的数据拷贝到用户空间

    static inline long copy_to_user(void __user *to,
    		const void *from, unsigned long n)
    {
    	might_sleep();
    	if (access_ok(VERIFY_WRITE, to, n))
    		return __copy_to_user(to, from, n);
    	else
    		return n;
    }
    

    参数说明:
    to:用户空间数据的地址
    from:内核空间数据的地址
    n:要拷贝的字节数

    返回值:
    0:拷贝成功
    非0值:不能被复制的字节数

    2.copy_from_user
    将用户空间的数据拷贝到内核空间

    static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
    {
    	if (access_ok(VERIFY_READ, from, n))
    		n = __copy_from_user(to, from, n);
    	else /* security hole - plug it */
    		memset(to, 0, n);
    	return n;
    	}
    

    参数说明:
    to:内核空间数据的地址
    from:用户空间数据的地址
    n:要拷贝的字节数

    返回值:
    0:拷贝成功
    非0值:不能被复制的字节数,也就是有多少个字节没有被成功拷贝

    附:常见的错误码

    #include <linux/errno.h>
    
    #define	EPERM		 1	/* Operation not permitted */
    #define	ENOENT		 2	/* No such file or directory */
    #define	ESRCH		 3	/* No such process */
    #define	EINTR		 4	/* Interrupted system call */
    #define	EIO		 5	/* I/O error */
    #define	ENXIO		 6	/* No such device or address */
    #define	E2BIG		 7	/* Argument list too long */
    #define	ENOEXEC		 8	/* Exec format error */
    #define	EBADF		 9	/* Bad file number */
    #define	ECHILD		10	/* No child processes */
    #define	EAGAIN		11	/* Try again */
    #define	ENOMEM		12	/* Out of memory */
    #define	EACCES		13	/* Permission denied */
    #define	EFAULT		14	/* Bad address */
    #define	ENOTBLK		15	/* Block device required */
    #define	EBUSY		16	/* Device or resource busy */
    #define	EEXIST		17	/* File exists */
    #define	EXDEV		18	/* Cross-device link */
    #define	ENODEV		19	/* No such device */
    #define	ENOTDIR		20	/* Not a directory */
    #define	EISDIR		21	/* Is a directory */
    #define	EINVAL		22	/* Invalid argument */
    #define	ENFILE		23	/* File table overflow */
    #define	EMFILE		24	/* Too many open files */
    #define	ENOTTY		25	/* Not a typewriter */
    #define	ETXTBSY		26	/* Text file busy */
    #define	EFBIG		27	/* File too large */
    #define	ENOSPC		28	/* No space left on device */
    #define	ESPIPE		29	/* Illegal seek */
    #define	EROFS		30	/* Read-only file system */
    #define	EMLINK		31	/* Too many links */
    #define	EPIPE		32	/* Broken pipe */
    #define	EDOM		33	/* Math argument out of domain of func */
    #define	ERANGE		34	/* Math result not representable */
    

    注:
    若驱动层返回错误码,应用层则需要调用perror函数就能够识别到该错误码。

    #demo3关键代码及操作演示

    10.自动创建设备节点

    (一)背景
    Linux 2.6 引入了动态设备管理, 用 udev 作为设备管理器(应用在x86), 相比之前的静态设备管理,在使用上更加方便灵活。
    udev 根据 sysfs 系统提供的设备信息实现对/dev 目录下设备节点的动态管理,包括设备节点的创建、删除等。
    在当前开发板是使用udev的简化版——mdev,常用在嵌入式系统中,作用是在系统启动和热插拔或动态加载驱动程序时,自动创建设备节点。文件系统的/dev目录下的设备节点都是由mdev创建的。

    举个例子:
    insmod led_drv.ko时候,mdev自动帮我们在/dev目录下创建设备节点
    rmmod led_drv时候,mdev自动帮我们在/dev目录下删除设备节点

    若要编写一个能用 mdev 管理的设备驱动,需要在驱动代码中调用 class_create()为设备创建一个 class 类,再调用 device_create()为每个设备创建对应的设备。

    (二)参考三星公司创建类与设备的方法

    #include <linux/device.h>
    
    static struct class  *adb_dev_class;
    static struct device *adb_dev_device;
    
    static int __init adbdev_init(void)
    {
    ...............
    	if (register_chrdev(ADB_MAJOR, "adb", &adb_fops)) {
    		printk(KERN_ERR "adb: unable to get major %d\n", ADB_MAJOR);
    		return;
    	}
    
    	adb_dev_class = class_create(THIS_MODULE, "adb");
    
    	if (IS_ERR(adb_dev_class))
    		return PTR_ERR(adb_dev_class);
    
    	adb_dev_device=device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
    
    	if (IS_ERR(adb_dev_device))
        {
            class_destroy(adb_dev_class);
            return PTR_ERR(adb_dev_device);
        }
    
        return 0;	
    }
    

    (三)创建一个类

    #include <linux/device.h>
    
    ..............................
    /* This is a #define to keep the compiler from merging different
     * instances of the __key variable */
    #define class_create(owner, name)		\
    ({						\
    	static struct lock_class_key __key;	\
    	__class_create(owner, name, &__key);	\
    })
    ..............................
    
    /**
     * class_create - create a struct class structure
     * @owner: pointer to the module that is to "own" this struct class
     * @name: pointer to a string for the name of this class.
     * @key: the lock_class_key for this class; used by mutex lock debugging
     *
     * This is used to create a struct class pointer that can then be used
     * in calls to device_create().
     *
     * Returns &struct class pointer on success, or ERR_PTR() on error.
     *
     * Note, the pointer created here is to be destroyed when finished by
     * making a call to class_destroy().
     */
    struct class *__class_create(struct module *owner, const char *name,
    			     struct lock_class_key *key)
    

    参数:
    owner:class的所有者,默认写THIS_MODULE
    name:自定义class的名字,会显示在/sys/class目录当中

    返回值:
    成功:就返回创建好的class指针
    失败:就返回错误码指针

    如何判断当前的指针为错误码,使用IS_ERR函数进行判断。

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

    如果IS_ERR返回值大于0该指针就为错误码指针。如果返回0值,就是正确的指针。

    2.创建属于class的设备

    /**
     * device_create - creates a device and registers it with sysfs
     * @class: pointer to the struct class that this device should be registered to
     * @parent: pointer to the parent struct device of this new device, if any
     * @devt: the dev_t for the char device to be added
     * @drvdata: the data to be added to the device for callbacks
     * @fmt: string for the device's name
     *
     * This function can be used by char device classes.  A struct device
     * will be created in sysfs, registered to the specified class.
     *
     * A "dev" file will be created, showing the dev_t for the device, if
     * the dev_t is not 0,0.
     * If a pointer to a parent struct device is passed in, the newly created
     * struct device will be a child of that device in sysfs.
     * The pointer to the struct device will be returned from the call.
     * Any further sysfs files that might be required can be created using this
     * pointer.
     *
     * Returns &struct device pointer on success, or ERR_PTR() on error.
     *
     * Note: the struct class passed to this function must have previously
     * been created with a call to class_create().
     */
    struct device *device_create(struct class *class, struct device *parent,
    			     dev_t devt, void *drvdata, const char *fmt, ...)
    

    参数:
    class:创建device是属于哪个类
    parent:默认为NULL
    devt:设备号,设备号必须正确,因为这个函数会在/dev目录下帮我们自动创建设备文件
    drvdata:默认为NULL
    fmt:设备的名字,如果创建成功,就可以在/dev目录看到该设备的名字

    返回值:
    成功:就返回创建好的设备指针
    失败:就返回错误码指针

    (五)类的销毁

    /**
     * class_destroy - destroys a struct class structure
     * @cls: pointer to the struct class that is to be destroyed
     *
     * Note, the pointer to be destroyed must have been created with a call
     * to class_create().
     */
    void class_destroy(struct class *cls)
    

    参数:

    class:创建device是属于哪个类

    (六)设备的销毁

    /**
     * device_destroy - removes a device that was created with device_create()
     * @class: pointer to the struct class that this device was registered with
     * @devt: the dev_t of the device that was previously registered
     *
     * This call unregisters and cleans up a device that was created with a
     * call to device_create().
     */
    void device_destroy(struct class *class, dev_t devt)
    

    参数:
    class:创建device是属于哪个类
    devt:设备号

    (七)错误码指针的判断

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

    (八)将错误码指针转换为数值(即错误码)

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

    附:关键代码

    static struct class 	*leds_class;
    static struct device 	*leds_device;
    
    //入口函数
    static int __init gec6818_led_init(void)
    {
    	..................
    	//创建类
    	leds_class=class_create(THIS_MODULE, "myled");
    	
    	if (IS_ERR(leds_class))
    	{
    		rt = PTR_ERR(leds_class);
    		
    		printk("class_create gec6818_leds fail\n");
    		
    		goto fail_class_create;
    		
    	}
    	
    	//创建设备
    	leds_device=device_create(leds_class, NULL, led_num, NULL, "gec6818_leds");
    	
    	if (IS_ERR(leds_device))
    	{
    		rt = PTR_ERR(leds_device);
    		
    		printk("device_create gec6818_leds fail\n");
    		
    		goto fail_device_create;
    		
    	}	
    ..................
    }
    
    //出口函数
    static void __exit gec6818_led_exit(void)
    {
    ..................
    	device_destroy(leds_class,led_num);
    	class_destroy(leds_class);
    ..................
    }
    

    检查是否创建myled设备类型成功。

    [root@GEC6818 /sys/class]#ls
    myled
    

    检查是否在gec6818_leds类,创建gec6818_leds设备成功

    [root@GEC6818 /sys/devices/virtual/myled/gec6818_leds]#cat uevent 
    MAJOR=244
    MINOR=0
    DEVNAME=gec6818_leds
    

    检查是否创建gec6818_leds设备文件成功。

    [root@GEC6818 /]#ls /dev
    gec6818_leds
    
    展开全文
  • Linux 字符设备——字符CAN驱动开发 刚开发完can的字符设备驱动,本想总结一下,但看到一篇博文,对字符设备驱动总结的很全面,将我想到和没想到的都总结了,顿然有种班门弄斧的感觉,遂不敢献丑,故在此贴出地址,...

    Linux 字符设备——字符CAN驱动开发

    刚开发完can的字符设备驱动,本想总结一下,但想到之前总结过一篇字符设备的博文,对字符设备驱动总结的很全面,故在此贴出地址,以飨各位:Linux字符设备驱动 - Leo Hou

    或公众号文章:

    Linux字符设备驱动

    一、字符设备

      字符设备是指在I/O传输过程中以字符为单位进行传输的设备,是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED等。
      字符设备会在linux系统的/dev目录下对应一个设备文件。字符设备可以使用与普通文件相同的文件操作命令对字符设备文件进行操作,例如打开、关闭、读、写等。

    二、字符设备驱动模型

      Linux系统下字符设备驱动主要有2部分:用户态设备文件和内核态驱动中文件操作函数(见图2.1)。用户态的进程想和硬件设备交互时,首先要通过系统调用操作(read()、write()、ioctl()等)字符设备文件,字符设备驱动然后调用file_operations 中注册的操作函数和硬件交互。

    图2.1

     

    三、字符设备驱动构建

      Linux系统下构建字符设备驱动主要有部分要实现:驱动初始化、设备操作、驱动注销(见图3.1)。
    a. 驱动初始化:
    需要完成cdev的分配、cdev初始化、注册cdev及硬件设备初始化。
    b. 设备操作:
    需要填充 struct file_operations 结构体中断的操作函数,实现 struct file_operations 结构体中
    的read()、write()和ioctl()等函数是驱动设计的主体工作。
    c. 驱动注销:
    即释放设备号、删除、注销cdev。

    图3.1

     

    3.1 struct cdev

    <include/linux/cdev.h>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    struct cdev {   
      struct kobject kobj;                  //内嵌的内核对象.  
      struct module *owner;                 //该字符设备所在的内核模块(所有者)的对象指针,一般为THIS_MODULE主要用于模块计数  
      const struct file_operations *ops;    //该结构描述了字符设备所能实现的操作集(打开、关闭、读/写、...),是极为关键的一个结构体
      struct list_head list;                //用来将已经向内核注册的所有字符设备形成链表
      dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成(如果是一次申请多个设备号,此设备号为第一个)
      unsigned int count;                   //隶属于同一主设备号的次设备号的个数
      ...
    };  
    

    3.2 cdev初始化

      对于struct cdev,内核在头文件linux/cdev.h中提供了相应操作接口:

    3.2.1 为cdev分配内存

    1
    2
    3
    4
    
    struct cdev *cdev_alloc(void);  
    /* 返回值:
        成功 cdev 对象首地址
        失败:NULL */
    

    3.2.2 初始化cdev

      初始化cdev的成员变量,并建立cdev和file_operations之间的关联。

    1
    2
    3
    4
    
    void cdev_init(struct cdev *p, const struct file_operations *p);  
    /* 参数:
        struct cdev *p - 被初始化的 cdev对象
        const struct file_operations *fops - 字符设备操作方法集 */
    

      

    3.2.3 注册cdev设备对象

      注册cdev设备对象,添加到系统字符设备列表中。

    1
    2
    3
    4
    5
    6
    7
    8
    
    int cdev_add(struct cdev *p, dev_t dev, unsigned count);
    /* 参数:
        struct cdev *p - 被注册的cdev对象
        dev_t dev - 设备的第一个设备号
        unsigned - 这个设备连续的次设备号数量
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/
    

    3.2.4 设备号申请

      一个字符设备有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
      linux内核中,设备号用dev_t来描述:

    1
    
    typedef u_long dev_t;  // 在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。 
    

      内核在头文件 linux/fs.h中提供了几个方便操作的宏定义来实现dev_t: 

    1
    2
    3
    4
    
    #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))  // 从设备号中提取主设备号
    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))  // 从设备号中提取次设备号
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>  // 将主、次设备号拼凑为设备号
    /* 只是拼凑设备号,并未注册到系统中,若要使用需要竞态申请 */
    

    3.2.4.1 - 静态申请设备号

    1
    2
    3
    4
    5
    6
    7
    8
    
    int register_chrdev_region(dev_t from, unsigned count, const char *name);
    /* 参数:
        dev_t from - 要申请的设备号(起始)
        unsigned count - 要申请的设备号数量
        const char *name - 设备名
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/
    

    3.2.4.2 - 动态分配设备号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
    /* 参数:
        dev_t *dev - 用于保存分配到的第一个设备号(起始)
        unsigned baseminor - 起始次设备号
        unsigned count - 要分配设备号的数量
        const char *name - 设备名
       返回值:
        成功:0
        失败:负数(绝对值是错误码)*/
    

    3.2.4.3 创建设备文件

      利用cat /proc/devices查看申请到的设备名,设备号。

    a. 使用mknod手工创建:

      mknod filename type major minor

    b. 自动创建设备节点:

      利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

    3.3 file_operations *fops

      Linux下一切皆文件,字符设备也是抽象成设备文件,struct cdev 中的file_operations结构体中的成员函数是字符设备程序设计的主题内容,这些函数实际会在用户层程序进行Linux的open()、close()、write()、read()等系统调用时最终被调用。 文件的操作接口结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    struct file_operations {
      struct module *owner;  
        /* 模块拥有者,一般为 THIS——MODULE */
      ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
        /* 从设备中读取数据,成功时返回读取的字节数,出错返回负值(绝对值是错误码) */
      ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);   
        /* 向设备发送数据,成功时该函数返回写入字节数。若为被实现,用户调层用write()时系统将返回 -EINVAL*/
      int (*mmap) (struct file *, struct vm_area_struct *);  
        /* 将设备内存映射内核空间进程内存中,若未实现,用户层调用 mmap()系统将返回 -ENODEV */
      long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg);  
        /* 提供设备相关控制命令(读写设备参数、状态,控制设备进行读写...)的实现,当调用成功时返回一个非负值 */
      int (*open) (struct inode *, struct file *);  
        /* 打开设备 */
      int (*release) (struct inode *, struct file *);  
        /* 关闭设备 */
      int (*flush) (struct file *, fl_owner_t id);  
        /* 刷新设备 */
      loff_t (*llseek) (struct file *, loff_t, int);  
        /* 用来修改文件读写位置,并将新位置返回,出错时返回一个负值 */
      int (*fasync) (int, struct file *, int);  
        /* 通知设备 FASYNC 标志发生变化 */
      unsigned int (*poll) (struct file *, struct poll_table_struct *);  
        /* POLL机制,用于询问设备是否可以被非阻塞地立即读写。当询问的条件未被触发时,用户空间进行select()和poll()系统调用将引起进程阻塞 */
      ...
    };
    

    3.4 cdev注销

    3.4.1 释放设备号

    1
    2
    3
    4
    
    void unregister_chrdev_region(dev_t from, unsigned count);
    /* 参数:
        dev_t from - 要释放的第一个设备号(起始)
        unsigned count - 要释放的次设备号数量 */
    

    3.4.2 注销cdev

    将cdev对象从系统中移除

    1
    2
    3
    
    void cdev_del(struct cdev *p);
    /*参数: 
        struct cdev *p - 要移除的cdev对象 */
    

    3.4.3 释放cdev内存

    1
    2
    3
    
    void cdev_put(struct cdev *p);
    /*参数:
        struct cdev *p - 要移除的cdev对象 */
    展开全文
  • Linux下字符设备驱动

    2021-05-13 15:18:41
    【linux操作系统驱动分类】字符设备: 串口,led,按键,一次只读取一个字节数据块设备: 一次读取多个字节(512字节), 硬盘,内存网络设备: 一次读取多字节数据本文引用地址:... 串口,Led,I2Cb开头的叫块设备文件 ...
  • Character Device Drive又被称为字符设备或裸设备raw devices; Block Device Driver通常成为块设备。而Block Device Driver是以固定大小长度来传送转移资料 ;Character Device Driver是以不定长度的字元传送资料 。...
  • Linux驱动开发:字符设备驱动开发
  • 关于块设备 和 字符设备 介绍:                 系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称作块设备,这些数据片就称作块。     &...
  • (三)字符设备

    千次阅读 2020-10-14 16:41:19
    1、字符设备—c 应用程序和驱动程序之间进行数据读写的时候,数据是以“字节”为单位。数据交互的时候,是按照固定的顺序传输的;数据是实时传输的,是没有缓存的。字符设备是没有文件系统的。 绝大部分设备驱动是...
  • 字符设备、块设备、网络设备详解

    千次阅读 2019-10-09 19:23:23
    字符设备、块设备、网络设备 设备模型 设备驱动的代码量占内核程序的50% 设备模型的意义: 为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作...
  • Linux设备类型主要分为:字符设备、块设备以及网络设备。 这里主要记录一下字符设备和块设备: 字符设备字符设备按照字符流的方式被有序访问,提供连续的数据流。应用程序可以顺序读取,通常不支持随机存取。...
  • 常见字符设备:鼠标 键盘 串口 控制台 led设备 块设备:是指可以从设备的任意位置读取一定长度数据的设备。 常见的块设备:硬盘 磁盘 u盘 光盘 sd卡。。。1.2 字符设备框架init: { 申请设备号(静态申请 动态...
  • 一、块设备 ... 字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符设备。提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据...
  • 其中,字符设备驱动程序是最常见的,也是相对比较容易理解的一种。其典型的程序框架示例,如下: #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <...
  • linux 设备驱动一、概述1.1 设备驱动介绍二、字符设备驱动2.1. 字符设备驱动简介2.2. LCD驱动 一、概述 1.1 设备驱动介绍 二、字符设备驱动 2.1. 字符设备驱动简介 2.2. LCD驱动 感谢阅读,祝君成功! -by aiziyou ...
  • linux新字符设备驱动

    2022-01-18 09:51:00
    linux 新字符设备驱动1 linux系统设备分类1.1 linux设备驱动之字符设备驱动linux设备驱动之字符设备驱动1.2 字符设备字符设备驱动与用户空间访问该设备的程序三者之间的关系1.3 用户空间访问该设备的程序2 字符...
  • 比如我们最常见的点灯、按键、IIC、SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。 /* 打开设备*/ static int chrdevbase_open(struct inode *inode, struct file *filp) { return 0; } ...
  • 一、实验目的 通过一个简单的设备驱动的实现过程。学会Linux中设备驱动程序的编写。 二、实验环境 Ubuntu20.04TSL,Linux5.10.0 三、实验内容 1、编写一个字符设备驱动程序...字符设备是面向流的设备,常见字符设备
  • 字符设备是 Linux 驱动中最基本的一类设备驱动,字节设备就是按照字节流来读写的设备,常见字符设备包括:LED、蜂鸣器、按键、I2C 以及 SPI 等。
  • 字符设备驱动

    千次阅读 2021-02-19 19:51:24
    比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。 在详细的学习字符设备驱动架构之前,先简单的了解一下 Linux 下的应用程序是如何调用驱动程序的, Linux 应用...
  • Linux字符设备(一)

    千次阅读 2020-02-27 19:37:14
    Linux字符驱动 ...字符设备是面向流的设备,常见字符设备有鼠标、键盘、串口、控制台和LED设备等。 块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等...
  • 字符设备、块设备、网络设备

    千次阅读 多人点赞 2017-08-25 10:48:33
    字符设备、块设备、网络设备设备模型设备驱动的代码量占内核程序的50%设备模型的意义: 为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作Driver ...
  • 字符设备与块设备的区别

    万次阅读 多人点赞 2019-06-18 13:46:31
    字符设备与块设备的区别 字符设备与块设备的区别 在LINUX里面,设备类型分为:字符设备、块设备以及网络设备,PCI是一种和ISA为一类的总线结构,归属于网络驱动设备~~~ 字符设备、块设备主要区别是:在对字符设备...
  • 驱动开发(1)——字符设备驱动

    千次阅读 2021-09-25 15:29:30
    比如我们常见的点灯、按键、IIC、SPI、LCD等都是字符设备,这些设备的驱动就叫做字符设备驱动。 在Linux中开发一般只能是用户态,也就是用户只能编写应用程序,但是要作用于内核,那么就需要了解Linux中应用程序是...
  • 从设备两个字我们应该了解它们属于操作系统的哪个部分,和什么部分交互,以及有什么性质和特征。 块设备 是一种具有一定结构的随机存取设备,对这种设备的读写是...另一种基本的设备类型是字符设备。是一个顺序的数据
  • 操作系统实验·字符设备驱动程序

    千次阅读 2021-12-10 23:08:56
    编写一个简单的字符设备驱动程序,该字符设备并不驱动特定的硬件, 而是用内核空间模拟字符设备,要求该字符设备包括以下几个基本操作,打开、读、写和释放,并编写测试程序用于测试所编写的字符设备驱动程序。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 319,682
精华内容 127,872
关键字:

常见的字符设备