專注電子技術(shù)學(xué)習(xí)與研究
當(dāng)前位置:單片機(jī)教程網(wǎng) >> MCU設(shè)計(jì)實(shí)例 >> 瀏覽文章

字符驅(qū)動(dòng)編寫(xiě)小結(jié)(基于mini2440,LED驅(qū)動(dòng))

作者:佚名   來(lái)源:本站原創(chuàng)   點(diǎn)擊數(shù):  更新時(shí)間:2014年08月18日   【字體:

    編程:需要什么功能(機(jī)制)、如何使用這些功能(策略)

    作為驅(qū)動(dòng)程序編寫(xiě)者,我們需要在所需的編程時(shí)間以及驅(qū)動(dòng)程序的靈活性之間選擇一個(gè)可接受的折中。讀者可能奇怪于說(shuō)驅(qū)動(dòng)程序“靈活”,我們用這個(gè)詞實(shí)際上是強(qiáng)調(diào)設(shè)備驅(qū)動(dòng)程序的作用在于提供機(jī)制,而不是提供策略。
    機(jī)制mechanism,策略policy。如果你看過(guò)《linux device drivers》,里面給出了大概的介紹。機(jī)制提供了干什么(do what),策略提供如何做(how to do)。驅(qū)動(dòng)程序完成機(jī)制的功能,把策略的實(shí)現(xiàn)留給用戶的應(yīng)用程序。通常在機(jī)制中,驅(qū)動(dòng)程序要完成打開(kāi),關(guān)閉,讀寫(xiě),控制等功能。這些都是設(shè)備使用時(shí)最基本的操作。而策略中就要實(shí)現(xiàn)一些高級(jí)的數(shù)據(jù)處理或界面功能。通過(guò)例子來(lái)說(shuō)明會(huì)更好些。

    linux中設(shè)備通常分為三類:字符設(shè)備、塊設(shè)備、網(wǎng)絡(luò)接口;

    編寫(xiě)訪問(wèn)硬件的內(nèi)核代碼時(shí),不要給用戶強(qiáng)加任何特定策略,驅(qū)動(dòng)程序應(yīng)該處理如何使硬件可用的問(wèn)題,而將怎樣使用硬件的問(wèn)題留給上層應(yīng)用程序;
    不帶策略的驅(qū)動(dòng)程序包括一些典型的特性:同時(shí)支持同步和異步操作、驅(qū)動(dòng)程序能夠被多次打開(kāi)、充分利用硬件特性,以及不具備用來(lái)“簡(jiǎn)化任務(wù)”的或提供與策略相關(guān)的軟件層等。
    不帶策略的軟件設(shè)計(jì)是軟件設(shè)計(jì)者的一個(gè)共同目標(biāo)。
 
    在沒(méi)有操作系統(tǒng)的裸機(jī)系統(tǒng)中,我們編寫(xiě)驅(qū)動(dòng)程序,直接對(duì)物理地址、各種寄存器等進(jìn)行操作并為應(yīng)用層直接提供接口;如果在有操作系統(tǒng)的平臺(tái)上,我們又應(yīng)該如何編寫(xiě)驅(qū)動(dòng)程序?以前在裸機(jī)上實(shí)現(xiàn)的驅(qū)動(dòng)程序又如何能移植到帶操作系統(tǒng)的平臺(tái)上呢?
    我們學(xué)習(xí)linux的驅(qū)動(dòng)編程,可以接觸linux這么優(yōu)秀的開(kāi)源內(nèi)核并了解他的驅(qū)動(dòng)架構(gòu)等各種機(jī)制。我們可以從中學(xué)習(xí)借鑒,來(lái)提高我們的編程能力以及編寫(xiě)程序時(shí)需要考慮的各種方面,如設(shè)備、驅(qū)動(dòng)等各種函數(shù)的注冊(cè)機(jī)制(各種回調(diào))、軟硬資源分離、資源保護(hù)等等。
    在編寫(xiě)linux的字符驅(qū)動(dòng)程序前,我們需要知道linux下驅(qū)動(dòng)程序的架構(gòu),驅(qū)動(dòng)程序的實(shí)現(xiàn)機(jī)制。
    linux驅(qū)動(dòng)模塊,是一種可動(dòng)態(tài)或靜態(tài)加入內(nèi)核的內(nèi)核模塊,內(nèi)核模塊的編程與編譯需要遵循一定的規(guī)則。假設(shè)我們有一定的基礎(chǔ),已經(jīng)清楚內(nèi)核模塊的架構(gòu),那么我們簡(jiǎn)要分析字符驅(qū)動(dòng)的程序架構(gòu)。
     1:模塊加載函數(shù):mini2440_led_init、module_init(mini2440_led_init);
     2:模塊卸載函數(shù):mini2440_led_exit、module_exit(mini2440_led_exit);
     3:實(shí)現(xiàn)相關(guān)的操作(file_operations):mini2440_led_open、mini2440_led_write mini2440_led_read
     4:相關(guān)數(shù)據(jù)結(jié)構(gòu)與在內(nèi)核中使用的相關(guān)函數(shù):cdev、dev_t、file_operations、cdev_add、device_create
 
    對(duì)字符設(shè)備的訪問(wèn)是通過(guò)文件系統(tǒng)內(nèi)的設(shè)備名稱(open(xxx文件))進(jìn)行的,即將字符設(shè)備已文件的形式訪問(wèn)與管理,這些文件是特殊文件、設(shè)備文件或稱為文件系統(tǒng)樹(shù)的節(jié)點(diǎn),他們通常位于/dev下。ls -l 可以查看設(shè)備文件相關(guān)信息。 
    設(shè)備文件創(chuàng)建:
    字符設(shè)備的設(shè)備文件可以通過(guò)mknod xxx c 250(主) 0,手動(dòng)創(chuàng)建,也可以在模塊加載函數(shù)中通過(guò)device_create自動(dòng)創(chuàng)建;
    inode結(jié)構(gòu):
    在linux 內(nèi)核中,使用inode結(jié)構(gòu)表示文件,即每個(gè)文件創(chuàng)建后內(nèi)核中都會(huì)有一個(gè)inode結(jié)構(gòu)與之關(guān)聯(lián),
    file結(jié)構(gòu):
    在linux中每個(gè)打開(kāi)的文件在內(nèi)核中都有一個(gè)file結(jié)構(gòu)與之關(guān)聯(lián)(所有文件都這樣);文件與file結(jié)構(gòu)可以使一對(duì)一或一對(duì)多的關(guān)系,而文件與inode是一對(duì)一的關(guān)系。內(nèi)核在open文件時(shí)創(chuàng)建對(duì)應(yīng)的file結(jié)構(gòu),并傳遞給在該文件上進(jìn)行操作的所有函數(shù),直到最后的close函數(shù)。
    字符設(shè)備的注冊(cè)
    字符設(shè)備需要注冊(cè)到內(nèi)核中,才能被使用。內(nèi)核中,使用struct cdev結(jié)構(gòu)體表示一個(gè)字符設(shè)備;cdev結(jié)構(gòu)包含設(shè)備的設(shè)備號(hào)以及該設(shè)備的操作方法file_operations等。而在設(shè)備文件的inode結(jié)構(gòu)體中又包含指向cdev的指針。
