一、概述
Linux驱动本质上是一种软件程序,使得上层软件能够在不了解底层硬件特性的情况下,通过驱动提供的接口与计算机硬件进行通信。
驱动程序可以被视为内核和硬件之间的接口,但因为其是一种软件程序,所以它处于内核层。内核通过生成驱动程序指定的设备文件,而应用层又可以通过操作“设备文件”进而达到控制驱动的目的。
二、内核加载、卸载驱动
在Linux系统中,内核模块(通常指的是驱动程序)可以在系统运行时动态地加载和卸载,而无需重启整个系统。这种动态加载和卸载的能力为Linux系统提供了极大的灵活性和可维护性。
一般来说加载、卸载驱动模块可以通过shell命令进行。
insmod XXX.ko //加载驱动
rmmod XXX.ko //卸载驱动
在加载驱动时,内核会调用驱动中实现的“初始化函数”进行一些初始化操作申请一些资源。在卸载驱动时,内核会调用驱动中实现的“退出函数”将申请的资源释放掉。
那么内核又如何知道哪个是初始化函数与退出函数呢?
module_init(XXX); //module_init 指定初始化函数,XXX为初始化函数名
module_exit(XXX); //module_exit 指定退出函数,XXX为初始化函数名
三、应用层通过“设备文件”控制驱动
在概述中说“应用层通过控制设备文件进而控制驱动”,又说设备文件是通过驱动程序指定并由内核生成的。
那这里有三个问题:
1、驱动程序是如何指定设备文件的?
2、应用层是如何操作设备文件的?
3、为什么能通过操作设备文件来控制驱动,它们之间有什么联系?
1、驱动程序是如何指定设备文件的?
驱动程序通过调用device_create函数来指定设备文件。
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
其中参数 fmt 为设备文件的名称。
2、应用层是如何操作设备文件的?
应用层通过文件I/O来操作设备文件,Linux文件I/O分为系统IO和标准IO
系统I/O通过文件描述符 fd 来操作文件
标准I/O通过文件流 FILE* 来操作文件
3、为什么能通过操作设备文件来控制驱动,它们之间有什么联系?
在驱动中将会定义 struct file_operations 类型的变量,通过注册函数 register_chrdev 将该变量与驱动绑定。此函数在加载驱动时的初始化函数中被调用。
可以看到 file_operations 结构体中的元素的数据类型都是函数指针,且元素名与文件I/O的操作函数类似。当应用层通过文件I/O操作设备文件时,内核会将其指向到 file_operations 中相应的函数指针所指向的函数上去执行。
总的来说,file_operations 结构体中的成员函数是设备驱动与内核虚拟文件系统的接口,是用户空间对 Linux 进行系统调用最终的落实者。
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 (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
四、具体实例
驱动模块代码
// filename:test_drv.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
static int major = 0;
static int minor = 0;
struct class *test_class = NULL;
static int test_drv_open (struct inode *node, struct file *file)
{
printk("%s line %d\n", __FUNCTION__, __LINE__);
return 0;
}
static int test_drv_close (struct inode *node, struct file *file)
{
printk("%s line %d\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t test_drv_write (struct file *file , const char __user *buff, size_t size, loff_t *offset)
{
int err;
char tmp_buff[8] = {0};
int len = 0;
printk("%s line %d\n", __FUNCTION__, __LINE__);
len = size < sizeof(tmp_buff) ? size : (sizeof(tmp_buff) - 1);
err = copy_from_user(tmp_buff, buff, size);
printk("%s line %d , buff: %s , size: %d \n", __FUNCTION__, __LINE__ , tmp_buff , size);
return 0;
}
static struct file_operations test_drv = {
.owner = THIS_MODULE,
.open = test_drv_open,
.write = test_drv_write,
.release = test_drv_close,
};
static int __init test_drv_init(void)
{
int err;
printk("%s line %d\n", __FUNCTION__, __LINE__);
major = register_chrdev(0, "test_drv_module", &test_drv);
test_class = class_create(THIS_MODULE, "test_drv_class");
err = PTR_ERR(test_class);
if (IS_ERR(test_class)) {
printk("%s line %d\n", __FUNCTION__, __LINE__);
unregister_chrdev(major, "test_drv");
return -1;
}
device_create(test_class, NULL, MKDEV(major, minor), NULL, "test_drv_dev_file");
return 0;
}
static void __exit test_drv_exit(void)
{
printk("%s line %d\n", __FUNCTION__, __LINE__);
device_destroy(test_class, MKDEV(major, minor));
class_destroy(test_class);
unregister_chrdev(major, "test_drv_module");
}
module_init(test_drv_init);
module_exit(test_drv_exit);
MODULE_LICENSE("GPL");
应用层测试程序
// filename:test.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd;
int len = 0;
if (argc != 3) {
printf("Usage: %s <dev> <string>\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd == -1) {
printf("can not open file %s\n", argv[1]);
return -1;
}
len = strlen(argv[2]);
write(fd, argv[2], len);
return 0;
}
makefile
KERNAL_DIR = /home/test/Linux-4.9.88 // Linux内核所在的路径,在编译驱动前要先编译内核
DRIVER_NAME = test_drv
TEST_NAME = test
all:
make -C $(KERNAL_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o $(TEST_NAME) $(TEST_NAME).c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f $(TEST_NAME)
obj-m += $(DRIVER_NAME).o
执行步骤与打印
[root@linux:~]# insmod test_drv.ko
[ 85.389033] test_drv_init line 60
[root@linux:~]# ls /dev/
......
test_drv_dev_file
......
[root@linux:~]# lsmod
Module Size Used by
test_drv 2439 0
......
[root@linux:~]# ls /sys/class/
......
test_drv_class
......
[root@linux:~]# ./test /dev/test_drv_dev_file 12345
[ 121.111244] test_drv_open line 24
[ 121.115331] test_drv_write line 40
[ 121.118814] test_drv_write line 44 , buff: 12345 , size: 5
[ 121.127800] test_drv_close line 30
[root@linux:~]# rmmod test_drv
[ 1036.862372] test_drv_exit line 78