linux字符设备驱动

linux系统设备的3种类型:字符设备驱动、块设备驱动和网络设备驱动
字符设备:只能一个一个字节读写数据的设备,不能随机读取设备内存中的某一数据
块设备:可以从设备的任意位置读取一定长度数据的设备
在 /dev 目录中 执行 ls-l 时
第一个字母为c表示该设备为字符设备,为b表示块设备

主设备号和次设备号

一个字符设备或者块设备都有一个主设备号和一个此设备号,统称为设备号。主设备号用来表示一个特定的驱动程序,此设备号用来表示使用该驱动程序的各设备。

1.主设备号和次设备号的表示

linux内核中 dev_t类型用来表示设备号,在linux 2.6.34.14中

typedef u_long dev_t

u_long 在32位机中是4个字节,64位机中是8字节,32位中,高12位为主设备号,低20位为次设备号。

2.主设备号和次设备号的获取

为了保障可移植性,应该使用宏MAJOR和MINOR获取主设备号和次设备号,MKDEV宏生成设备号,宏的定义如下:

#define MINORBITS 20                 //次设备号的位数
#define MINORMASK ((1U << MINORBITS)-1)  //次设备号掩码  有MINORBITS个1
#define MAJOR(dev)  ((unsigned int) ((dev) >> MINORBITS)) 
                                    //dev右移20位得到主设备号
#define MINOR(dev)  ((unsigned int) ((dev) &MINORMASK))
                                    //与次设备号掩码相与得到次设备号

#define MKDEV(ma,mi) (((ma)<<MINORBITS|(mi))
3.分配设备号

驱动开发者静态指定一个设备号,内核开发者已经为常用设备分配了设备号,在内核源码/documentation/devices.txt 中可找到,此方法很可能造成设备号冲突

建议使用方法,动态分配设备号函数为 alloc_chrdev_region()

  • 静态申请设备号

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

form :要分配的设备号范围的起始值,一般只提供form的主设备号,次设备号通常设置为0
count:需要申请的连续设备号的个数
name:和该范围编号关联的设备名称,不能超过64字节
成功返回0

  • 动态获取设备号

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

dev : 输出参数,函数成功后保存分配到的设备号或者连续设备号的第一个
baseminor : 要申请的第一个次设备号 通常设为0
count : 要申请的连续设备号个数 与register_chrdev_region()函数参数相同
name : 设备名字 与register_chrdev_region()函数参数相同

  • 释放设备号
    不使用设备的时候应该释放设备号

void unregister_chardev_region(dev_t form ,unsigned count);

form:要释放的设备号
count:从form开始要释放的设备号个数

4.查看设备号

当静态分配设备号时,需要查看文件系统中已经存在的设备号,从而决定使用哪个设备号,方法为读取/proc/devices文件,如下所示

can@ubuntu:~/c/hehe$ cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
14 sound/midi
14 sound/dmmidi
21 sg
29 fb

Block devices:
1 ramdisk
259 blkext
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
68 sd
69 sd

cdev结构

当申请字符设备的设备号之后,需要将字符设备注册到系统中,才能使用字符设备。
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; //使用该字符设备驱动的设备数量
}

list 结构是一个双向链表,用于将其他结构体连接成一个双向链表。该结构在linux内核中广泛使用

struct list_head {
    struct list_head *next,*prev;
}

cdev结构体的list成员连接到了inode结构体i_devices成员。其中i_devices也是一个list_head结构。cdev结构与inode结点组成了一个双向链表。
这里写图片描述

每一个字符设备在/dev目录下都有一个设备文件,打开设备文件就相当于打开相应字符设备。例如应用程序打开设备文件A,那么系统会产生一个inode结点,通过inode结点的i_cdev字段找到cdev结构体,通过cdev的ops指针,就能找到设备的操作函数。

file_operations结构体

