Linux驱动之 字符设备register_chrdev_region、cdev_init、cdev_add、unregister_chrdev_region、cdev_del

Linux驱动–字符设备学习记录:
所谓设备驱动程序就是控制与管理硬件设备数据收发的软件,它是应用程序与硬件设备沟通的桥梁。

从本质上讲驱动程序主要负责硬件设备的数据读写,参数配置与中断处理。

设备驱动程序是操作系统的一部分,通常运行在内核层。
硬件设备多种多样,使得设备驱动程序繁多,设备模型将硬件设备分类,抽象出一套标准的数据结构。

/sys/class目录下是按照设备功能分类的设备模型,
如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。
它也是构成 Linux 统一设备模型的一部分

root@ubuntu16:/sys/class# ls
ata_device   devfreq-event   hidraw       mem       power_supply  scsi_device    thermal
ata_link     dma             hmm_device   misc      ppdev         scsi_disk      tpm
ata_port     dmi             hwmon        mmc_host  ppp           scsi_generic   tpmrm
backlight    drm             i2c-adapter  nd        printer       scsi_host      tty
bdi          drm_dp_aux_dev  i2c-dev      net       pwm           sound          vc
block        extcon          input        pci_bus   rapidio_port  spi_host       virtio-ports
bsg          firmware        iommu        pci_epc   regulator     spi_master     vtconsole
devcoredump  gpio            leds         phy       rfkill        spi_slave      watchdog
devfreq      graphics        mdio_bus     powercap  rtc           spi_transport
root@ubuntu16:/sys/class# 
Linux系统为了管理方便,将设备分成三种基本类型:字符设备、块设备、网络设备

字符设备,字符设备文件(类型为c)

  • 字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。
    字符设备驱动程序通常至少要实现open、close、read和write的系统调用。
    在这里插入图片描述
    设备文件是没有文件大小的,取而代之的是两个号码:主设备号 +次设备号。

块设备,块设备文件(类型为b)

  • 和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备(例如磁盘)上能够容纳filesystem。在大多数的Unix系统中,进行I/O操作时块设备每次只能传输一个或多个完整的块,而每块包含512字节(或2的更高次幂字节的数据)。
    Linux可以让app像字符设备一样地读写块设备,允许一次传递任意多字节的数据。因此,块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的。在内核中,和字符驱动程序相比,块驱动程序具有完全不同的接口。
    在这里插入图片描述

网络设备

任何网络事物都需要经过一个网络接口形成,网络接口是一个能够和其他主机交换数据的设备。接口通常是一个硬件设备,但也可能是个纯软件设备,比如回环(loopback)接口。
网络接口由内核中的网络子系统驱动,负责发送和接收数据包。许多网络连接(尤其是使用TCP协议的连接)是面向流的,但网络设备却围绕数据包的传送和接收而设计。网络驱动程序不需要知道各个连接的相关信息,它只要处理数据包即可。
由于不是面向流的设备,因此将网络接口映射到filesystem中的节点(比如/dev/tty1)比较困难。
Unix访问网络接口的方法仍然是给它们分配一个唯一的名字(比如ens33),但这个名字在filesystem中不存在对应的节点。内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包相关的函数socket,也叫套接字。
查看网络设备使用命令ifconfig:
在这里插入图片描述

查看系统已使用主设备号: cat /proc/devices
cxx@ubuntu16:~$ cat /proc/devices 
Character devices: //字符设备
  1 mem
  4 /dev/vc/0
  4 tty
、、、
247 hmm_device
248 watchdog
249 rtc
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip

Block devices: //块设备
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
、、、
135 sd
253 device-mapper
254 mdp
259 blkext
cxx@ubuntu16:~$ 
二、字符设备架构是如何实现的?

在Linux的世界里面一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。Linux内核中有那么多驱动程序,应用层怎么才能精确的调用到底层的驱动程序呢?
在这里我们字符设备为例,来看一下应用程序是如何和底层驱动程序关联起来的。
1.在Linux文件系统中,每个文件都用一个struct inode结构体来描述,这个结构体里面记录了这个文件的所有信息,例如:文件类型,访问权限等。
2.在Linux操作系统中,每个驱动程序在应用层的/dev目录下都会有一个设备文件和它对应,并且该文件会有对应的主设备号和次设备号。
3.在Linux操作系统中,每个驱动程序都要分配一个主设备号,字符设备的设备号保存在struct cdev结构体中。

如果想访问底层设备,就必须打开对应的设备文件。也就是在这个打开的过程中,Linux内核将应用层和对应的驱动程序关联起来。

1.当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备)。还会分配一个struct file结构体。

2.根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备有一个struct cdev结构体。此结构体描述了字符设备所有的信息,其中最重要一项的就是字符设备的操作函数接口。

3.找到struct cdev结构体后,Linux内核就会将struct cdev结构体所在的内存空间首地记录在struct inode结构体的i_cdev成员中。将struct cdev结构体的中记录的函数操作接口地址记录在struct file结构体的f_op成员中。

4.任务完成,VFS层会给应用层返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层的应用程序就可以通过fd来找到strut file,然后在由struct file找到操作字符设备的函数接口了。

cdev数据结构:
 struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;//接口函数集合
        struct list_head list;//内核链表
        dev_t dev;    //设备号
        unsigned int count;//次设备号个数
    };
