Linux设备驱动-字符驱动

1、应用程序的主要作用与实现方法

三个作用:与内存进行IO交互(比如读写文件);输出信息到外设(如点灯);从外设读入信息(如识别键盘按键)。应用程序一般提供三个标准的接口函数来实现这三个功能:open、write、read,这三个接口函数的具体实现在标准C语言库中。C语言库和应用程序都属于用户空间的东西。

open负责打开一个文件,write负责往打开的文件里写入信息,read负责从打开的文件里读取信息。(注意:Linux中一切皆是文件,内存和外设也是,所以这个打开的文件可以是内存也可以是具体的外设,比如我open一个关于led的文件,再往里面write一个“1”,则相当于将信号1输出到led,从而点亮led)。

2、open、write、read的具体执行流程

调用这三个函数实际上是执行一个软中断(或者叫异常)指令:SWI value,三个函数各自对应的value值。调用SWI指令后,系统会进入内核的异常处理函数里(就从用户空间进入了内核空间),内核里有个东西叫做系统调用接口,系统调用接口根据SWI指令中value值的不同,调用对应的sys_open、sys_write、sys_read,sys_open、sys_write、sys_read所对应的叫虚拟文件系统(VFS)。

但是这三个函数并不是真正直接操作硬件或者内存完成相应的工作,而是通过进一步调用与三个函数对应的驱动程序去完成具体的工作;比如要打开的是led文件,输出的信息是输出到led,读入的信息是从键盘读入,那么最简单的方式就是分别有三个名为led_open、led_write、key_read的驱动程序分别与之对应。

3、实操案例:编写一个小的字符驱动程序

3.1 编写具体的n个驱动函数

观察用户程序,写出其需要用到的驱动函数,这里用户程序用了open和write(用于字符操作),则先写出其对应的驱动函数,这里为my_drv1_char_open与my_drv1_char_write。

3.2 将所编写的n个驱动函数封装为一个整体,方便调用

kernel代码中include/linux下的fs.h中定义了一个file_operations结构体,用户程序中有什么接口函数(如open、write等),file_operations结构体就有对应的成员。我们需要做的就是新创一个file_operations结构体对象(这里为 my_drv1_char_fops),将具体的open和write驱动函数赋给对应的结构成员,这样所写的n个驱动函数就有了一个统一的调用接口。

3.3 让内核知道有这n个驱动函数可以使用

这个过程称为“模块注册”,用到Linux的fs.h中的字符设备注册函数:register_chrdev(111,“my_drv1_char”,& my_drv1_char_fops)。其中111为主设备号,是用户程序中open(“/dev/my_drv1_cpp”,O_RDWR)所打开的字符设备的设备编号,这样表明驱动程序操作的是111号字符设备。设备号可以在命令行键入 cat /proc/devices来查看。"my_drv1_char"是驱动程序的名字,出现在/proc/devices中

3.4 让内核在insmod装载模块时知道模块从哪里进入

编写好的驱动模块首先在内核中注册(3.3),当内核需要用到该模块时,将会用insmod指令将模块加入到内核中。模块进入内核后就需要运行,运行需要入口函数(init函数)和入口宏,这里为my_drv1_char_init,以及module_init(my_drv1_char_init),这两个缺一不可。

3.5 rmmod卸载模块

当驱动模块不再使用时,需要从内核里卸载出去,用到rmmod指令,同时需要一个出口函数(清除掉注册的相关信息)以及出口宏,这里为my_drv1_char_exit和module_exit

3.6 运行测试

3.6.1 编译驱动和用户程序

##驱动的Makefile
KERDIR = /lib/modules/$(shell uname -r)/build
obj-m += my_drv1_char.o

build:
        make -C $(KERDIR) M=$(CURDIR) modules

clean:
        make -C $(KERDIR) M=$(CURDIR) clean

3.6.2 运行用户程序后的结果

文件名:my_drv1_app.c,记为用户程序A
描述:A为要使用到驱动程序的用户程序,具体表现为:(1)A将调用标准接口函数open()以打开位于/dev/目录下的文件my_drv1_cpp,,O_RDWR是文件的操作权限,如果open打开成功则返回1,否则返回一个负数。
(2)A在打开对应文件后将调用write函数向文件中写入内容。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main( int argc,char **argv)
{
int fd;
int val=1;
fd=open("/dev/my_drv1_cpp",O_RDWR);
if(fd<0)
printf("can not open! \n");
write(fd,&val,4);
return 0;
}
文件名:my_drv1_char.c,记为驱动程序B
描述:A中的open与write需要具体的my_drv1_char_open与my_drv1_char_write来实现。也就是说真正负责实际
操作的只有my_drv1_char_open与my_drv1_char_write这两个函数,在这里表现为打印出两句话。
//以下这些头文件并不都是必须的
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/fb.h>
#include <linux/notifier.h>
#include <linux/proc_fs.h>
#include <linux/syscore_ops.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/mutex.h>
static int my_drv1_char_open(struct inode *inode,struct file *file)
{
printk("my_drv1_char_open\n");
return 0;
}
static ssize_t my_drv1_char_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
printk("my_drv1_char_write\n");
return 0;
}
static struct file_operations my_drv1_char_fops={

.owner=THIS_MODULE,//一个宏,指向模块编译时自动创建的__this_module变量
.open = my_drv1_char_open,
.write = my_drv1_char_write,
};
int my_drv1_char_init(void)
{
//register_chrdev,这个注册函数是专门用来注册字符驱动的。参数major为主设备号
register_chrdev(111,"my_drv1_char",& my_drv1_char_fops);
return 0;
}
void my_drv1_char_exit(void)
{
unregister_chrdev(111,"my_drv1_char");
}
module_init(my_drv1_char_init);
module_exit(my_drv1_char_exit);

printk():与C标准库中的printf类似,由于内核中没有C库,所以内核自己弄了一个。可以在终端中键入:dmesg -e来查看printk的输出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值