Linux字符设备驱动开发

一、Linux 应用程序对驱动程序的调用

Linux 应用程序对驱动程序的调用流程
在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。
比如现在有个叫做/dev/led 的驱动文件,此文件是 led 灯的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。
open和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭 led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数。如果要获取led 灯的状态,就用 read 函数从驱动中读取相应的状态。
下面以open()函数举例其函数调用流程图。
Open 函数调用流程
每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合

二、开发步骤

字符设备开发结构图

字符设备开发的基本结构关系如上图所示。

2.1 驱动模块的加载和卸载

模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

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

module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“insmod”命令加载驱动的时候,xxx_init 这个函数就会被调用。
module_exit()函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。

2.1.1 加载驱动

驱动模块编译后会生成拓展名为*.ko的文件,称为驱动模块,有两种方法可以加载驱动模块。insmod和 modprobe

(1)insmod 命令:不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。
(2)modprobe :会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中

2.1.2 卸载驱动

(1)rmmod命令rmmod drv.ko
(2)modprobe -r 命令modprobe -r drv.ko

2.2 字符设备注册与注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:

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 类型指针,指向设备的操作函数集合变量。
示例代码:

/* 注册字符设备驱动 */
retvalue = register_chrdev(200, "chrtest", &test_fops);

unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下
major:要注销的设备对应的主设备号。
name:要注销的设备对应的设备名。
示例代码:

/* 注销字符设备驱动 */
unregister_chrdev(200, "chrtest");

这里涉及到了设备号在这一概念,在启动linux的情况下使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号

2.3 实现设备的具体操作函数

file_operations 结构体就是设备的具体操作函数。定义一个结构体并对其中初始化其中的open、release、read 和 write 等具体的设备操作函数即是实现了设备具体操作函数的初始化。

一般至少包含open、release、read、write四个功能函数。

2.3.1首先保证file_operations结构体内的函数指向我们编写的驱动实现函数

static struct file_operations test_fops = {
.owner = THIS_MODULE, 
.open = chrtest_open,
.read = chrtest_read,
.write = chrtest_write,
.release = chrtest_release,
};

2.3.2编写驱动实现函数

/*打开设备*/
static int chrtest_open(struct inode *inode,struct file *filp)
{
	/*用户具体实现功能*/
	return 0;
}
/*关闭设备*/
static int chrtest_release(struct inode *inode,struct file *filp)
{
	/*用户具体实现功能*/
	return 0;
}
/*从设备读取*/
static int chrtest_read(struct file *filp,char __user *buf,size_t cnt, loff_t *offt)
{
	/*用户具体实现功能*/
	return 0;
}
/*从设备写入*/
static int chrtest_write(struct file *filp,char __user *buf,size_t cnt, loff_t *offt)
{
	/*用户具体实现功能*/
	return 0;
}

2.3.3编写驱动入口函数和出口函数

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

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(200, "chrtest", &test_fops);
	if(retvalue < 0){
		/* 字符设备注册失败,自行处理 */
	}
	return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(200, "chrtest");
}

2.3.3指定模块入口函数和出口函数

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

2.4 添加 LICENSE 和作者信息

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

示例代码:

MODULE_LICENSE("GPL");//LICENSE 采用 GPL 协议。
MODULE_AUTHOR("zwz");//添加作者名字

LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。

三、测试APP的编写

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
static char usrdata[] = {"usr data!"};

/*
* @description : main 主程序
* @param - argc : argv 数组元素个数
* @param - argv : 具体参数

* @return : 0 成功;其他 失败
*/ 
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];

	if(argc != 3){
		printf("Error Usage!\r\n");
	return -1;
	}
	
	filename = argv[1];


	/* 打开驱动文件 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
	printf("Can't open file %s\r\n", filename);
	return -1;
	}
	
	if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
	retvalue = read(fd, readbuf, 50);
	if(retvalue < 0){
		printf("read file %s failed!\r\n", filename);
	}else{
		/* 读取成功,打印出读取成功的数据 */
		printf("read data:%s\r\n",readbuf);
	}
	if(atoi(argv[2]) == 2){//向驱动文件写数据
		memcpy(writebuf,usrdata,sizeof(usrdata));
		revalue = write(fd,writebuf,50);
		if(revalue < 0){
			printf("Write file:%s faild!\r\n",filename);
		}
	}
	/*关闭设备*/
	revalue = close(fd);
	if(revalue < 0){
		printf("Can't close file: %s \r\n",filename);
		return -1
	}
	return 0;
}

四、将.ko文件和app复制到根文件目录

将 chrdevbase.ko 和 chrdevbaseAPP 复制到rootfs/lib/modules/4.1.15 目录中。
使用如下命令即可

sudo cp chrdevbase.ko chrdevbaseApp /home/zwz/linux/nfs/rootfs/lib/modules/4.1.15/ -f

五、uboot下测试过程

参考以下博客:
Linux字符设备驱动测试过程中常用的命令

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值