cdev 初始化函数
/*
 * @description		: 初始化cdev结构体
 * @param - cdev 	: cdev结构体地址
 * @param - fops 	: 操作字符设备的函数接口地址
 * @return 			: 无
 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
register_chrdev_region
/*
 * @description		: 注册一个范围()的设备号
 * @param - from 	: 设备号
 * @param - count	: 注册的设备个数
 * @param -	name 	:设备的名字
 * @return 			: 成功返回0,失败返回错误码(负数)
 */										
int register_chrdev_region(dev_t from, unsigned count, const char *name)
cdev_add
/*
 * @description		: 添加一个字符设备到操作系统
 * @param - p		: cdev结构体地址
 * @param - dev		: 设备号
 * @param -	count	:次设备号个数
 * @return 			: 成功返回0,失败返回错误码(负数)
 */	
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
cdev_del
/*
 * @description		: 从系统中删除一个字符设备
 * @param - p		: cdev结构体地址
 * @param - dev		: 设备号
 * @return 			:  无
 */	
void cdev_del(struct cdev *p)
register_chrdev
/*
 * @description		: 注册或者分配设备号,并注册fops到cdev结构体
 * @param - major	: 主设备号,如果major>0,功能为注册该主设备号,major=0,功能为动态分配主设备号。
 * @param - name	: 设备名称,执行 cat /proc/devices显示的名称
 * @param -	fops 	:文件系统的接口指针
 * @return 			: 如果major>0   成功返回0,失败返回负的错误码,major=0  成功返回主设备号,失败返回负的错误码
 */	
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
unregister_chrdev_region
/*
 * @description		: 注销一个范围()的设备号
 * @param - from 	: 设备号
 * @param - count	: 注册的设备个数
 * @return 			: 无
 */	
void unregister_chrdev_region(dev_t from, unsigned count)
vim hellocdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>

static int major = 222; //主设备号
static int minor = 0; //次设备号
static dev_t hello_devno; //合成设备号
static struct cdev hello_cdev;

static int hellocdev_open (struct inode *inode, struct file *filep)
{
        printk("hellocdev_open()\n");
        return 0;
}
static struct file_operations hellocdev_ops = 
{
        .open = hellocdev_open,
};
static int hellocdev_init(void)
{
        int result;
        int error;
        printk("hellocdev_init \n");
        hello_devno = MKDEV(major,minor);       //把主设备号和次设备号合成dev_t
        result = register_chrdev_region(hello_devno, 1, "hellocdev");
        if(result<0)
        {
                printk("register_chrdev_region fail \n");
                return result;
        }
        cdev_init(&hello_cdev,&hellocdev_ops);
        error = cdev_add(&hello_cdev,hello_devno,1);
        if(error < 0)
        {
                printk("cdev_add fail \n");
                unregister_chrdev_region(hello_devno,1);
                dev_del(&hello_cdev); //删除cdev
                return error;
        }
        return 0;
}
static void hellocdev_exit(void)
{
        printk("hellocdev_exit \n");    
        unregister_chrdev_region(hello_devno,1); //注销设备
        cdev_del(&hello_cdev); //删除cdev
        return;
}

MODULE_LICENSE("GPL");
module_init(hellocdev_init);
module_exit(hellocdev_exit);

//proc/devices
make && insmod
root@ubuntu16:/home/cxx/driver/cdev# make clean
rm -f *.ko *.o *.mod.o *.symvers *.cmd  *.mod.c *.order
root@ubuntu16:/home/cxx/driver/cdev# make
make -C /lib/modules/4.15.0-142-generic/build M=/home/cxx/driver/cdev modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
  CC [M]  /home/cxx/driver/cdev/hellocdev.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/cxx/driver/cdev/hellocdev.mod.o
  LD [M]  /home/cxx/driver/cdev/hellocdev.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
root@ubuntu16:/home/cxx/driver/cdev# lsmod | grep hellocdev
root@ubuntu16:/home/cxx/driver/cdev# insmod hellocdev.ko
root@ubuntu16:/home/cxx/driver/cdev# dmesg 
[32902.939675] hellocdev_init 
root@ubuntu16:/home/cxx/driver/cdev# cat /proc/devices | grep hellocdev
222 hellocdev
root@ubuntu16:/home/cxx/driver/cdev#  

mknod /dev/hellocdev c 222 0

手动创建设备文件:mknod 设备文件名[/dev/xyz] b/c 主设备号 次设备号

root@ubuntu16:/home/cxx/driver/cdev# mknod /dev/hellocdev c 222 0 //c:字符设备文件 b:块设备
root@ubuntu16:/home/cxx/driver/cdev# ls /dev/hellcodev -l
crw-r--r-- 1 root root 222, 0 Apr 24 17:46 /dev/hellocdev
vim demo.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
        int fd;
        fd = open("/dev/hellocdev",O_RDWR);
        if(fd<0)
        {
                perror("open fail \n");
                return -1;
        }
        printf("hellocdev open ok \n ");
        close(fd);
        return 0;
}
./demo
root@ubuntu16:/home/cxx/driver/cdev# gcc demo.c -o demo
root@ubuntu16:/home/cxx/driver/cdev# ./demo
hellocdev open ok 
root@ubuntu16:/home/cxx/driver/cdev# 
rmmod && dmesg
root@ubuntu16:/home/cxx/driver/cdev# rmmod hellocdev.ko
root@ubuntu16:/home/cxx/driver/cdev# dmesg 
[23373.355620] hellocdev_init 
[24474.220663] hellocdev_open()
[29690.708345] hellocdev_exit 
root@ubuntu16:/home/cxx/driver/cdev# 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程一时爽Cxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值