字符设备驱动程序接口

2022-11-15 21:22:29 浏览数 (1)

驱动模块操作命令

代码语言:javascript复制
# 列出当前内核中已经安装的模块(list module)
lsmod

# 安装模块(install module)
insmod xxx.ko

# 打印模块的自带信息(module information)
modinfo xxx.ko

# 卸载模块(remove module)
rmmod xxx

# 查看驱动加载信息
dmesg

# 查看设备号
cat /proc/devices

# 创建设备文件
# 格式(c表示字符设备驱动、b表示块设备驱动):
mknod /dev/xxx c或者b major minor

查看设备文件信息

代码语言:javascript复制
ls -l /dev/xxx

最简单的模块

module_test.c

代码语言:javascript复制
#include <linux/module.h>
#include <linux/init.h>

static int __init chrdev_init(void)
{
    printk(KERN_INFO "moudue_test chrdev initrn");
    return 0;
}

static void __exit chrdev_exit(void)
{
    printk(KERN_INFO "module_test chrdev exitrn");
}

module_init(chrdev_init);
module_exit(chrdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ifan Tsai");

__init__宏:被修饰的函数会被链接器链接放入.init.text段中(本来默认情况下函数是被放入.text段中)。对内核而言是一种暗示,表示该函数仅在初始化期间使用,内核启动时统一会加载.init.text段中的这些模块安装函数,加载完后就会把这个段给释放掉以节省内存。

__exit__宏:被修饰的函数仅用于模块卸载,链接器会将其放入特殊的ELF段。如果模块被直接内嵌到内核中,或内核的配置不允许卸载模块,则被修饰的函数将被简单的丢弃。

prink函数:模块在被加载到内核后,它能调用的函数仅仅是由内核导出的那些函数。KERN_INFO是printk的打印级别,其实只是一个字符串(如<1>)。操作系统的命令行中也会有一个打印级别的设置(值为0-7),当前操作系统中执行printk的时候会去对比printk中的打印级别和操作系统命令行中设置的打印级别,小于命令行设置级别的信息会被打印出来,大于的会被拦截。

module_init宏:该宏声明的函数会在模块被装载到内核中调用。

module_exit宏:该宏声明的函数会在模块被卸载时调用。

MODULE_LICENSE宏:指定该代码所使用的许可证协议。

MODULE_AUTHOR:描述模块作者。

附:可在模块中包含的其他描述信息还有:MODULE_DESCRIPTION(模块的简短描述)、MODULE_VERSION(代码修订号)、MODULE_ALIAS(模块别名)等等

Makefile

代码语言:javascript复制
# ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build


# 开发板的linux内核的源码树目录
KERN_DIR = ~/Embedded/kernel

obj-m    = module_test.o

all:
    make -C $(KERN_DIR) M=`pwd` modules
    # 将编译好的模块复制到开发板的rootfs下
    cp *.ko -f ~/Embedded/nfs/rootfs/root/driver

.PHONY: clean
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean

驱动模块的Makefile是通过make -C进入到内核源码树下借用内核源码的编译体系(调用内核源码下的Makefile)来完成驱动模块的编译链接。

内核和应用之间的数据传递

copy_from_user

从用户空间拷贝数据到内核空间

代码语言:javascript复制
/* asm/uaccess.h */
static inline unsigned long __must_check copy_from_user(void* to, const void __user *from, unsigned long n)

返回值:成功返回0, 失败返回剩下的未成功复制的字节数

copy_to_user

从内核空间拷贝数据到用户空间

代码语言:javascript复制
/* asm/uaccess.h */
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)

返回值:成功返回0, 失败返回剩下的未成功复制的字节数

file_operations结构体

代码语言:javascript复制
/* 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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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);
    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 *, 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 **);
};

该结构体成员除了owner,其他皆为函数指针。每个设备驱动都需要一个该结构体类型的变量,在应用层的系统编程中open、read、write、close等API最终调用的就是该结构体内函数指针的指向。该结构体第一个成员owner指针指向拥有该结构体模块的指针,这个成员几乎所有情况下都被初始化为THIS_MODULE,相当于c 中的this指针。

字符设备驱动老接口

register_chrdev

向内核注册字符设备驱动

代码语言:javascript复制
/* linux/fs.h */
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

