chrdevbase字符设备驱动开发实验
chrdevbase是一个虚拟设备,有两个缓冲器,一个读,一个写,两个缓冲器的大小都为100 bytes,应用程序可以从chrdevbase设备读写数据。
新建 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>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : chrdevbase.c
作者 : 左忠凯
版本 : V1.0
描述 : chrdevbase驱动文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/
#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,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 0 成功;其他 失败
*/
static int __init chrdevbase_init(void)
{
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
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("zuozhongkai");
驱动程序操作函数通过系统调用进入内核态,不能再使用printf函数 ,而采用printk函数。
printk有8个消息级别,在 include/linux/kern_levels.h里面定义。0最高级,7最低级
#define KERN_SOH "\001"
#define KERN_EMERG KERN_SOH "0" // 紧急事件,一般是内核崩溃 紧急事件
#define KERN_ALERT KERN_SOH "1" //需要立即处理
#define KERN_CRIT KERN_SOH "2" //临界条件,严重的软硬件错误
#define KERN_ERR KERN_SOH "3" //错误信息,驱动程序中使用报告硬件错误
#define KERN_WARNING KERN_SOH "4" //警告信息
#define KERN_NOTICE KERN_SOH "5" //必要提示信息
#define KERN_INFO KERN_SOH "6" //提示信息
#define KERN_DEBUG KERN_SOH "7" //调试信息
printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");
//
在用printk时根据不同消息级别显示,在 include/linux/printk.h中宏CONSOLE_LOGLEVEL_DEFAULT来设置,默认值为4
#define CONSOLE_LOGLEVEL_DEFAULT 4
// 优先级高于4的消息才能显示在控制台
参数 filp有个叫做 private_data的成员变量, 一般在驱动中将指针指向设备结构体,会存放的一些属性。
char __user *buf; 定义用户空间的内存。内核空间不能直接操作用户空间的内存,需要使用copy_to_user和copy_from_user函数来访问。
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
// 参数 to表示目的,参数 from表示源,参数 n表示要复制的数据长度。如果成功,返 回值为 回值为 0,如果复制失败则返回负数。
编译驱动程序
创建Makefile文件,将chrdevbase.c编译为.ko模块
KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
// 将chrdevbase.o编译为.ko模块
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
make编译后生成一个chrdevbase.ko文件
编译测试APP
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : chrdevbaseApp.c
作者 : 左忠凯
版本 : V1.0
描述 : chrdevbase驱测试APP。
其他 : 使用方法:./chrdevbase /dev/chrdevbase <1>|<2>
argv[2] 1:读文件
argv[2] 2:写文件
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/
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));
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;
}
编译APP
# arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
# file chrdevbaseApp
// 查看文件属性
加载驱动模块
# insmod chrdevbase.ko
# modprobe chrdevbase.ko
如果出现上面的提示信息,请输入depmod命令自动生成modules.dep,输入 depmod命令以后会自动生成 modules.alias、 modules.symbols和 modules.dep这三个文件
# modprobe chrdevbase.ko
// 重新输入命令,加载成功
# cat /proc/devices
// 查看当前系统中的缩影设备,查找设备号
创建设备节点文件
驱动加载成功,需要在/dev目录下创建一个与之对应的设备节点文件,APP就是通过这个设备节点文件来完成对具体设备的操作。
# mknod /dev/chrdevbase c 200 0
// c 表示字符设备 , 200 表示主设备号, 0 表示次设备号
# ./chrdevbaseApp /dev/chrdevbase 1
# ./chrdevbaseApp /dev/chrdevbase 2
# rmmod chrdevbase.ko
# lsmod