韦东山嵌入式教程第四篇Linux驱动基础知识学习笔记(1)——Hello驱动程序

驱动入门——Hello驱动程序

1、怎么写出一个驱动程序?

  1. 确定主设备号
  2. 定义自己的file_operations结构体
  3. 实现对应的open、read、write函数,填入file_operations结构体
  4. 实现入口函数。安装驱动程序时,就会调用入口函数,把file_operations结构体告诉内核,注册驱动程序
  5. 实现出口函数。卸载驱动时就会调用该出口函数unregister_chrdev()
  6. 其他完善,提供设备信息,自动创建设备节点
    说明:
    1、file_operations结构体怎么注册?
    通过函数register_chrdev(… , file_operations,…)来注册到内核中的“数组”chrdevs[主设备号](可以自定义主设备号,也可以传入0,让内核给驱动程序分配主设备号)。应用程序在调用open、read、write函数时会根据主设备号选择对应的驱动程序,即从chrdevs[ ]中去去找到对应的file_operations结构体。那么谁来调用这个注册函数register_chrdev()?答案是入口函数。安装驱动时就会调用入口函数来注册file_operations结构体。

应用程序在调用open打开一个文件时会返回一个整数,

m = open ("/dev/xxx" , flags,mode);
这个整数m 对应结构体 struct file{ }

struct file{
	...
	const struct file_operations  *f_op;
	...
	unsigned int       f_flags;  //open中的flags和mode都被保存在这里
	fomde_t            f_mode;
	...
}

去读写文件时,文件的当前偏移地址也会保存在struct file结构体的f_pos成员里。
file_operations结构体中有驱动程序提供的open等函数

struct file_operations{
ssize_t (*read)(......);
ssize_t (*write)(......);
long (unlocked_ioctl)(......);
int(*open)();
int(*release)();
}

2、开始code

可参考内核linux-4.4_xxx中的/drivers/char/misc.c驱动的写法
Hello驱动程序的作用:APP调用write时,传入数据保存到驱动程序中,read把数据再读出来。
以下是hello驱动的代码

//头文件参考misc字符驱动代码copy的
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 1. 确定主设备号                                                                 */
static int major = 0;   //设备号可以自定义,0号是由OS帮我们设定
static char kernel_buf[1024];
static struct class *hello_class;


#define MIN(a, b) (a < b ? a : b)

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)  
//__user是一个空的宏,表示这个buf数组是来自用户空间,不能随意访问
//size_t是一个为了增强程序可移植性而设立的新数据类型,相当于一个无符号整数,在32位机器中是32位,在64位机器中是64位(int在64位机器中也是32位的)。 size_t类型通常用于循环、数组索引、大小的存储和地址运算。
//loff_t用于维护当前读写位置
//ssize_t是signed的
{
	int err;    //定义这个变量是为了用上copy_to_user的返回值,否则就会报错
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));//把内核里面的数组内容拷贝到用户的数组里面,第三个参数是拷贝的大小
	return MIN(1024, size);
}

static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));
	return MIN(1024, size);
}

static int hello_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int hello_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 2. 定义自己的file_operations结构体  */
static struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_drv_open,
	.read    = hello_drv_read,
	.write   = hello_drv_write,
	.release = hello_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);//调试信息
	major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */
	//第一个参数是默认主设备号,第二个是名字,第三个是file_operations结构体的指针
	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "hello");
		return -1;
	}
	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
	
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit hello_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	unregister_chrdev(major, "hello");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./hello_drv_test -w abc  写数据
 * ./hello_drv_test -r 	    读数据
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;
	
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);   //读写打开
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))//如果是-w写文件
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);  //往设备文件里面写数据
	}
	else
	{
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	close(fd);
	return 0;
}

简单来说写驱动就是注册设备,实现fops,实现init和exit函数,然后应用程序就能往设备文件中读写传输数据了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
也可去华为网盘下载! http://dl.dbank.com/c0f2x98c6n# 第1课 环境搭建及工具、概念介绍 第2课 GPIO实验 第3课 存储管理器实验 第4课 MMU实验 第5课 NAND FLASH控制器 第6课 中断控制器 第7课 系统时钟和UART实验 第8课 LCD实验 第9课第1节 u-boot分析之编译体验 第9课第2节 u-boot分析之Makefile结构分析 第9课第3节 u-boot分析之源码第1阶段 第9课第3节 u-boot分析之源码第2阶段 第9课第4节 u-boot分析之u-boot命令实现 第9课第5节 u-boot分析_uboot启动内核 第10课第1节 内核启动流程分析之编译体验 第10课第2节 内核启动流程分析之配置 第10课第3节 内核启动流程分析之Makefile 第10课第4节 内核启动流程分析之内核启动 第11课第1节 构建根文件系统之启动第1个程序 第11课第2节 构建根文件系统之init进程分析 第11课第3节 构建根文件系统之busybox 第11课第4节 构建根文件系统之构建根文件系统 第12课第1节 字符设备驱动程序之概念介绍 第12课第2.1节 字符设备驱动程序之LED驱动程序_编写编译 第12课第2.2节 字符设备驱动程序之LED驱动程序_测试改进 第12课第2.3节 字符设备驱动程序之LED驱动程序_操作LED 第12课第3节 字符设备驱动程序之查询方式的按键驱动程序 第12课第4.1节 字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构 第12课第4.2节 字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构 第12课第4.3节 字符设备驱动程序之中断方式的按键驱动_编写代码 第12课第5节 字符设备驱动程序之poll机制 第12课第6节 字符设备驱动程序之异步通知 第12课第7节 字符设备驱动程序之同步互斥阻塞
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值