初学字符设备驱动

经过上次学习Linux的helloworld驱动,现在对驱动程序有了一点概念了。今天,接着往下学习Linux驱动咯!
Linux驱动学习任务:
(1)网络接口驱动:重点;(2)块设备驱动;(3)字符设备驱动:重点
Linux用户如何使用上面这些驱动程序的?
对应序号分别是:
(1)用户-->套接字-->协议栈-->网络设备驱动-->网络接口设备
(2)用户-->文件系统-->块设备文件-->块设备驱动-->块设备
(3)用户-->字符设备文件-->字符设备驱动-->字符设备
今天主题:简单的字符设备驱动程序设计框架
相关理论:
1.字符设备:是一种按字节来访问的设备
2.字符驱动:负责驱动字符设备,通常实现open、close、read、write的系统调用
3.主设备号:反映设备类型
4.次设备号:区分同类型的设备,被驱动程序用来辨别操作的是哪一个设备,比如操作多个串口时,次设备就可以知道操作哪一个
5.内核中设备号的描述:
dev_t类型:unsigned int 32位的数,高12位主设备号,底20位次设备号
分离主次设备号:使用宏
MAJOR(dev_t drv)
MINOR(dev_t drv)
6.内核如何分配设备号
①静态申请:
首先程序员根据Documentation/devices.txt查看确定没有使用的主设备号,然后使用register_chrdev_region函数注册设备号
缺点:容易设备号冲突
②动态分配
交给内核自己去安排使用哪一个设备号,因为它最清楚哪一个编号没有被使用
程序中使用alloc_chrdev_region函数注册,使用unregister_chrdev_region函数注销
insmod安装完驱动之后cat proc/drivers查询设备号
③还有一个古老的函数可以实现设备号分配
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops),当major为0时为动态分配,不为0时major数值就是指定分配的设备号
3个参数意义:
major:主设备号
name:设备名字

fops:指向驱动操作函数结构体

成功注册:返回值是主设备号

注:在2.6内核里边register_chrdev_region()和unregister_chrdev_region()函数分别是register_chrdev()和unregister_chrdev()的升级版。

这两个版本的函数有什么区别?

①老版本:看源码可以发现:把添加字符设备的工作囊括了进来

register_chrdev()

cdev = cdev_alloc();//申请字符设备

cdev_add(cdev,MKDER(cd->major,0),256); //添加字符设备

②新版本:将老版本的函数拆开,添加字符设备另外操作

register_chrdev_region();

cdev_add();

实验操作:

一、新建一个目录cdrv,在其中新建char_drv.c文件,文件内容如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

int major;

static int char_drv_open(struct inode *inode, struct file *file)
{
	printk("char_drv_open\n");
	return 0;
}

static int char_drv_write(struct file * file, const char __user * buffer, size_t count, loff_t * ppos)
{
	printk("char_drv_write\n");
	return 0;
}

static struct file_operations char_drv_fops = {
    .owner  =   THIS_MODULE,
    .open   =   char_drv_open,     
	.write	=	char_drv_write,	   
};

static int char_drv_init(void)
{
	printk("char_drv_init\n");
	major = register_chrdev(252, "char_drv", &char_drv_fops);
	return 0;
}

static void char_drv_exit(void)
{
	printk("char_drv_exit\n");
	unregister_chrdev(major, "char_drv"); // 卸载
}

module_init(char_drv_init);
module_exit(char_drv_exit);

MODULE_AUTHOR("CLBIAO");
MODULE_DESCRIPTION("Just for Demon");
MODULE_LICENSE("GPL");

代码分析:
(1)首先编写驱动操作函数char_dev_open()和char_dev_write()
(2)上面这些操作函数怎么告诉内核?通过char_drv_init函数初始化
   首先,定义file_operation型结构体char_drv_fops,填充他,就会有那么一个对应的函数接口。填充内容:具体的驱动函数的指针
   然后,通过register_chrdev(设备号,设备名,操作函数结构体)函数把这个结构体注册到内核数组chardev[]里面,用户调用该设备时是根据设备号为索引在内核数组里面进行匹配的
(3)卸载驱动程序的时候就是把char_drv_fops从chardev[ ]拖出来
(4)修饰char_drv_init、char_drv_exit
   宏module_init和module_exit就是给这两个函数添加属性:
   module_init:加载到内核映像的initcall区段,初始化加载,后期内存释放
   module_exit:将退出函数添加到指定的区段,调用时直接在该区段搜索。
二、驱动程序万能的Makefile编写:

ifneq ($(KERNELRELEASE),)
obj-m	+= char_drv.o

else
KDIR = /opt/EmbedSky/linux-2.6.30.4  

all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux

clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif

这个Makefile文件执行过程:第一次进入Makefile,KERNELRELEASE变量为空,条件不成立,执行else分支,make后用“-C”选项指定进入“KDIR”目录读取里边的Makefile文件,从中读取信息,即KERNELRELEASE在这里边有被赋值,接着根据“M=$(PWD)”进入到当前的目录,执行当前目录下面的Makefile,这一次KERNELRELEASE已经有数值了,执行ifneq分支,注意目标文件的名字部分要和其依赖文件的一样,不然编译将出错,这里的编译方式是“obj-m”编译成模块(“obj-y”则是编译进内核)

三、编译make,将生成的char_drv.ko拷贝到nfs文件系统里面,启动开发板内核并挂载nfs文件系统
四、编写测试应用程序:

cdev_test.c代码:

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

int main(void)
{
	int fd;
	int val = 1;
	fd = open("/dev/xxx",O_RDWR);//打开设备xxx,xxx设备需要使用mknod手动创建设备节点
	if(fd < 0)
	{
		printf("open error\n");
	}
	write(fd,&val,4); //写xxx设备
	return 0;
}

ubuntu下编译生成可执行文件:
arm-linux-gcc -static -o char_dev char_dev.c
由于我们制作的是静态链接的根文件系统,库文件不共享,所以这里要添加-static编译选项
将编译好的可执行文件复制到nfs文件系统中。

五、在开发板控制台终端输入:
insmod char_drv.ko
cat proc/devices          查看驱动的设备号,当然如果是手动分配设备号程序员自己心中有数了
mknod /dev/xxx c 252 0    手动创建设备文件,这里的“xxx”为设备文件(节点)名字,名字不重要,重要的是这个名字对应的设备号,这个名字要和app打开的设备名称一致
ls -l /dev/xxx
./cdev_test

运行结果就是:

char_dev_open

char_dev_write

运行的整个流程就是:

首先,顶层APP的open()函数根据设备节点文件/dev/xxx里面的信息(包括:设备类型、主设备号等),找到内核里边字符设备数组chrdev[ ]匹配的一项的file_operation结构体,然后调用结构体里边“.open”指向的函数char_dev_open( )。write()函数根据刚才open打开的那个设备文件的描述符找到设备文件xxx,再根据他找到对应file_operation结构体。write()传入4字节,即val指针指向的内容(int型数值“1”),这个数值最终是被传给file_operation结构体里边“.write”所指向的函数的第二个参数,最终有没有被使用就决定于这个函数了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值