专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

字符驱动编写小结(基于mini2440,LED驱动)

作者:佚名   来源:本站原创   点击数:  更新时间:2014年08月18日   【字体:

    编程:需要什么功能(机制)、如何使用这些功能(策略)

    作为驱动程序编写者,我们需要在所需的编程时间以及驱动程序的灵活性之间选择一个可接受的折中。读者可能奇怪于说驱动程序“灵活”,我们用这个词实际上是强调设备驱动程序的作用在于提供机制,而不是提供策略。
    机制mechanism,策略policy。如果你看过《linux device drivers》,里面给出了大概的介绍。机制提供了干什么(do what),策略提供如何做(how to do)。驱动程序完成机制的功能,把策略的实现留给用户的应用程序。通常在机制中,驱动程序要完成打开,关闭,读写,控制等功能。这些都是设备使用时最基本的操作。而策略中就要实现一些高级的数据处理或界面功能。通过例子来说明会更好些。

    linux中设备通常分为三类:字符设备、块设备、网络接口;

    编写访问硬件的内核代码时,不要给用户强加任何特定策略,驱动程序应该处理如何使硬件可用的问题,而将怎样使用硬件的问题留给上层应用程序;
    不带策略的驱动程序包括一些典型的特性:同时支持同步和异步操作、驱动程序能够被多次打开、充分利用硬件特性,以及不具备用来“简化任务”的或提供与策略相关的软件层等。
    不带策略的软件设计是软件设计者的一个共同目标。
 
    在没有操作系统的裸机系统中,我们编写驱动程序,直接对物理地址、各种寄存器等进行操作并为应用层直接提供接口;如果在有操作系统的平台上,我们又应该如何编写驱动程序?以前在裸机上实现的驱动程序又如何能移植到带操作系统的平台上呢?
    我们学习linux的驱动编程,可以接触linux这么优秀的开源内核并了解他的驱动架构等各种机制。我们可以从中学习借鉴,来提高我们的编程能力以及编写程序时需要考虑的各种方面,如设备、驱动等各种函数的注册机制(各种回调)、软硬资源分离、资源保护等等。
    在编写linux的字符驱动程序前,我们需要知道linux下驱动程序的架构,驱动程序的实现机制。
    linux驱动模块,是一种可动态或静态加入内核的内核模块,内核模块的编程与编译需要遵循一定的规则。假设我们有一定的基础,已经清楚内核模块的架构,那么我们简要分析字符驱动的程序架构。
     1:模块加载函数:mini2440_led_init、module_init(mini2440_led_init);
     2:模块卸载函数:mini2440_led_exit、module_exit(mini2440_led_exit);
     3:实现相关的操作(file_operations):mini2440_led_open、mini2440_led_write mini2440_led_read
     4:相关数据结构与在内核中使用的相关函数:cdev、dev_t、file_operations、cdev_add、device_create
 
    对字符设备的访问是通过文件系统内的设备名称(open(xxx文件))进行的,即将字符设备已文件的形式访问与管理,这些文件是特殊文件、设备文件或称为文件系统树的节点,他们通常位于/dev下。ls -l 可以查看设备文件相关信息。 
    设备文件创建:
    字符设备的设备文件可以通过mknod xxx c 250(主) 0,手动创建,也可以在模块加载函数中通过device_create自动创建;
    inode结构:
    在linux 内核中,使用inode结构表示文件,即每个文件创建后内核中都会有一个inode结构与之关联,
    file结构:
    在linux中每个打开的文件在内核中都有一个file结构与之关联(所有文件都这样);文件与file结构可以使一对一或一对多的关系,而文件与inode是一对一的关系。内核在open文件时创建对应的file结构,并传递给在该文件上进行操作的所有函数,直到最后的close函数。
    字符设备的注册
    字符设备需要注册到内核中,才能被使用。内核中,使用struct cdev结构体表示一个字符设备;cdev结构包含设备的设备号以及该设备的操作方法file_operations等。而在设备文件的inode结构体中又包含指向cdev的指针。
