Linux驱动开发【一】:字符设备驱动

[驱动文件内容]

对设备的操作函数

  1. 打开设备
  2. 从设备文件中读取数据
  3. 向设备文件内写入数据
  4. 关闭设备文件

三个组件

  1. 设备操作函数结构体
  2. 设备初始化(作为驱动的入口函数)
  3. 设备注销(作为驱动的出口函数)

其他信息

  1. LICENSE
  2. 作者信息

驱动文件包含的函数

  1. 打开设备
    static int chrdevbase_open(struct inode *inode, struct file *filp)
  2. 从设备读取数据
    static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
  3. 向设备写数据
    static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
  4. 关闭设备
    static int chrdevbase_release(struct inode *inode, struct file *filp)
  5. 驱动入口函数(注册)
    static int __init chrdevbase_init(void)
  6. 驱动出口函数(注销)
    static void __exit chrdevbase_exit(void)

驱动文件包含的设备操作函数结构体

这个结构体内的成员函数被赋值为我们在驱动文件里编写的相对应的函数
static struct file_operations chrdevbase_fops =
{
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};

注意事项

  1. 关于从设备读取数据的函数(xxx_read())
    在编写 xxx_read 函数的时候,不要直接读取在内核中定义的数据,即 kerneldata 的内容,
    而是要先用 memcpy 函数拷贝一份数据到发送缓冲区 readbuf 中,然后用 copy_to_user() 函数(该
    函数的功能是复制一段数据给用户)将缓冲区的数据复制给用户空间的数据缓冲区 buf 中,这样做的
    目的主要是为了保证内核的独立性。而在应用程序中读取buf内的数据就行了。

  2. 关于向设备写数据的函数(xxx_write())
    直接利用 copy_from_user() 函数将数据搬到 writebuf 中就行。

  3. 关于驱动初始化(chrdevbase_init())
    利用函数 register_chrdev() 注册设备驱动,该函数有3个参数,分别是:

    1. 主设备号: CHRDEVBASE_MAJOR
    2. 设备名: CHRDEVBASE_NAME
    3. 设备操作函数结构体: &chrdevbase_fops

[应用文件内容]

对驱动文件的操作过程

  1. 获取驱动文件的文件名
  2. 用open()函数打开驱动文件
  3. 用read()函数从驱动文件内读取数据(optional)
  4. 用write()函数向驱动文件写入数据(optional)
  5. 读写完毕后用close()函数关闭文件

参数说明

  1. fd: 文件描述符
  2. retvalue: 文件操作返回值
  3. *filename: 指针,用来存放文件的名字
  4. argc: 应用程序参数个数
  5. argv[]: 应用程序的具体参数集合,字符串的形式
    argv[0]: 应用程序名
    argv[1]: 要操作的驱动文件名
    argv[2]: 操作标志
  6. readbuf[100]: 数据接收缓冲区
  7. writebuf[100]: 数据发送缓冲区

应用程序的主函数流程

  1. 先判断应用程序参数是否齐全,齐全则继续,不齐全则返回 -1;
  2. 利用 filename 接收驱动文件的名字,并放入argv[1]中;
  3. 用 open 函数打开文件,并用 fd 接收函数的返回值,
    如果 fd = -1 ,则说明文件打开失败;
  4. 文件打开成功后,用 read 函数读取文件,用 retvalue 接收函数的返回值,
    如果 retvalue < 0 则说明读取文件内容失败,否则成功读取数据,并打印出来,
    read(fd, readbuf, 50) 的意思是读取指定的文件描述符的文件中50个字节数据,
    并放入 readbuf 即接收缓冲区中;
  5. 再利用 write 函数将数据写入驱动文件中,需要注意的是,向驱动文件写入数据时,
    不能直接写入,而应该利用 memcpy 函数拷贝一份用户数据存放到临时的发送缓冲区中,
    以此来保证内核的独立性。因为 writebuf 在函数执行时才会临时在堆栈中申请内存空间,
    当函数执行完后就会立即被释放掉;
  6. 当读写操作完成后,就用 close 函数关闭驱动文件。
  • 注意事项:读写操作要通过判断argv[2]的数值来决定是读数据还是写数据,可以利用
    函数 atoi() 将字符串类型转化为整数型数据。

代码

chrdevbase.c文件(驱动文件)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>


#define CHRDEVBASE_MAJOR	200					/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 		/* 设备名  */

static char readbuf[100];						/* 读缓冲区 */
static char writebuf[100];						/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};	/* 内核要发送的数据 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @description		: 从设备读取数据
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	
	/* 向用户空间发送数据 */
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	retvalue = copy_to_user(buf, readbuf, cnt);
	if(retvalue == 0){
		printk("kernel senddata ok!\r\n");
	}
	else{
		printk("kernel senddata failed!\r\n");
	}
	
	//printk("chrdevbase read!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	/* 接收用户空间传递给内核的数据并且打印出来 */
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0){
		printk("kernel recevdata:%s\r\n", writebuf);
	}
	else{
		printk("kernel recevdata failed!\r\n");
	}
	
	//printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*./chrdevbaseApp /dev/chrdevbase 1 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	/* para:主设备号,设备名,设备操作函数结构体 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	/* 返回值小于0则说明设备注册失败 */
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);		/* 用于注册字符设备驱动 */
module_exit(chrdevbase_exit);		/* 用于注销字符设备驱动 */

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("swiler");

chrdevApp.c文件(应用文件)

#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[] 	: 具体参数,字符串形式
 *  ./chrdevbaseAPP	<filename>
 * @return 			: 0 成功;其他 失败
 */
/* argv[0]是应用程序本身,argv[1]是要操作的文件名,argv[2]是操作标志 */
int main(int argc, char *argv[])
{
	//fd:文件描述符
	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));
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0){
			printf("write file %s failed!\r\n", filename);
		}
	}

	/* 关闭设备 */
	retvalue = close(fd);
	if(retvalue < 0){
		printf("Can't close file %s\r\n", filename);
		return -1;
	}

	return 0;
}

Makefile文件

KERNELDIR := /home/swiler/linux_core/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

终端执行命令

编译

#清理工程
make clean
#编译工程,生成驱动 chrdevbase.ko 文件
make
#利用交叉编译器编译应用文件 chedevbaseApp.c
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
#将两个文件复制到自己构建的根文件系统中去
cp chrdevbase.ko  chrdevbaseApp /home/swiler/workdir/rootfs/lib/modules/4.1.15 -f

启动

开发板在u-boot模式下,通过tftp启动内核,进入 /lib/modules/4.1.15/ 目录下;

  1. 先加载驱动文件,用 insmod chrdevbase.ko 或 modprobe chrdevbase.ko 命令。
    (首次使用modprobe命令要先执行depmod命令)。
  2. 再创建设备节点文件
    命令:mknod /dev/chrdevbase c 200 0
  3. 执行应用程序 chrdevbaseApp,测试驱动是否正常运行。

正常运行则会出现如图所示:

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值