Linux驱动笔记(一)

为什么要写笔记

    再整理笔记之前,我想先说明一个问题——为什么要写笔记?写笔记的理由非常简单,就是对学习过的问题整理,增强对知识系统的理解。再工作中,我发现一个问题,就是如果一个问题可以讲明白了,其实这个问题已经解决了一大半。而且整理笔记其实就是对问题说明分一个过程。我在使用ALIENTEK IMX6ULL 学习Linux驱动的时候花费了大量的时间,后来发现很多时间花费非常不值,因为有些问题其实某种情况下可以不搞明白,知道问题的自变量和因变量之间关系就可以了。可以简单的理解为照猫画虎或者依葫芦画瓢之类。知其然就可以了。至于知其所以然,在过去知识总量比较少的年代还可以作为铭言。在当今知识总量近乎无穷大的时代,要知其所以然几乎不可能了。最后我发现其实要学习的知识干货其实也不多。至于为什么厂家的学习文本为什么近2000页?首先厂家要考虑到使用客户水平,为了更多的兼容客户,只有以0基础的客户作为参考点来设计教材和书籍。作为写过代码的用户来说,大部分内容浏览理解一下就可以了。
   **让我们开始吧**
       工欲善其事,必先利其器。所以在开始之前必须搭建开发环境,因为周围没有人写过Linux,也没有人使用C语言在Linux系统中写过程序。所以我前后花了10几天的时间搭建了系统。当然我白天要上班,公司的工作任务也很紧张,只有晚饭后2小时可以利用的时间。但是系统搭建完成其实发现过程很简单。花费的时间有些不值。所以我建议如果周围有搭建过此类系统的朋友,直接请求支援最好。路虽远行必至。有时候也有运气的缘故,也许过程非常顺利,也许非常非常的不顺利,一切随缘吧。
       第一  我采购的硬件是 ALIENTEK IMX6ULL开发板,所以我使用的就是该硬件。
       第二  我学习的是ALIENTEK IMX6ULL开发板,所以我使用的软件和代码大量借用了该厂商的软件和代码。如果厂商认为有关知识产的问题,可以通知我,我将更换,避免在知识产权方面的纠纷。
       第三  本人虽然已经工作很多年,但是在Linux及其相关工作方面经验有限,如果涉及专业知识错误或者描述逻辑错误,希望网友不吝赐教。本人将尽可能的完善和修改。
       第四  整理笔记目的如果对您的学习有帮助,我将非常欣慰。
       **关于字符驱动**
            ALIENTEK IMX6ULL 采用了大量篇幅介绍了字符驱动,通过字符驱动的编写和讲解,介绍了大量的Linux的驱动的基础知识。知识之所以是知识,因为我们还没有掌握,如果掌握了,并且可以熟练应用,知识就会转化为常识。学习过程其实就是知识向常识的转化。等这个过程完成,学习的过程也就结束了。
     ## 字符驱动的框架
     整个字符驱动代码量不大,所以可以通过一个完整的
#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 LED_MAJOR	200				/* 主设备号 */
#define LED_NAME		"chrdevbase" 	/* 设备名     */

static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data luo_xinli!"};

/*
 * @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");

chrdevbase_fops 是驱动的关键, 其成员变量open 、read、write、release是四个指向函数的指针。指针open、read、write、read 指向四个函数。

static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

四个函数分别是 chrdevbase_open( )、chrdevbase_read( )、chrdevbase_write( )、chrdevbase_release( ).函数的形参都已经定义完成, 这个是驱动规范决定的。并且是由驱动程序设计人员完成。由 register_chrdev()函数将该结构体注册到系统中。register_chrdev()函数在 chrdevbase_init被调用。

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
module_init()函数功能非常明确,就是注册 chrdevbase_init( ),当驱动被加载时就会运行被注册的函数,目的是对设备和变量的初始化。module_exit( )功能也非常明确,就是这侧chrdevbase_exit(),当驱动卸载是就会运行被注册的函数,目的时当设备驱动卸载时释放资源。register_chrdev()在chrdevbase_init( )被调用,也就当驱动加载时使用register_chrdev( )函数,将chrdevbase_fops 注册到系统中。
驱动加载后APP程序中fopen( )就是调用chrdevbase_open( )函数,APP程序中fread( )就是调用chrdevbase_read( ),fwrite( )就是调用 chrdevbase_write( ), fclose( )就是调用chrdevbase_release( )函数。明白了APP程序fopen( )、fread( )、fwrite( )、fclose( )与chrdevbase_fops 中open、read、write、release 指向函数的关系其实就明白了驱动函数的框架结构,作为程序员在此框架中实现open、read、write、release指向的函数就了,

  函数是加载驱动后,因为module_init(chrdevbase_init)注册了chrdevbase_init( )函数,所以操作系统在加载驱动后首先执行chrdevbase_init( )函数。因为该驱动只是一个最简单的驱动,并不使用任何硬件。所以chrdevbase_init( )仅仅完成一件事情,就是注册驱动成员函数。我们仔细分析该函数。
`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;
}

retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
完成了驱动成员函数的注册,CHRDEVBASE_MAJOR是设备id, CHRDEVBASE_NAME是函数名。chrdevbase_fops就是成员函数的集合。‘
register_chrdev( )函数功能是注册设备,在注册之前设定了设备id和设备名称,并且实现了chrdevbase_fops集合中的open、read、write、realse函数。只要实现open、read、write、realse函数就可以完成注册。
在驱动加载要的注册设备,在驱动卸载时就必须注销该设备。module_exit(chrdevbase_exit)注册了chrdevbase_exit是驱动卸载时执行的函数。

static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);注销了该设备,注销时只需指定id和设备名称,

chrdevbase_open( )函数是打开设备

static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

chrdevbase_read( )函数是从设备中读取数据。

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;
}

其中重要的函数 “retvalue = copy_to_user(buf, readbuf, cnt)", copy_to_user()函数是系统提供的函数。buf、cnt来自于chrdevbase_read( )的形参,readbuf是设备数据存储的缓冲区。

chrdevbase_write( )函数向设备写入数据。
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;
}

chrdevbase_write()函数中主要函数是retvalue = copy_from_user(writebuf, cnt),copy_from_user( )函数是将数据写入设备。buf是源数据地址,cnt是数据长度。

chrdevbase_release( ) 关闭设备,

 如果总结一下本节内容,第一  这是一个最基本字符驱动程序的框架;
                                        第二   chrdevbase_init( ) 是驱动加载时执行,相当于驱动的入口函数;
                                        第二  chrdevbase_exit( ) 是驱动卸载时执行,相当于驱动出口函数;
                                        第三  chrdevbase_open( )、chrdevbase_write( )、chrdevbase_read( )、chrdevbase_release( )是设备的open( )、write( )、read( )、        realse(  )的函数。

        下一节看看从APP层面学习驱动完成后,怎么使用驱动程序。
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值