在 Linux/fs.h 中定义,对设备进行操作的抽象结构体,常用的函数有open(),read(),write(),close(),ioctl()等
某个版本linux内核中的file_operation结构体定义

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(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
    ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t,loff_t);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int,unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *, 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(*readv) (struct file *, const struct iovec *, unsigned long,loff_t *);
    ssize_t(*writev) (struct file *, const struct iovec *, unsigned long,loff_t *);
    ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t,void __user *);
    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);
};

重要成员:
owner:不是一个函数,是一个指向这个结构模块的指针,用来维持引用计数,当模块还在使用时,不能使用rmmod卸载模块,几乎所有时刻被简单初始化为 THIS_MODULE 一个在linux/module.h中的宏定义
llseek():改变文件中的当前读写位置,将新的位置返回 loff_t类型为 long long类型
read() : 从设备获取数据,成功返回读取字节数,失败返回负的错误编码
write() : 用来邪道设备中,成功返回写入字节数,失败返回负错误码
ioctl() : 执行设备特定命令的方法
open() : 打开一个设备,如果被复制为NULL,那么设备永远打开成功,并不会对设备产生影响
release(): 释放被open()函数中申请的资源,将在文件引用计数为0时被系统调用,对应应用程序的close()方法,但仅当对设备文件的所有打开都被释放后才会被调用

字符设备驱动的组成

常见的设备结构体、加载函数和卸载函数如下:

struct xxx_dev{           //自定义设备结构体
    struct cdev cdev;       //cdev结构体
    ...
};

static int __init xxx_init(void)
{
    `...
    //申请设备号,当XXX_major不为0时,表示静态指定,为0时表示动态申请
    if(xxx_major)
        result = register_chrdev_region(xxx_devno,1,"DEV_NAME"); //静态申请设备号
    else
    {
        result = alloc_chrdev_region(&xxx_devno,0,1,"DEV_NAME");  
        xxx_major  MAJOR(xxx_devno);//获取申请的主设备号
    }

    //初始化cdev结构,传递file_operation结构指针
    cdev_init(&xxx_dev.cdev,&xxx_fops);
    dev->cdev.owner = THIS_MODULE;   //指定所属模块
    err = cdev_add(&xxx_dev.cdev,xxx_devno,1);  //注册设备
}

static void __exit xxx_exit(void)
{
    cdev_del(&xxx_dev.cdev);    //注销cdev
    unregister_chrdev_region(xxx_devno,1);   //释放设备号
}

file_operations 结构体和其成员函数

大多数字符设备驱动都会实现read(),write()和ioctl()函数,常见写法如下代码所示

//文件操作结构体
static const struct file_operations xxx_fops=
{
    .owner = THIS_MODULE,  //模块引用,任何时候都赋值 THIS_MODULE
    .read = xxx_read,   //指定设备的读函数
    .write = xxx_write,  //指定设备的写函数
    .ioctl = xxx_ioctl,  //指定设备的控制函数
}

//读函数
static ssize_t xxx_read(struct file *filp,char _user *buf, size_t size,loff_t *ppos)
{
    ...
    if(size>8)
        copy_to_user(buf,...,...);  //当数据较大时,使用copy_to_user(),效率较高
    else
        put_user(...,buf);  //当数据较小时,使用put_user(),效率较高
    ...
}

//写函数
static ssize_t xxx_write(struct file *filp,const char _user *buf,size_t size,loff_t *oppos)
{
    ...
    if(size>8)
        copy_from_user(...,buf,...); //数据较大时
    else
        get_user(...,buf);  //数据较小时
    ...
}

//ioctl 设备控制函数
static long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long arg)
{
    ...
    switch(cmd)
    {
        case xxx_cmd1:
            ...
            break;
        ...
            break;
        default:
            return - EINVAL;  //内核和驱动程序都不支持该命令时,返回无效的命令
    }
    return 0;
}

驱动程序与应用程序的数据交换函数

unsigned long copy_to_user(void _user *to,const void *from,unsigned long n);
unsigned long copy_from_user(void *to,const _user *from,unsigned long n);
put_user(local,user);
get_user(local,user);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值