linux驱动开发(2)开发流程

1.驱动

驱动其实就是获取外设数据或者控制外设,将数据给应用层,应用层进行处理,显示等等操作。

驱动写好了,注入到内核之后,我们还需要去写简单的测试应用程序去检测程序是否正确。裸机开发,驱动和测试是写在一起的,这里有可以体现出驱动开发和裸机开发的不同之处了。

 驱动程序表现为就是一个/dev/xx文件,这个文件就是控制外设的文件。

比如led ,通过write写入1,点亮,写0关闭。

1.1.开发流程

         在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应
用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实
现对硬件的操作。简单理解,驱动开发可以将硬件变为文件,提供给上层应用去操控。

        好了,明白了驱动开发是干嘛,接下来该学习一下驱动开发了,驱动开发其实就是一个模板开发,很多都是操作系统写好的,我们只需要去按照模板去修改,满足自己的要求。本质就是自己实现file_operations里面的方法。

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 *);
	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 (*mremap)(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 **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
};

上面就是 file_operations 的结构体。里面有很多我们常见的方法,比如open函数,read/write函数等等,有没有一点点熟悉的感觉了,但是这里的函数和我们应用层使用的不一样。

2.驱动 模块的加载和卸载

        Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启
动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在
Linux 内核启动以后使用“insmod/modprobe(modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此modprobe 命令相比 insmod 要智能一些)”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编
译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进
Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

这两个函数是比较重要的,加载函数里面,我们一般要注册设备,注册设备就需要使用到刚刚上面提及到的file_operations 的结构体了;卸载的时候进行注销,下面会有讲这两个函数的实现。

/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
 return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 出口函数具体内容 */
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

linux源码里面很多案例可以去看看.里面有一些函数看不懂没关系,后面会有博客会讲,也会有博客进行总结常见api使用。

 3.字符设备注册与注销

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)

 register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
major :主设备号,Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两
部分,关于设备号后面会详细讲解。
name:设备名字,指向一串字符串。
fops :结构体 file_operations 类型指针,指向设备的操作函数集合变量。
unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
major :要注销的设备对应的主设备号。
name :要注销的设备对应的设备名。

3.1.设备号

为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分
组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux 提供了
一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,这个其实是一个宏,在源码里面可以看到就是一个 unsigned int 类型,这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围,在文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

宏 MINORBITS 表示次设备号位数,一共是 20 位。
宏 MINORMASK 表示次设备号掩码。
宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号

后面三个函数我们比较常用。

3.1.1 、静态分配设备号

注册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态的指定一个
设备号,比如选择 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配掉
了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉
的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个
主设备号,使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号。

3.1.2动态分配设备号

静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用
的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:

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 开始,要释放的设备号数量。

4.添加 LICENSE 和作者信息

我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否
则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加使用
如下两个函数:

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

 如:

MODULE_LICENSE("GPL");  //LICENSE 采用 GPL 协议。
63 MODULE_AUTHOR("your name"); //作者名字

5.物理内存和虚拟内存之间的转换

ioremap 和 iounmap。

5.1 、ioremap 函数

ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在
arch/arm/include/asm/io.h 文件中

#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)

void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
     return arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}

ioremap 是个宏,有两个参数:cookie 和 size,真正起作用的是函数__arm_ioremap,此函
数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr:要映射的物理起始地址。
size:要映射的内存空间大小。
mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。

5.2 、iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原
型如下:

void iounmap (volatile void __iomem *addr)

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。这里虚拟地址空间首地址其实就是ioremap宏返回值。