/////////////////////////////////////////////////////////////////////////////////////////////////
    应用程序需要使用某个字符设备,因为linux是通过文件的形式管理设备文件,所以在调用应用程序前需要先创建设备文件,且应用程序要使用驱动字符设备,就必须要用到驱动函数,所以在应用程序使用之前必须先注册好该字符设备的驱动程序。
   字符设备的设备文件可以通过mknod手动创建,也可以在模块加载函数使用device_create自动创建;自动创建利于模块跨平台的使用。设备文件被创建后,在内核中会有一个inode结构与之对应,inode结构包含dev_t(设备号)以及file_operations结构体等。字符驱动程序注册时会将一个绑定了正确的设备号(该设备号要与被创建的设备文件对应)与各种操作的file_operations结构体的cdev结构(内核中使用cdev表示设备)注册到系统内核中。
    当应用程序中通过open(“xxx”)系统调用打开设备文件(调用应用程序之前)时,在内核中同时会创建一个与之对应的file结构体,且通过设备号file结构中的file_operations结构体与内核中cdev结构中的file_operations结构体对应。这样应用层的各种系统调用就被映射到内核中的cdev结构中file_operations中的函数一一对应起来,cdev结构中file_operations各个函数在驱动编写时实现。

    编写思路: 字符驱动模块安装内核模块的程序架构写,即有加载卸载函数以及模块声明。加载函数中又有以下介个方面:
     1:向系统申请一个设备号(或自己通过全局变量指定一个可用的设备,但是仍然要通过向系统申请这个设备号)-----在向系统注册cdev时需要使用设备号;
     2:定义一个file_operations实体----后面cdev需要将他绑定它
     3:申请cdev实例(可动态申请与静态定义一块),并初始化cdev(同时绑定file_operations)
     4:注册cdev到内核中去
     5:可以手动在/dev创建设备文件或者在模块加载函数中自动生成
     6:加载模块
     7:应用程序通过系统调用打开设备文件,这时系统调用的打开读写等函数 将直接调用cdev中绑定的file_operation实例中的函数。
////////////////////////////////////////////////////////////////////////////////////////////////
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/cdev.h>
 
#define GLOBAL_LED_MAJOR  250
 
static unsigned int global_led_major = GLOBAL_LED_MAJOR;主设备号
static struct cdev *led_cdev = NULL;                    指向cdev结构体
static struct class *led_class = NULL;                  为下面自动生成设备文件做准备
 
static volatile unsigned long *gpfcon = NULL;
static volatile unsigned long *gpfdat = NULL; 
static volatile unsigned long *gpfup = NULL; 
 
 
static int mini2440_led_open(struct inode * inode,struct file * file)
{
printk("mini2440_open[kernel_space]\n");
*gpfcon &=~((0x3<<0) | (0x3<<8) |(0x3<<10) |(0x3<<12)|(0x3<<14));
*gpfcon |= (0x1<<0) | (0x1<<8) |(0x1<<10) |(0x1<<12)|(0x1<<14);
return 0;
}
 
static ssize_t mini2440_led_read(struct file * file,const char __user * in,size_t size,loff_t * off)
{
printk("mini2440_read[kernel_space]\n");
return 0;
}
 
static ssize_t mini2440_led_write(struct file * file,const char __user * in,size_t size,loff_t * off)
{
    int ret;
char ker_buf;
printk("mini2440_write[kernel_space]\n");
ret = copy_from_user(&ker_buf,in,size);
printk("ker_buf =%d\n",ker_buf);
if(ker_buf)
{
*gpfdat &=~((0x1<<4)|(0x1<<5)|(0x1<<6)|(0x1<<7));
       
*gpfdat |= (0x1<<0);
}
else
{
*gpfdat |=(0x1<<4)|(0x1<<5)|(0x1<<6)|(0x1<<7);
*gpfdat &= ~(0x1<<0);
}
return 0;
 
}
 
struct file_operations led_fops = {
.owner = THIS_MODULE,
.open  = mini2440_led_open,
.read  = mini2440_led_read,
.write = mini2440_led_write,
};
 
