【Linux-驱动开发】

Linux-驱动开发

  • [■ Linux-应用程序对驱动程序的调用流程](#■ Linux-应用程序对驱动程序的调用流程)
  • [■ Linux-file_operations 结构体](#■ Linux-file_operations 结构体)
  • [■ Linux-驱动模块的加载和卸载](#■ Linux-驱动模块的加载和卸载)
    • [■ 1. 驱动编译进 Linux 内核中](#■ 1. 驱动编译进 Linux 内核中)
    • [■ 2. 驱动编译成模块(Linux 下模块扩展名为.ko)](#■ 2. 驱动编译成模块(Linux 下模块扩展名为.ko))
  • [■ Linux-](#■ Linux-)
  • [■ Linux-](#■ Linux-)
  • [■ Linux-设备号](#■ Linux-设备号)
    • [■ Linux-设备号-分配](#■ Linux-设备号-分配)
      • [■ 静态分配设备号](#■ 静态分配设备号)
      • [■ 动态分配设备号](#■ 动态分配设备号)
  • [■ Linux-驱动分类](#■ Linux-驱动分类)
    • [■ 字符设备:](#■ 字符设备:)
      • [■ 字符设备-注册与注销函数](#■ 字符设备-注册与注销函数)
      • [■ 字符设备-具体操作函数](#■ 字符设备-具体操作函数)
      • [■ 字符设备-LICENSE 和作者信息](#■ 字符设备-LICENSE 和作者信息)
      • [■ 示例一:](#■ 示例一:)

■ Linux-应用程序对驱动程序的调用流程


■ Linux-file_operations 结构体

在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合,

cpp 复制代码
1588 struct file_operations {
1589 struct module *owner;        // 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
1590 loff_t (*llseek) (struct file *, loff_t, int);  //llseek 函数用于修改文件当前的读写位置。
1591 ssize_t (*read) (struct file *, char __user *, size_t, loff_t*);  // read 函数用于读取设备文件。
1592 ssize_t (*write) (struct file *, const char __user *, size_t,loff_t *); //write 函数用于向设备文件写入(发送)数据。
1593 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1594 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1595 int (*iterate) (struct file *, struct dir_context *);
1596 unsigned int (*poll) (struct file *, struct poll_table_struct*);  //是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
1597 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
1598 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,
32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是
unlocked_ioctl。
1599 int (*mmap) (struct file *, struct vm_area_struct *); //mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓
冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用
程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
1600 int (*mremap)(struct file *, struct vm_area_struct *);
1601 int (*open) (struct inode *, struct file *); //open 函数用于打开设备文件。
1602 int (*flush) (struct file *, fl_owner_t id);
1603 int (*release) (struct inode *, struct file *); //release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
1604 int (*fsync) (struct file *, loff_t, loff_t, int datasync); //fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
1605 int (*aio_fsync) (struct kiocb *, int datasync); //aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的
数据。
1606 int (*fasync) (int, struct file *, int);
1607 int (*lock) (struct file *, int, struct file_lock *);
1608 ssize_t (*sendpage) (struct file *, struct page *, int, size_t,loff_t *, int);
1609 unsigned long (*get_unmapped_area)(struct file *, unsigned long,unsigned long, unsigned long, unsigned long);
1610 int (*check_flags)(int);
1611 int (*flock) (struct file *, int, struct file_lock *);
1612 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,loff_t *, size_t, unsigned int);
1613 ssize_t (*splice_read)(struct file *, loff_t *, structpipe_inode_info *, size_t, unsigned int);
1614 int (*setlease)(struct file *, long, struct file_lock **, void**);
1615 long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
1617 void (*show_fdinfo)(struct seq_file *m, struct file *f);
1618 #ifndef CONFIG_MMU
1619 	unsigned (*mmap_capabilities)(struct file *);
1620 #endif
1621 };
序号 描述
1 驱动加载成功以后会在"/dev"目录下生成一个相应的文件
2 比如现在有个叫做/dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用close 函数关闭/dev/led 这 个文件。 open和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led
3 应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间
4
5
6
7
8

■ Linux-驱动模块的加载和卸载

■ 1. 驱动编译进 Linux 内核中

这样当 Linux 内核启动的时候就会自动运行驱动程序。

■ 2. 驱动编译成模块(Linux 下模块扩展名为.ko)

在Linux 内核启动以后使用"insmod"命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。

module_init(xxx_init); //注册模块加载函数 当使用"insmod"命令加载驱动的时候, xxx_init 这个函数就会被调用

module_exit(xxx_exit); //注册模块卸载函数 当使用"rmmod"命令卸载具体驱动的时候, xxx_exit 函数就会被调用。

示例一:

第15行,调用函数module_init 来声明 xxx_init 为驱动入口函数,当加载驱动的时候 xxx_init函数就会被调用。

第16行,调用函数module_exit来声明xxx_exit为驱动出口函数,当卸载驱动的时候xxx_exit函数就会被调用。

关键字功能 作用 说明
加载驱动 insmod insmod 是最简单的模块加载命令 例如 insmod drv.ko , insmod 命令不能解决模块的依赖关系, 比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。
加载驱动 modprobe modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中, 因此modprobe 命令相比 insmod 要智能一些。 推荐使用 modprobe 命令来加载驱动。 modprobe 命令默认会去/lib/modules/目录中查找模块,
卸载驱动 modprobe -r 例如 modprobe -r drv.ko 使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。
卸载驱动 rmmod 例如 rmmod drv.ko 推荐使用 rmmod 命令。

■ Linux-

■ Linux-

■ Linux-设备号

Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,
主设备 号表示某一个具体的驱动,
次设备 号表示使用这个驱动的各个设备。

dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。高 12 位为主设备号, 低 20 位为次设备号。

cpp 复制代码
typedef unsigned int __u32;      
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;   dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。高 12 位为主设备号, 低 20 位为次设备号。

在include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:

cpp 复制代码
#define MINORBITS 20       宏 MINORBITS 表示次设备号位数,一共是 20 位。
#define MINORMASK ((1U << MINORBITS) - 1)         宏 MINORMASK 表示次设备号掩码。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))  宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))  宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))   宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。

■ Linux-设备号-分配

■ 静态分配设备号

具体分配的内容可以查看文档 Documentation/devices.txt。

看硬件平台运行过程中有没有使用这个主设备号,使用"cat /proc/devices" 命令即可查看当前系统中所有已经使用了的设备号。

■ 动态分配设备号

静态分配设备号很容易带来冲突问题, Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。

cpp 复制代码
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)  //用于申请设备号,
	dev:保存申请到的设备号。
	baseminor: 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
	count: 要申请的设备号数量。
	name:设备名字。

void unregister_chrdev_region(dev_t from, unsigned count)     //设备号释放函数
	from:要释放的设备号。
	count: 表示从 from 开始,要释放的设备号数量。

■ Linux-驱动分类

■ 字符设备:

字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

■ 字符设备-注册与注销函数

cpp 复制代码
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)

major: 主设备号,
name:设备名字,指向一串字符串。
fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。

■ 字符设备-具体操作函数

打开

关闭

写 操作

■ 字符设备-LICENSE 和作者信息

MODULE_LICENSE() //添加模块 LICENSE 信息

MODULE_AUTHOR() //添加模块作者信息

■ 示例一:

cpp 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define CHRDEVBASE_MAJOR      200           //主设备号
#define CHRDEVBASE_NAME     "chrdevbase"    //名字

static char readbuf[100]; /*读缓冲 */
static char writebuf[100];  /* 写缓冲 */
static char kerneldata[] = {"kernel data!"};


static int chrdevbase_open(struct inode *inode, struct file *filp)
{
   // printk("chrdevbase_open\r\n");
    return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *filp)
{
   // printk("chrdevbase_release\r\n");
    return 0;   
}

static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count,
			loff_t *ppos)
{ 
    int ret  = 0;
    //printk("chrdevbase_read\r\n");
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    ret = copy_to_user(buf, readbuf, count);
    if(ret == 0) {

    } else {

    }
    return 0;  
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    int ret = 0;
    //printk("chrdevbase_write\r\n");
    ret = copy_from_user(writebuf, buf, count);
    if(ret == 0) {
        printk("kernel recevdata:%s\r\n", writebuf);
    } else {
 
    }
    return 0; 
}

/*
 * 字符设备 操作集合
 */
static struct file_operations chrdevbase_fops={
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .release = chrdevbase_release,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
};

static int __init chrdevbase_init(void)
{
    int ret = 0;
    printk("chrdevbase_init\r\n");
    /* 注册字符设备 */
    ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
    if(ret < 0) {
        printk("chrdevbase init failed!\r\n");
    }
	return 0;
}

static void __exit chrdevbase_exit(void)
{
    printk("chrdevbase_exit\r\n");	
    /* 注销字符设备 */
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
}

/*
 模块入口与出口
*/
module_init(chrdevbase_init);  /* 入口 */
module_exit(chrdevbase_exit);  /* 出口 */

MODULE_LICENSE("GPL");      
MODULE_AUTHOR("zuozhongkai");

相关推荐
维生素C++15 分钟前
【可变模板参数】
linux·服务器·c语言·前端·数据结构·c++·算法
CodeHackerBhx3 小时前
如何使用VMware安装Linux操作系统
linux·运维·服务器
小阿轩yx3 小时前
小阿轩yx-通过state模块定义主机状态
linux·云计算·运维开发·state定义主机状态·jinja模板
Pakho love5 小时前
Linux:软件包管理器 yum和编辑器-vim使用
linux·编辑器·vim
吴半杯6 小时前
Linux-mysql5.7-mysql8.0安装包下载及安装教程,二合一
linux·运维·服务器
默行默致6 小时前
Linux 常用命令
linux·运维
码哝小鱼6 小时前
firewalld实现NAT端口转发
linux·网络
RememberLey6 小时前
【VitualBox】VitualBox的网络模式+网络配置
linux·网络·virtualbox
卡戎-caryon6 小时前
【Linux】09.Linux 下的调试器——gdb/cgdb
linux·运维·服务器·开发语言·笔记
Spring-wind8 小时前
【linux】kill命令
linux