Linux驱动学习

本文详细介绍了Linux驱动的学习,包括开发板文件系统操作指令、Ubuntu的重要文件路径和驱动文件框架。讲解了如何在内核中添加设备驱动,如何关联应用程序和设备驱动,以及中断处理、虚拟内存地址映射、文件阻塞IO模型和异步收发数据等内容。同时,文章提供了实践练习,帮助读者深入理解驱动开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开发板文件系统操作指令

指令 功能 备注
insmod+xxx.ko 向开发板内核添加驱动文件
rmmod+xxx.ko 从开发板内核中移除对应的驱动文件
ls /sys/bus/xxxbus/devices/ 查看xxx总线中的设备文件 需要添加总线和设备文件
ls /sys/bus/xxxbus/drivers/ 查看xxx总线中的驱动文件 需要添加总线和驱动文件
ls /dev/xxx 查看添加的设备文件 就是device_create()第5个参数
ls /proc 查看设备号
./xxx 执行xxx可执行文件 需要添加可执行文件
ls /proc/device-tree/ 查看添加到设备树上的节点 要在设备树文件中添加节点并重新编译,拷贝编译后的设备树文件到tftpdir目录,重启开发板,利用tftp方式下载到开发板中。
ls /proc/interrupts/ 查看申请的中断 就是request_irq()的第4个参数

ubunt重要文件路径

注:该路径为个人路径

路径 描述 备注
/home/ubuntu/code/kernel/linux-3.14 Linux-3.14内核路径 包含众多内核文件
/home/ubuntu/tftpdir tftp服务路径 包含tftp服务上传下载文件
/home/ubuntu/nfsdir/rootfs nfs服务路径 包含nfs服务共享的文件(根文件系统)
/home/ubuntu/ARM_Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc gcc交叉编译工具路径
/home/ubuntu/code/kernel/linux-3.14/arch/arm/boot/dts 设备树文件

驱动文件框架

/* 头文件 */
#include <linux/module.h>

/* 函数入口实现 */
static int __init chr_dev_init(void)
{
   
	printk("-----%s-----\n",__FUNCTION__);
	return 0;
}

static void __exit chr_dev_init(void)
{
   
	printk("-----%s-----\n",__FUNCTION__);
}

/* 模块入口声明 */
module_init(chr_dev_init);
module_exit(chr_dev_init);
MODULE_LICENSE("GPL");

Makefile脚本

KERNEL_DIR=/home/ubuntu/code/kernel/linux-3.14
CUR_DIR=$(shell pwd)
MODULE_NAME=module
APP_NAME=led_app
ROOTFS_DIR=/home/ubuntu/nfsdir/rootfs/drv_module

ifeq ($(KERNELRELEASE), )
all:
	make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
	/home/ubuntu/ARM_Tools/gcc-4.6.4/bin/arm-none-linux-gnueabi-gcc $(APP_NAME).c -o $(APP_NAME)
clean:
	make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
install:
	cp *.ko $(ROOTFS_DIR)
	cp $(APP_NAME).o $(ROOTFS_DIR)
else
	obj-m += $(MODULE_NAME).o
	
endif

应用层实现底层硬件的控制原理

首先应用程序想要实现底层硬件的控制,必须要通过内核去调用设备驱动来控制底层硬件,所以说直接控制硬件的是内核中的设备驱动。那么应用程序又是如何让设备驱动去控制硬件的呢?首先需要在内核中添加应用程序想要控制的硬件的设备驱动,让内核知道有这个驱动设备,有了想要的设备驱动,然后让应用程序关联这个设备驱动,实现应用程序 调用文件IO,设备驱动执行对应的接口函数。我们可以在这个接口函数中,编写程序实现对硬件寄存器的控制(地址的控制),最终实现硬件的控制。

这里有几个知识点

问题1、如何在内核中添加应用层想要的设备驱动?

答:通过创建字符设备文件(设备节点)来实现。这个字符设备文件其实就是一个设备信息结构体,

其中存放了我们想要的设备驱动信息,其中包含了,字符设备的信息内容、我们想要创建的设备驱动的设备号、以及给我们想要创建的设备驱动取得名字等信息。然而这些作为该结构体成员的设备驱动的具体参数值(内容),需要我们去创建出来,然后利用一个返回值为该结构体类型的指针函数struct device *device_create(),将这些参数值(内容)传到这个结构体中。这样才算创建完了一个字符设备文件,此时的字符设备文件中相当于被初始化了,将传入的参数值(内容)作为对应成员的值(内容)。