unsigned int major:主设备号(1 - 255),传递0则由内核自动分配一个合适的空闲的主设备号

**const char *name**:设备名称

**const struct file_operations *fops**:结构体指针, file_operations里封装了一堆字符驱动操作的函数指针

返回值:成功返回主设备号, 失败返回一个负值

内核中有一个数组要来存储注册的字符设备驱动,register_chrdev注册的字符设备驱动的信息主要就存在这个数组的相应位置,该数组的下标就是主设备号,可以通过查看/proc/devices文件来确定已经注册了 的字符设备驱动和块设备驱动以及相应的编号

代码语言:javascript复制
cat /proc/devices

unregister_chrdev

从内核卸载字符设备驱动

代码语言:javascript复制
/* linux/fs.h */
static inline void unregister_chrdev(unsigned int major, const char *name)

unsigned int major:主设备号(1 - 255),传递0则由内核自动分配一个合适的空闲的主设备号

**const char *name**:设备名称

字符设备驱动新接口

主次设备号

在老接口中使用register_chrdev函数来注册字符设备驱动,但是设备号只有major(主设备号),而新接口中提供了minor(次设备号),内核中通过dev_t类型描述设备号,其实质是unsigned int类型,其中高12位为设备号,低20位为次设备号。内核并提供了三个宏函数对dev_t的操作。

代码语言:javascript复制
/* linux/kdev_t.h */

// 传入两个参数生成设备号(dev_t),分别为主设备号和次设备号
MKDEV(ma, mi) 
// 传入设备号(dev_t),返回主设备号
MAJOR(dev) 
// 传入设备号(dev_t),返回次设备号
MINOR(dev)

register_chrdev_region

事先知道要使用的主次设备号,需要cat /proc/devices查看未使用的设备号,则使用该接口来分配主次设备号。

代码语言:javascript复制
/* linux/fs.h */
int register_chrdev_region(dev_t from, unsigned count, const char *name)

from: 起始设备号(主次)

count: 请求分配的连续设备的数量

name: 设备名称

返回值: 成功返回0, 失败返回负值

alloc_chrdev_region

动态分配主次设备号

代码语言:javascript复制
/* linux/fs.h */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

dev:传出参数,传入需要自动分配的设备号变量

baseminor: 起始次设备号

count: 请求分配的连续设备的数量

name: 设备名称

返回值: 成功返回0, 失败返回负值

unregister_chrdev_region

注销主次设备号

代码语言:javascript复制
/* linux/fs.h */
void unregister_chrdev_region(dev_t from, unsigned count)

from: 起始设备号(主次)

count: 注销的连续设备的数量

struct cdev

内核使用cdev结构体来描述字符设备,在新接口中必须使用该结构体和file_operations结构体一起来描述一个字符设备驱动。

代码语言:javascript复制
/* linux/cdev.h */
struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;  // fops
    struct list_head list;               // 内核链表
    dev_t dev;                           // 设备号 (主设备号   次设备号)
    unsigned int count;                  // 文件引用计数
};

cdev_alloc

为cdev指针分配内存

代码语言:javascript复制
/* linux/cdev.h */
struct cdev *cdev_alloc(void)

cdev_init

初始化cdev结构体,将cdev与file_operations绑定起来

代码语言:javascript复制
/* linux/cdev.h */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    memset(cdev, 0, sizeof *cdev);
    INIT_LIST_HEAD(&cdev->list);
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    cdev->ops = fops;
}

INIT_LIST_HEAD(&cdev->list);kobject_init(&cdev->kobj, &ktype_cdev_default);这两句代码在cdev_alloc中已经做了,所以这个函数可以用cdev->ops = fops;代替

cdev_add

注册字符设备驱动

代码语言:javascript复制
/* linux/cdev.h */
int cdev_add(struct cdev *p, dev_t dev, unsigned count)

p:cdev结构体变量的指针

dev: 起始设备号(主次)

count: 请求注册的连续设备的数量

返回值:成功返回0, 失败返回负值

cdev_del

注销字符设备驱动,并释放用cdev_alloc分配的内存

代码语言:javascript复制
/* linux/cdev.h */
void cdev_del(struct cdev *p)

本文作者: Ifan Tsai  (菜菜)

本文链接: https://cloud.tencent.com/developer/article/2164585

版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

0 人点赞