/////////////////////////////////////////////////////////////////////////////////////////////////
    應(yīng)用程序需要使用某個(gè)字符設(shè)備,因?yàn)閘inux是通過(guò)文件的形式管理設(shè)備文件,所以在調(diào)用應(yīng)用程序前需要先創(chuàng)建設(shè)備文件,且應(yīng)用程序要使用驅(qū)動(dòng)字符設(shè)備,就必須要用到驅(qū)動(dòng)函數(shù),所以在應(yīng)用程序使用之前必須先注冊(cè)好該字符設(shè)備的驅(qū)動(dòng)程序。
   字符設(shè)備的設(shè)備文件可以通過(guò)mknod手動(dòng)創(chuàng)建,也可以在模塊加載函數(shù)使用device_create自動(dòng)創(chuàng)建;自動(dòng)創(chuàng)建利于模塊跨平臺(tái)的使用。設(shè)備文件被創(chuàng)建后,在內(nèi)核中會(huì)有一個(gè)inode結(jié)構(gòu)與之對(duì)應(yīng),inode結(jié)構(gòu)包含dev_t(設(shè)備號(hào))以及file_operations結(jié)構(gòu)體等。字符驅(qū)動(dòng)程序注冊(cè)時(shí)會(huì)將一個(gè)綁定了正確的設(shè)備號(hào)(該設(shè)備號(hào)要與被創(chuàng)建的設(shè)備文件對(duì)應(yīng))與各種操作的file_operations結(jié)構(gòu)體的cdev結(jié)構(gòu)(內(nèi)核中使用cdev表示設(shè)備)注冊(cè)到系統(tǒng)內(nèi)核中。
    當(dāng)應(yīng)用程序中通過(guò)open(“xxx”)系統(tǒng)調(diào)用打開(kāi)設(shè)備文件(調(diào)用應(yīng)用程序之前)時(shí),在內(nèi)核中同時(shí)會(huì)創(chuàng)建一個(gè)與之對(duì)應(yīng)的file結(jié)構(gòu)體,且通過(guò)設(shè)備號(hào)file結(jié)構(gòu)中的file_operations結(jié)構(gòu)體與內(nèi)核中cdev結(jié)構(gòu)中的file_operations結(jié)構(gòu)體對(duì)應(yīng)。這樣應(yīng)用層的各種系統(tǒng)調(diào)用就被映射到內(nèi)核中的cdev結(jié)構(gòu)中file_operations中的函數(shù)一一對(duì)應(yīng)起來(lái),cdev結(jié)構(gòu)中file_operations各個(gè)函數(shù)在驅(qū)動(dòng)編寫(xiě)時(shí)實(shí)現(xiàn)。

    編寫(xiě)思路: 字符驅(qū)動(dòng)模塊安裝內(nèi)核模塊的程序架構(gòu)寫(xiě),即有加載卸載函數(shù)以及模塊聲明。加載函數(shù)中又有以下介個(gè)方面:
     1:向系統(tǒng)申請(qǐng)一個(gè)設(shè)備號(hào)(或自己通過(guò)全局變量指定一個(gè)可用的設(shè)備,但是仍然要通過(guò)向系統(tǒng)申請(qǐng)這個(gè)設(shè)備號(hào))-----在向系統(tǒng)注冊(cè)cdev時(shí)需要使用設(shè)備號(hào);
     2:定義一個(gè)file_operations實(shí)體----后面cdev需要將他綁定它
     3:申請(qǐng)cdev實(shí)例(可動(dòng)態(tài)申請(qǐng)與靜態(tài)定義一塊),并初始化cdev(同時(shí)綁定file_operations)
     4:注冊(cè)cdev到內(nèi)核中去
     5:可以手動(dòng)在/dev創(chuàng)建設(shè)備文件或者在模塊加載函數(shù)中自動(dòng)生成
     6:加載模塊
     7:應(yīng)用程序通過(guò)系統(tǒng)調(diào)用打開(kāi)設(shè)備文件,這時(shí)系統(tǒng)調(diào)用的打開(kāi)讀寫(xiě)等函數(shù) 將直接調(diào)用cdev中綁定的file_operation實(shí)例中的函數(shù)。