到目前为止模板里面的函数介绍差不多了,该介绍 file_operations 的结构体里面函数的实现了,这些函数介绍会先通过一些案例,进行介绍,到最后会汇总到一起,方便大家使用查看。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux驱动开发流程大致如下: 1. 确定驱动类型:确定需要开发的驱动类型,例如字符设备驱动、块设备驱动、网络设备驱动等。 2. 编写驱动代码:根据驱动类型编写相应的驱动代码,包括初始化函数、读写函数、中断处理函数等。 3. 编译驱动代码:使用交叉编译工具链编译驱动代码,生成驱动模块文件。 4. 安装驱动模块:将驱动模块文件安装到系统中,使用insmod命令加载驱动模块。 5. 测试驱动功能:使用相应的测试工具或应用程序测试驱动功能是否正常。 6. 调试驱动问题:如果出现驱动问题,需要使用调试工具进行调试,例如使用gdb调试驱动代码。 7. 优化驱动性能:对驱动进行性能优化,提高驱动的响应速度和稳定性。 8. 发布驱动代码:将驱动代码发布到开源社区或自己的网站上,供其他开发者使用和改进。 ### 回答2: Linux驱动是Linux内核中的一段代码,是连接硬件和应用程序的桥梁。Linux驱动开发是一项涉及到操作系统内核、硬件和编程的综合性工作,需要开发人员具备扎实的操作系统和嵌入式系统知识、熟练的编程能力和丰富的实践经验。 Linux驱动开发流程大致包括以下几个步骤: 1.确定需求:确定开发的驱动是哪一种类型(字符设备驱动、块设备驱动、网络设备驱动等),并明确驱动的功能需求。 2.编写设备驱动程序:根据需求,编写驱动程序的相应模块。 3.配置内核:将编写好的驱动程序模块集成到内核中,编译内核并安装。 4.加载驱动:通过modprobe等命令加载驱动到内核中。 5.测试和调试:编写测试程序对驱动进行测试和调试,保证驱动与所连接的硬件设备正常通信。 6.发布和维护:将测试通过的驱动发布,并持续对驱动进行维护和更新。 在以上步骤中,配置内核是一个非常关键和复杂的过程。内核的开发需要各种各样的支持程序,如交叉编译器、调试器等开发工具,还需要了解内核的构建体系和源代码结构。同时,为了在内核中正确地安装和配置驱动程序,需要了解驱动程序的接口函数、注册函数、出错处理等技术细节。 除此之外,还需要对驱动程序进行优化,使得驱动程序在运行时不会占用过多的资源和影响系统性能。因此,对硬件设备或内核进行深入研究也是实施优化的必要条件。此外,开发人员还需要对驱动的修改、升级进行版本控制和管理。 总之,Linux驱动开发是一个非常复杂而有挑战性的工程,需要开发人员具备深厚的技术功底和经验,才能保证驱动的质量和稳定性。 ### 回答3: Linux驱动开发流程主要分为以下几个步骤: 1. 确定需求和目标:在开始开发Linux驱动程序之前,我们需要明确需要创建什么样的驱动程序,需要解决什么问题,以及它将如何与其他组件(如Linux内核)交互。我们还需要确定目标硬件平台,并确定驱动程序需要支持哪些硬件设备和哪些功能。 2. 阅读文档和源代码:在开始编写代码之前,我们需要认真阅读相关的文档,如Linux内核源代码、驱动API、硬件规格书等。这有助于我们了解内核API和常用数据结构,并了解在内核中进行编程的常规方法和惯例。 3. 编写代码:在阅读完相关文档后,我们可以开始编写代码。在编写代码时,我们需要遵循内核编程的最佳实践,如使用适当的数据结构、避免全局变量、使用Linux锁等。 4. 编译测试和调试:编写完代码后,我们需要对其进行编译和测试,以确保其在不同硬件平台上可以正常工作。在测试期间,我们应该使用内核调试器或其他工具进行调试,以解决代码中可能存在的问题。 5. 发布和维护:一旦驱动程序经过测试并准备好发布,我们需要将其提交给Linux内核维护人员进行审核并将其合并到内核代码库中。一旦驱动程序被合并,我们需要定期更新代码以跟随内核代码库的发展,并解决用户反馈的问题和错误。 总体来说,Linux驱动开发需要我们具备扎实的C语言编程技能和对Linux内核的深入理解。除此之外,我们还应该熟悉常用的内核编程工具、调试方法和最佳实践。通过不断练习和不断学习,我们可以成为一名优秀的Linux驱动程序员。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值