int mini2440_led_init(void)
{
int result;
int err;
    dev_t devno = MKDEV(global_led_major,0);通过全局变量global_led_major合成设备号
if (global_led_major) {
result = register_chrdev_region(devno,1,"led_driver");注册使用人为指定的设备号,可以从cat /proc/devices 看到"led_driver"
printk("sd!");
} else {
result = alloc_chrdev_region(&devno,0,1,"led_driver");由系统分配指定设备号 存放在devno参数中
global_led_major = MAJOR(devno);提取主设备号
printk("zd!");
}
if (result < 0){
        return result;
}
led_cdev = cdev_alloc();动态分配得到一个cdev
cdev_init(led_cdev,&led_fops);初始化cdev 将得到的cdev与具体操作绑定在一起
led_cdev->owner = THIS_MODULE;
err = cdev_add(led_cdev,devno,1);将使用devno设备号的绑定了led_fops的cdev注册到内核中
led_class = class_create(THIS_MODULE,"led_class");先生存一个class类,再生成设备文件
device_create(led_class,NULL,MKDEV(global_led_major,0),NULL,"mini2440_led"); 在/dev下生存设备文件
物理地址空间映射到虚拟地址空间 这里将从物理地址0x56000010开始的12字节的物理空间映射到虚拟地址空间
        返回的是虚拟地址空间对应的起始地址,以后对该片虚拟地址空间的数据操作将映射到物理地址空间
gpfcon = ioremap(0X56000010,12);
gpfdat = gpfcon + 1;
gpfup  = gpfcon + 2;
if (err) {
printk(KERN_NOTICE"Error %d adding led_cdev",err);
return -1;
} else {
printk("mini2440_led_init ok!\n");
return 0;
}
}
 
void mini2440_led_exit(void)
{
cdev_del(led_cdev);从内核中注销cdev结构体
iounmap(gpfcon);注销物理地址空间与虚拟地址空间的映射
kfree(led_cdev);释放动态分配到的led_cdev
unregister_chrdev_region(MKDEV(global_led_major,0),1);注销使用过的设备号
device_destroy(led_class, MKDEV(global_led_major,0));
class_destroy(led_class);
        printk("mini2440_led_exit!\n");
}
 
MODULE_AUTHOR("aaaa");
MODULE_LICENSE("GPL");
module_param(global_led_major,int,S_IRUGO);
module_init(mini2440_led_init);
module_exit(mini2440_led_exit);
 
//////////////////////////////////////////////
ifneq ($(KERNELRELEASE), )
obj-m  := mini2440_led.o
else 
KDIR := /home/tools/linux-2.6.32.2 
all:
make -C $(KDIR)  M=/linux_prg  modules
clean:
rm -f *.ko  *.o  *.mod.o  *.mod.c  *.symvers 
endif
////////////////////////////////////////////////
清除:make clean
编译【自动寻找Makefile文件】:make
////////////////////////////////////////////////
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
int main(int argc,char **argv)
{
int fd;
char val;
 
fd =open("/dev/mini2440_led",O_RDWR);
if(fd<0)
{
printf("cannot open /dev/led!\n");
return 0;
}
printf("open /dev/mini2440_led[usr_space]!\n");
if(strcmp(argv[1],"on")==0)
{
val =1;
}
else
{
val =0;
}
write(fd,&val,1);
printf("finish!\n");
return 0;
}
////////////////////////////////////////////////
arm-linux-gcc mini2440_app.c -o mini2440_led_app
////////////////////////////////////////////////
点亮以及蜂鸣器响  ./mini2440_led_app on
不亮以及蜂鸣器不响 ./mini2440_led_app off
////////////////////////////////////////////////
模块加载函数的流程:(卸载函数与之相反)
1:向系统申请设备号(或向系统注册自己设定的设备号)
2:向系统申请一块cdev结构体
3:初始化cdev:cdev_init(led_cdev,&led_fops);绑定操作函数
关闭窗口

相关文章