////////////////////////////////////////////////////////////////////////////////////////////////
#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;主設(shè)備號(hào)
static struct cdev *led_cdev = NULL;                    指向cdev結(jié)構(gòu)體
static struct class *led_class = NULL;                  為下面自動(dòng)生成設(shè)備文件做準(zhǔn)備
 
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);通過(guò)全局變量global_led_major合成設(shè)備號(hào)
if (global_led_major) {
result = register_chrdev_region(devno,1,"led_driver");注冊(cè)使用人為指定的設(shè)備號(hào),可以從cat /proc/devices 看到"led_driver"
printk("sd!");
} else {
result = alloc_chrdev_region(&devno,0,1,"led_driver");由系統(tǒng)分配指定設(shè)備號(hào) 存放在devno參數(shù)中
global_led_major = MAJOR(devno);提取主設(shè)備號(hào)
printk("zd!");
}
if (result < 0){
        return result;
}
led_cdev = cdev_alloc();動(dòng)態(tài)分配得到一個(gè)cdev
cdev_init(led_cdev,&led_fops);初始化cdev 將得到的cdev與具體操作綁定在一起
led_cdev->owner = THIS_MODULE;
err = cdev_add(led_cdev,devno,1);將使用devno設(shè)備號(hào)的綁定了led_fops的cdev注冊(cè)到內(nèi)核中
led_class = class_create(THIS_MODULE,"led_class");先生存一個(gè)class類,再生成設(shè)備文件
device_create(led_class,NULL,MKDEV(global_led_major,0),NULL,"mini2440_led"); 在/dev下生存設(shè)備文件
物理地址空間映射到虛擬地址空間 這里將從物理地址0x56000010開(kāi)始的12字節(jié)的物理空間映射到虛擬地址空間
        返回的是虛擬地址空間對(duì)應(yīng)的起始地址,以后對(duì)該片虛擬地址空間的數(shù)據(jù)操作將映射到物理地址空間
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);從內(nèi)核中注銷cdev結(jié)構(gòu)體
iounmap(gpfcon);注銷物理地址空間與虛擬地址空間的映射
kfree(led_cdev);釋放動(dòng)態(tài)分配到的led_cdev
unregister_chrdev_region(MKDEV(global_led_major,0),1);注銷使用過(guò)的設(shè)備號(hào)
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
編譯【自動(dòng)尋找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
////////////////////////////////////////////////
點(diǎn)亮以及蜂鳴器響  ./mini2440_led_app on
不亮以及蜂鳴器不響 ./mini2440_led_app off
////////////////////////////////////////////////
模塊加載函數(shù)的流程:(卸載函數(shù)與之相反)
1:向系統(tǒng)申請(qǐng)?jiān)O(shè)備號(hào)(或向系統(tǒng)注冊(cè)自己設(shè)定的設(shè)備號(hào))
2:向系統(tǒng)申請(qǐng)一塊cdev結(jié)構(gòu)體
3:初始化cdev:cdev_init(led_cdev,&led_fops);綁定操作函數(shù)
關(guān)閉窗口

相關(guān)文章