​ 这里又有个问题:

问题1.1怎么创建该结构体(字符设备文件)的具体参数值(内容)呢?

​ 在回答这个问题前,我们先要知道,这个结构体(设备文件)需要我们创建哪些具体的参数值(内容):创建的设备文件的信息内容、父类对象、设备号、私有数据、设备文件名。好,明白了要创建什么,现在就要知道,怎么创建

  1. 参数1的具体值(内容)-------设备文件的信息内容

    通过 class_create()函数创建
    struct class * class_create(owner,name);
    例如:struct class * cls_led = class_create(THIS_MODULE,"led cls");
    
    参数1:
    	owner:拥有者,一般THIS_MODULE
    参数2:
    	name:字符串,描述信息
    
    返回值:
    	struct class * 
    	信息结构体	
    
  2. 参数2的具体值(内容)-------创建父类对象

    这个参数一般为NULL
    
  3. 参数3的具体值(内容)--------设备号

    在创建设备号之前需要明白:
    设备号:32bit = 主设备号(高12bit) + 次设备号(低20bit)
    			主设备号:表示同一类设备
    			次设备号:表示同一类设备中的不同设备
    
    方式1:
    其实以下两步操作可以通过一个封装好的函数来实现  -------MKDEV(主设备号,次设备号),返回值就是设备号
    
    方式2:
    我们只需要设置主设备号,次设备号自动分配
    /* 向内核申请注册设备号 */
    int register_chrdev(	unsigned int major,
    					const char *name,
    					const struct file_operations *fops)
    
    例如:ret = register_chrdev(250,"led",&fops);
    	进一步计算的到设备号:
    	dev_t devt = 250<<20 | 0;
    	
    					
    					
    参数1:
    		unsigned int major:主设备号
    		无符号整数
    参数2:
    		const char *name:描述一个设备驱动信息,自定义,通俗来说就是设备名叫什么
    		字符串首地址
    参数3:
    		const struct file_operations *fops:文件操作对象
    		结构体地址(指针)
    		解释:文件操作对象,就是一个结构体的实例化对象的地址,该结构体中的成员都是函数指针,这个结构体可以实现应用层程序和存储驱动间的函数关联,置于这个关联有什么用,下面会详细讲,这里大概就说一下,就是应用层调用什么文件IO接口,底层设备驱动就执行该结构体实例化的对象中赋值的函数接口。
    		
    返回值:
    		正确返回0,失败返回负数
    
    
  4. 参数4的具体值(内容)-------私有数据

    私有数据,一般填NULL
    
  5. 参数5的具体值(内容)------设备文件名

    一个字符串
    
问题1.2、得到了这个设备节点的成员的具体的值了(设备节点是一个结构体),怎么赋值给这个设备节点(结构体)呢?
当我们得到结构体(设备节点)所需要的成员的具体值(内容)后,就可以利用下面这个函数将值赋值给(设备节点)结构体中对应的成员。

struct device *device_create(	struct class *class,    //参数1
					struct device *parent,            //参数2
					dev_t devt,                       //参数3
					void *drvdata,                     //参数4
					const char *fmt,                    //参数5
					...)----创建设备节点

例如:struct device * led_dev = device_create(cls_led,NULL,devt,NULL,"led%d",3);

问题2、有了字符设备驱动文件,怎么关联应用程序和设备驱动呢?

首先我们要知道,什么是关联,所谓关联就是:在驱动中实现文件io接口功能,当应用程序调用文件io时,驱动程序也调用对应的文件io接口函数

在内核中设计好的结构体 struct file_operations 每一个成员变量都代表绑定一个系统调用(文件io)函数,只要对结构体中的成员赋值,就代表值绑定上一个文件io函数
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 (*iterate) (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 (*fsync) (struct file *, loff_t, loff_t, 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 **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f);};//函数指针的集合,
​			每个函数指针赋值为函数地址,就代表当应用程序调用对应的文件io函数时,驱动就执行函数指针赋值的对应函数
例如:
//驱动需要对应执行的IO函数实现,注意,这些IO函数是根据应用程序的需要进行关联和编写的,如果应用程序中没有用到相关的IO函数,就不需要关联和编写
int led_open (struct inode * inode, struct file * file)
{
   
	printk("led_open\n");
	return 0;
}

int led_close (struct inode * inode, struct file * file
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值