Linux驱动一:最简形式

一、概述

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值