天天看點

基于Linux作業系統的底層驅動技術

裝置管理即輸入/輸出子系統,可分為上下兩部分:一部分是上層的,與裝置無關,這部分根據輸入/輸出請求,通過特定的裝置驅動程式接口來與裝置進行通信。另一部分是下層的,與裝置有關,常稱為裝置驅動程式,它直接與相應裝置打交道,并且向上層提供一組通路接口。

裝置管理的目标是對所有外接裝置進行良好的讀、寫、控制等操作。由于使用者希望能用同樣的應用程式和指令來通路裝置和普通檔案。為此,Linux中的裝置管理應用了裝置檔案這個概念來統一裝置的通路接口。簡單地說,系統試圖使它對所有各類裝置的輸入、輸出看起來就好像對普通檔案的輸入、輸出一樣。使用者希望能用同樣的應用程式和指令來通路裝置和普通檔案。

由于Linux中将裝置當做檔案來處理,是以對裝置進行操作的系統調用和對檔案的操作類似,主要包括open()、read()、write()、ioctl()、close()等。應用程式發出系統調用指令

以後,會從使用者态轉換到核心态,通過核心将open()這樣的系統調用轉換成對實體裝置的操作。

Linux下的裝置驅動任務包括以下兩個。

(1)自動配置和初始化子程式:這部分程式僅在初始化的時候被調用一次。

(2)服務于I/O請求的子程式:這部分是系統調用的結果。在執行這部分程式的時候,系統仍認為和進行調用的程序屬于同一個程序,隻是由使用者态變成了核心态,并具有進行此系統調用的使用者程式運作環境,是以可以在其中調用sleep()等與程序運作環境有關的函數。

縱覽linux/drivers目錄,大概還有35個以上的子目錄,每個子目錄基本上就代表了一種裝置驅動,有atm、block、char、misc、input、net、usb、sound、video等。這裡隻描述在嵌入式系統裡面用得最為廣泛的3種裝置。

字元裝置是Linux最簡單的裝置,可以像檔案一樣通路。初始化字元裝置時,它的裝置驅動程式向Linux登記,并在字元裝置向量表中增加一個device_struct資料結構條目,這個裝置的主裝置辨別符用做這個向量表的索引。一個裝置的主裝置辨別符是固定的。chrdevs向量表中的每一個條目,一個device_struct資料結構,包括兩個元素:一個登記裝置驅動程式名稱的指針和一個指向一組檔案操作的指針。可以參考的代碼是include/linux/ major.h。

一般來說像滑鼠、序列槽、鍵盤等裝置都屬于字元裝置。

塊裝置是檔案系統的物質基礎,它也可以像檔案一樣被通路。Linux用blkdevs向量表維護已經登記的塊裝置檔案。它像chrdevs向量表一樣,使用裝置的主裝置号作為索引。它的條目也是device_struct資料結構。與字元裝置不同的是,塊裝置分為SCSI類和IDE類。向Linux核心登記并向核心提供檔案操作。一種塊裝置類的裝置驅動程式向這種類提供和類相關的接口。可以參考的代碼是fs/devices.c。

每一個塊裝置驅動程式必須提供普通的檔案操作接口和對于buffer cache的接口。每一個塊裝置驅動程式填充blk_dev向量表中的blk_dev_struct資料結構。此向量表的索引是裝置的主裝置号。其中blk_dev_struct資料結構包括一個請求例程的位址和一個指針,指向一個request資料結構的清單,每一個都表達buffer cache向裝置讀/寫一塊資料的一個請求。

可以參考的源代碼是drivers/block/ll_rw_blk.c和include/linux/blkdev.h。

當buffer cache從一個已登記的裝置讀/寫一塊資料,或者希望讀、寫一塊資料到其他

位置時,就在blk_dev_struct中增加一個request資料結構。每個request資料結構都有一個指向一個或多個buffer_head資料結構的指針,每一個都是讀/寫一塊資料的請求。如果buffer_head資料結構被鎖定(buffer_cache),可能會有一個程序在等待這個緩沖區的阻塞程序完成。每一個request資料結構都是從all_request表中配置設定的。如果request增加到空的request清單中,就調用驅動程式的request函數處理這個request隊列,否則驅動程式隻是簡單地處理request隊列中的每一個請求。

塊裝置驅動程式和字元裝置驅動程式的主要差別是:在對字元裝置發出讀、寫請求時,實際的硬體I/O一般緊接着就發生了,塊裝置則不然,它利用一塊系統記憶體作為緩沖區,當使用者程序對裝置請求能滿足使用者的要求時,就傳回請求的資料,如果不能就調用請求函數來進行實際的I/O操作。塊裝置是主要針對磁盤等慢速裝置的,以免耗費過多的CPU時間來等待。

塊裝置主要有硬碟、CD光牒驅動器等。可以檢視檔案/proc/devices獲得。

網絡裝置在系統中的作用類似于一個已挂載的塊裝置。塊裝置将自己注冊到blk_dev資料及其他核心結構中,然後通過自己的request函數在發生請求時傳輸和接收資料塊,同樣網絡裝置也必須在特定的資料結構中注冊自己,以便與外界交換資料包時被調用。網絡裝置在Linux裡做專門的處理。Linux的網絡系統主要是基于BSD UNIX的Socket機制。在系統和驅動程式之間定義有專門的資料結構(sk_buff)進行資料的傳遞。系統裡支援對發送資料和接收資料的緩存,提供流量控制機制,提供對多協定的支援。

雜項裝置也是在嵌入式系統中用得比較多的一種裝置驅動,在第11章裡面介紹的sub LCD和弦晶片的驅動等都是采用 misc device 的驅動方式實作的。在 Linux 核心的include\linux目錄下有Miscdevice.h檔案,要把自己定義的misc device從裝置定義在這裡。其實是因為這些字元裝置不符合預先确定的字元裝置範疇,所有這些裝置采用主編号10,一起歸于misc device,其實misc_register就是用主标号10調用register_chrdev()的。

核心内部通過file結構識别裝置,通過file_operations資料結構提供檔案系統的入口點

函數。file_operations定義在<linux/fs.h>中的函數指針表。這個結構的每一個成員的名字都對應着一個系統調用。從某種意義上說,寫驅動程式的任務之一就是完成file_operations

中的函數指針。如果在2.4版本核心下開發的驅動很可能在2.6版本中無法使用,需要進行移植。通常file_operations提供了包括open()、write()、read()、release()、poll()、ioctl()等檔案系統的入口函數。

下面簡單描述一下幾個重要的入口函數。

1)open():

static int mydriver_open(struct inode *inode, struct file *filp)

當上層對mydriver執行open操作時調用該函數,其中參數inode為裝置特殊檔案的inode(索引節點)結構指針,參數file是指向這一裝置的檔案結構指針。open()的主要任務是确定硬體處在就緒狀态、驗證次裝置号的合法性(次裝置号可以用MINOR(inode→i - rdev)取得)、控制使用裝置的程序數、根據執行情況傳回狀态碼(0表示成功,負數表示存在錯誤)等。

2)write():

static ssize_t mydriver_write(struct file *filp, const char *buf, size_t size, loff_t *offp)

當裝置特殊檔案進行write系統調用時,将調用驅動程式的write()函數,向裝置發送資料。如果沒有這個函數,write 系統調用會向調用程式傳回一個-EINVAL。如果傳回值非負,則表示成功寫入的位元組數。Write函數通常就是把資料從使用者空間複制到核心空間,是以在write函數裡經常會看到copy_from_user()函數。

3)read():

static ssize_t mydriver_read(struct file *filp, char *buf, size_t size, loff_t *offp)

當對裝置特殊檔案進行read系統調用時,将調用驅動程式read()函數,用來從裝置中讀取資料。當該函數指針被賦為NULL 值時,将導緻read 系統調用出錯并傳回-EINVAL(“Invalid argument,非法參數”)。函數傳回非負值表示成功讀取的位元組數(傳回值為“signed size”資料類型,通常就是目标平台上的固有整數類型)。Read()函數則通常是把資料從核心空間複制到使用者空間,一般都會調用copy_to_user()函數。

4)release():

static int mydriver_release(struct inode *inode, struct file *filp)

當最後一個打開裝置的使用者程序執行close()系統調用時,核心将調用驅動程式的release()函數,release()函數的主要任務是清理未結束的輸入/輸出操作、釋放資源、使用者自定義其他标志的複位等。

5)ioctl():

static int mydriver_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

該函數是特殊的控制函數,可以通過它向裝置傳遞控制資訊或從裝置取得狀态資訊,unsigned int參數為裝置驅動程式需要執行的指令代碼,由使用者自定義。unsigned long參數為相應的指令提供參數,類型可以是整型、指針等。如果裝置不提供ioctl入口點,則對任何核心未預先定義的請求,ioctl系統調用将傳回錯誤(-ENOTTY,“No such ioctl fordevice,該裝置無此ioctl指令”)。如果該裝置方法傳回一個非負值,那麼該值會被傳回給調用程式以表示調用成功。

6)poll():

static unsigned int mydriver_poll(struct file *filp, poll_table *wait)

poll方法是poll和select 這兩個系統調用的後端實作,用來查詢裝置是否可讀、可寫或是否處于某種特殊狀态。

檔案系統處理的檔案所需要的資訊在inode(索引節點)中。一個filesystem可以粗略地分成inode table與data area兩部分。Inode table上有許多的inode,每個inode分别記錄一個檔案的屬性,以及這個檔案分布在哪些data block上。inode包含檔案通路權限、屬主、組、大小、生成時間、通路時間、最後修改時間等資訊。它是Linux管理檔案系統的最基本機關,也是檔案系統連接配接任何子目錄、檔案的橋梁。inode結構中的靜态資訊取自實體裝置上的檔案系統,由檔案系統指定的函數填寫,它隻存在于記憶體中,可以通過inode緩存通路。雖然每個檔案都有相應的inode節點,但是隻有在需要的時候,系統才會在記憶體中為其建立相應的inode資料結構,建立的inode結構将形成一個連結清單,可以通過周遊這個連結清單得到所需要的檔案節點。

file結構主要用于與檔案系統對應的裝置驅動程式使用。在Linux裡,每一個檔案都有一個file結構和inode結構,inode結構是用來讓Kernel做管理的,而file結構則是平常對檔案讀、寫或開啟,關閉所使用的。當然,從user的觀點來看是看不出什麼的。比起inode結構,file結構就顯得小多了,file結構也是用串行來管理的,f_next會指到下一個file結構,而f_pprev則會指到上一個file結構的位址,f_dentry會記錄其inode的dentry位址,f_mode為檔案存取種類,f_pos則是目前檔案的offset,每次讀寫都從offset記錄的位置開始讀寫,f_count是此file結構的reference cout,f_flags則是開啟此檔案的模式,f_reada,f_ramax, f_raend,f_ralen,f_rawin則是控制read ahead的參數,f_owner記錄了要接收SIGIO和SIGURG的行程ID或行程群組ID,private_data則是tty driver所使用的字段。

Linux下的驅動程式雖然複雜,但是總結下來還是有很多的規律可尋。Linux下的裝置驅動開始程式設計時顯得比較容易,可以輕松地開始驅動編寫,但是要把驅動寫好也的确需要花一定的時間去研究。

裝置驅動模闆代碼如例程5-4所示。

例程5‑4  Mydriver.c

#include <linux/module.h>

#include <linux/config.h>

#include <linux/types.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/delay.h>

#include <linux/miscdevice.h>

#include <linux/ioctl.h>

#include <linux/interrupt.h>

#include <linux/spinlock.h>

#include <linux/smp_lock.h>

#include <linux/poll.h>

#include <linux/sched.h>

#include <linux/ioport.h>

#include <linux/slab.h>

#include <asm/hardware.h>

#include <asm/io.h>

#include <asm/arch/irqs.h>

#include <asm/irq.h>

#include <asm/signal.h>

#include <asm/uaccess.h>

/*定義裝置的從裝置号*/

#define MYDRIVER_MINOR  174

/*定義裝置相關資料結構*/

typedef struct _MYDRIVER_DEV

{

   spinlock_t            dev_lock;

   wait_queue_head_t     oWait;     

   int                  open_count;

}MYDRIVER_DEV, *PMYDRIVER_DEV;

/*定義裝置狀态資料結構*/

typedef struct _MYDRIVER_DEV_STATS

    unsigned long         rx_intrs;

    unsigned long         rx_errors;

    unsigned long         rx_blocks;

    unsigned long         rx_dropped;

    unsigned long         tx_intrs;

    unsigned long         tx_errors;

    unsigned long         tx_missed;

    unsigned long         tx_blocks;

    unsigned long         tx_dropped;

}MYDRIVER_DEV_STATS, * MYDRIVER_DEV_STATS;

unsigned int IntInit=0;

/*定義裝置open接口函數*/

static int mydriver_open(struct inode *inode, struct file * filp)

    int  minor;

    DBGPRINT("mydriver_open\n");

    minor = MINOR(inode->i_rdev);

    if ( minor != MYDRIVER_MINOR )  { return -ENODEV; }

#ifdef MODULE

    MOD_INC_USE_COUNT;    /*打開使用次數累加*/

#endif

mydriver_dev.open_count++;

if ( mydriver_dev.open_count == 1 )

    {

        DBGPRINT("mydriver_open: first opne\n");

        /*第一次打開裝置,在這裡可以放些裝置初始化代碼*/

    }

    return 0;

}

/*定義裝置close接口函數*/

    DBGPRINT("mydriver_release\n");

    mydriver_dev.open_count--;

    if ( mydriver_dev.open_count == 0 )

        DBGPRINT("mydriver_release: last close\n");

        /*裝置徹底關閉,這裡可以放一些使裝置休眠,或者poweroff的代碼*/

    MOD_DEC_USE_COUNT;    /*打開次數遞減*/

/*定義裝置read接口函數*/

static ssize_t mydriver_read(struct file *filp, char *buf, size_t size,                        loff_t *offp)

    if(size> 8192) size = 8192;

/* copy_to_user()*/

/*copy kernel space to user space. */

                    /*把資料從核心複制到使用者空間的代碼,可以根據實際添加*/

    return size;                      /*傳回位元組數*/

/*定義裝置write接口函數*/

static ssize_t mydriver_write(struct file *filp, const char *buf, size_t                        size, loff_t *offp)

    lock_kernel();

    DBGPRINT("mydriver_write\n");

    if(size> 8192)  size = 8192;

/*copy_from_user()*/

/*copy user space to kernel space. */   /*把資料從使用者空間複制到核心空間*/

    unlock_kernel();        

/*定義裝置ioctl接口函數*/

static int mydriver_ioctl(struct inode *inode, struct file *filp, unsigned                     int cmd, unsigned long arg)

    int  ret = 0;

    DBGPRINT("mydriver_ioctl: cmd: 0x%x\n", cmd);

    switch(cmd)

        case cmd1:      /*指令字,注意幻數的使用*/

             …..

            break;

        case cmd3:

       default:

            DBGPRINT("mydriver_ioctl: bad ioctl cmd\n");

            ret = -EINVAL;

    }

    return ret;

/*定義裝置select函數接口*/

static unsigned int mydriver_poll(struct file *filp, poll_table *wait)

poll_wait(filp,&mydriver_dev.oWait,wait);

        if(IntInit)

    { 

           IntInit=0;

           return POLLIN|POLLRDNORM;  //可以寫

        }

        else {  return POLLOUT;      //可以讀 }

/*定義裝置的file_operations*/

static struct file_operations  mydriver_fops =

    owner:      THIS_MODULE,

    open:       mydriver_open,

    release:     mydriver_release,

    read:       mydriver_read,

    write:      mydriver_write,

    ioctl:       mydriver_ioctl,

    poll:       mydriver_poll,

};

/*定義裝置結構體*/

static struct miscdevice  mydriver_miscdev =

    MYDRIVER_MINOR,

    " mydriver ",

    & mydriver_fops

/*定義裝置init函數*/

int __init mydriver_init(void)

    int  ret;

    DBGPRINT("mydriver_init\n");

    ret =misc_register(&mydriver_miscdev); //注意這裡調用misc_register()來注冊

    if ( ret )

       DBGPRINT("misc_register failed: 0x%x\n", ret);

        return ret;

    memset(&mydriver_dev, 0, sizeof(mydriver_dev));

    init_waitqueue_head(&mydriver_dev.oWait);

    spin_lock_init(&mydriver_dev->dev_lock);

    /*這裡可以放一些硬體初始化的函數*/   

/*定義裝置exit函數*/

void __exit mydriver_exit(void)

    DBGPRINT("mydriver_exit\n");

    misc_deregister(&mydriver_miscdev);  //登出misc dev

module_init(mydriver_init);

module_exit(mydriver_exit);

MODULE_LICENSE("GPL");

從上面的模闆代碼可以看出,裝置驅動主要給上層提供file_operation和ioctl功能,實作上層對于底層裝置的管理和讀、寫操作等。另外不同的裝置調用的裝置注冊和登出函數有所不同,大家可以區分一下:misc_register()函數、register_chardev()函數、register_netdev()函數及misc_deregister()函數。也可以去分析一下deregister_chardev()函數和deregister_netdev() 函數的不同之處。

通常的裝置驅動參照上面的模闆就可以實作基本的架構了,當然還需要注意有關硬體的一些操作,包括初始化、參數設定、中斷服務等。這些代碼可以根據系統的設計放在driver_init裡面,或者放在第一次打開的時候。

在裝置驅動程式中通用的申請中斷的方法如下:

request_irq(INT_DEVICE, device_intr, SA_INTERRUPT, "device_INT", &my- driver_dev)

n  INT_DEVICE:對應的硬體中斷号;

n  device_intr:中斷服務回調函數名;

n  SA_INTERRUPT:申請中斷的方式,表明這是一個快速中斷;

n  device_INT:中斷的名字;

n  mydriver_dev:申請中斷的裝置。

INT_DEVICE可以定義在include/asm-arm/arch目錄下的irqs.h檔案裡面。另外中斷的名字device_INT在什麼地方可以看到呢?不妨可以在嵌入式系統啟動之後,cat /proc/interrupts看看列印出來的中斷資訊清單裡有沒有定義的device_INT這個名字。

request_irq()函數的實體如下:

int request_irq(unsigned int irq, void (*handler)(int, void *, struct                       pt_regs *),

            unsigned long irq_flags, const char * devname, void *dev_id)

unsigned long retval;

struct irqaction *action;

if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||

    (irq_flags & SA_SHIRQ && !dev_id))

           return -EINVAL;

action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_                                     KERNEL);

if (!action)

           return -ENOMEM;

action->handler = handler;

action->flags = irq_flags;

action->mask = 0;

action->name = devname;

action->next = NULL;

action->dev_id = dev_id;

retval = setup_arm_irq(irq, action);

if (retval)

           kfree(action);

return retval;

從上面request_irq的原形函數,可以看出其内部其實是調用了setup_arm_irq()函數在系統中注冊中斷的。當然,如果在module_init函數或者裝置對應的open()函數裡面申請了中斷,那麼相應的就應該在module_exit函數或者module_release函數裡面調用free_irq()函數來登出中斷服務,方法是:

        free_irq(INT_DEVICE, &mydriver_dev);

另外,在進行中斷的時候盡量用一些核心提供的像cli()、sti()這樣的函數。

對于底層裝置有的時候需要改變裝置的運作狀況,有時候需要改變裝置的運作參數等。為完成這些參數的設定,上層隻要傳遞少量的指令字或參數給底層裝置。對于這樣的應用,底層驅動通常是通過給上層提供ioctl函數來實作的。下面先給出一個簡單的ioctl例子:

#define MYDRIVER_IOC_MAGIC  'm'   /*定義幻數*/

#define MYDRIVER_IOC_BASE   0

/*定義指令字*/

#define MYDRIVER_IOC_ON

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 1)

#define MYDRIVER_IOC_OFF

 _IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 2)

#defineMYDRIVER_IOC_SLEEP_IN

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 3)

#define MYDRIVER_IOC_SLEEP_OUT  

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 4)

#define MYDRIVER_IOC_RESET  

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 5)

#define MYDRIVER_IOC_CLEAR_SCREEN

_IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 6)

#define MYDRIVER_IOC_CONTRAST_INC

                          _IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 7)

#define MYDRIVER_IOC_CONTRAST_DEC

                          _IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 8)

#define MYDRIVER_IOC_INIT 

                          _IO(MYDRIVER_IOC_MAGIC, MYDRIVER_IOC_BASE + 9)

static int mydriver_ioctl(struct inode *inode, struct file *filp, unsigned

intcmd, unsigned long arg)

    /*根據指令執行對應的操作*/

        case MYDRIVER_IOC_RESET:

            mydriver_reset();

        case MYDRIVER_IOC_ON:

            mydriver_on();

        case MYDRIVER_IOC_OFF:

            mydriver_off();

        case MYDRIVER_IOC_SLEEP_IN:

            mydriver_sleep_in();

        case MYDRIVER_IOC_SLEEP_OUT:

            mydriver_sleep_out();

        case MYDRIVER_IOC_CLEAR_SCREEN:

            mydriver_clear_screen();

            break;

        case MYDRIVER_IOC_CONTRAST_DEC:

            mydriver_contrast_dec();

        case MYDRIVER_IOC_CONTRAST_INC:

            mydriver_contrast_inc();

        case MYDRIVER_IOC_INIT:

            mydriver_initial();

            break;               

特殊的系統函數調用ioctl(input output control)。一般情況下每個裝置可以有自己的ioctl指令,它可以讀ioctl(從程序向核心發送資訊)和寫ioctl(傳回資訊給程序)(注意一下:在此讀、寫的作用是颠倒的,是以ioctl的讀是發送消息給核心而寫是從核心接收消息)或什麼也不做。ioctl使用3個參數調用:合适的裝置檔案的檔案描述符,ioctl号及一個參數,該參數是類型長度,是以可以使用一個模型傳遞一些參數。

ioctl号用主裝置号,ioctl類型,指令和參數類型編碼。這個ioctl号通常用一個頭檔案中的宏調用(_IO,_IOR,_IOW或_IOWR——取決于類型)建立。如果想在自己的子產品中使用ioctl,最好接受官方的ioctl配置設定。

驅動程式加載(insmod)之後,通過什麼樣的手段來觀測裝置的運作狀況呢?通常可以在file_operation對應的各個函數裡面用printk(核心态常用的列印函數)列印出需要了解的調試資訊。如果仔細留意的話可以發現在嵌入式系統的檔案系統目錄下通常會有proc目錄,在該目錄下可以通過cat interrupt去了解ARM嵌入式系統中ARM處理器中斷的情況,通過cat devices可以了解device裝置的狀況。那麼裝置的運作狀态是不是也可以通過

proc來了解呢?答案是肯定的。因為proc也是一種Linux下用得比較多的使用者态與核心态資料交換的方式,核心的很多資料都是通過這種方式給使用者的,核心的很多參數也可以通過這種方式來讓使用者友善設定的。除了sysctl出口到/proc下的參數,proc提供的大部分核心參數是隻讀的。實際上,很多應用嚴重地依賴于proc,是以它幾乎是必不可少的元件。那麼如何來使用proc呢?首先一定要包含 procfs的頭檔案,如#include <linux/proc _fs.h>,其次利用procfs提供的如下API函數。

1)struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent)

該函數用于建立一個正常的proc條目,參數name給出了要建立proc條目的名稱,參數mode給出了建立的該proc條目的通路權限,參數parent指定建立的proc條目所在的目錄。如果要在/proc下建立proc條目,parent應當為NULL,否則它應當為proc_mkdir。傳回struct proc_dir_entry結構的指針。

2)extern void remove_proc_entry(const char *name, struct proc_dir_entry *parent)

該函數用于删除上面函數建立的proc條目,參數name給出要删除的proc條目的名稱,參數parent指定建立的proc條目所在的目錄。

3)struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir _entry *parent)

該函數用于建立一個proc目錄,參數name指定要建立的proc目錄的名稱,參數parent為該proc目錄所在的目錄。

4)extern struct proc_dir_entry *proc_mkdir_mode(const char *name, mode _t mode, struct- proc_dir_entry *parent)

該函數用于以一定的模式建立proc目錄,參數name指定要建立的proc目錄的名稱,參數mode給出了建立的該proc目錄的通路權限,參數parent為該proc目錄所在的目錄。

5)struct proc_dir_entry *proc_symlink(const char * name,struct proc_ dir_entry * parent, const char * dest)

該函數用于建立一個proc條目的符号連結,參數name給出要建立的符号連結proc條目的名稱,參數parent指定符号連結所在的目錄,參數dest指定連結到的proc條目名稱。

6)struct proc_dir_entry *create_proc_read_entry(const char *name,mode_t mode, struct proc_dir_entry *base,read_proc_t *read_proc, void * data)

該函數用于建立一個規則的隻讀proc條目,參數name給出要建立proc條目的名稱,參數mode給出了建立該proc條目的通路權限,參數base指定建立proc條目所在的目錄,參數read_proc給出讀取該proc條目的操作函數,參數data為該proc條目的專用資料,它将儲存在該proc條目對應的struct file結構的private_data字段中。

7)struct proc_dir_entry *create_proc_info_entry(const char *name,mode_t mode, struct proc_dir_entry *base, get_info_t *get_info)

該函數用于建立一個info型的proc條目,參數name給出了要建立proc條目的名稱,參數mode給出了建立該proc條目的通路權限,參數base指定建立proc條目所在的目錄,參數get_info指定該proc條目的get_info操作函數。實際上get_info等同于read_proc,如果proc條目沒有定義read_proc,對該proc條目的read操作将使用get_info取代,是以它在功能上非常類似于函數create_proc_read_entry。

8)struct proc_dir_entry *proc_net_create(const char *name, mode_t mode, get_info_t *get_info)

該函數用于在/proc/net目錄下建立一個proc條目,參數name給出了要建立proc條目的名稱,參數mode給出了建立該proc條目的通路權限,參數get_info指定該proc條目的get_info操作函數。

9)struct proc_dir_entry *proc_net_fops_create(const char *name, mode_t mode, struct file_operations *fops)

該函數也用于在/proc/net下建立proc條目,但是它也同時指定了對該proc條目的檔案操作函數。

10)void proc_net_remove(const char *name)

該函數用于删除前面兩個函數在/proc/net目錄下建立的proc條目。參數name指定要删除的proc名稱。

除了上述這些函數,值得一提的是結構struct proc_dir_entry,為了建立可讀可寫的proc條目并指定該proc條目的寫操作函數,必須設定上面那些建立proc條目的函數傳回指針指向的struct proc_dir_entry結構的write_proc字段,并指定該proc條目的通路權限有寫權限。

讀者可以通過cat和echo等檔案操作函數來檢視和設定這些proc檔案。下面就在上面的mydriver.c裡面增加一個可讀的proc接口功能,來擷取裝置的運作狀态。

注意要先在頭檔案引用部分添加#include <linux/proc_fs.h>,

接着在mydriver_init(void)裡面添加如下一句語句:

create_proc_read_entry("mydriver", 0, NULL, mydriver_stats, &mydriver_dev);

不要忘記在mydriver_exit(void)裡面要添加:

remove_proc_entry("mydriver", NULL);

當然最重要的還是要定讀取proc時的操作函數mydriver_stats,具體定義如下:

static int mydriver_stats(char *buf, char **start, off_t offset,                                     int count, int *eof, void *data)

    PMYDRIVER_DEV  dev = (PMYDRIVER_DEV)data;

    int  len;

    char *  p = buf;

    p += sprintf(p, "mydriver power count: %d\n", dev->power_                                            count);

    p += sprintf(p, "mydriver  RX stats: \n");

    p += sprintf(p, "     intrs:   %ld\n", dev->stats.rx_intrs);

    p += sprintf(p, "     errors:  %ld\n", dev->stats.rx_errors);

    p += sprintf(p, "     blocks:  %ld\n", dev->stats.rx_blocks);

    p += sprintf(p, "     dropped: %ld\n", dev->stats.rx_dropped);

    p += sprintf(p,    "mydriver  TX stats: \n");

    p += sprintf(p, "     intrs:   %ld\n", dev->stats.tx_intrs);

    p += sprintf(p, "     errors:  %ld\n", dev->stats.tx_errors);

    p += sprintf(p, "     missed:  %ld\n", dev->stats.tx_missed);

    p += sprintf(p, "     blocks:  %ld\n", dev->stats.tx_blocks);

    p += sprintf(p, "     dropped: %ld\n", dev->stats.tx_dropped);

    len = p - buf;

    return len;

通過上面對Linux下驅動的幾方面介紹,讀者應該可以實作基本的驅動架構,嘗試在驅動中進行中斷。還可以利用ioctl來進行裝置的管理及使用procfs來擷取裝置的運作狀态等。

Linux 2.4的核心下驅動編譯出來的名字通常是*.o的檔案,而在Linux 2.6的核心下編譯出來的檔案是*.ko的檔案。在編譯驅動的時候,注意要在編譯的時候加__DKERNEL__和_DMODULE參數。還要注意在 makefile 檔案裡面正确地指定 KERNELDIR 和INCLUDEDIR。驅動程式有兩種加載方式,核心自動加載和手動加載。通常的做法是在調試過程中采用手動加載的方式,等調試好了之後,就可以編譯到核心裡面采用自動加載的方式。驅動相對于核心來說就是核心的子產品。

核心驅動子產品的加載指令用insmod,如insmod mydriver.o。這個指令其實就是調用驅動裡面的mydriver_init()函數。用insmod指令将編譯好的子產品調入記憶體時,就是向系統的字元裝置表登記了一個字元裝置。如果登記成功,傳回裝置的主裝置号,不成功,傳回一個負值。那麼核心驅動子產品的解除安裝就是調用rmmod mydriver,這是調用驅動裡面的mydriver_exit()函數,它釋放字元裝置test在系統字元裝置表中占有的表項。當然,上層如果要通路核心的驅動子產品,還需要在dev目錄下添加裝置通路節點,在dev目錄下執行mknod c主裝置号從裝置号:

mknod mydriver c 10 174

從裝置号可以從0-254,主裝置号在include/linux/major.h檔案裡可以看到具體的定義,在include/linux/miscdevices.h檔案裡可以看到從裝置号的一些定義,在定義自己的裝置号的時候注意不要和系統核心中原有的裝置号沖突。

另外一點需要注意如果使用了devfs檔案系統的話,裝置節點的目錄是不同的。裝置的通路節點要改成/dev/misc/mydriver。可以通過檢視編譯出來的 system.map 檔案或 cat /proc/ksyms檢視底層驅動導出的函數。

當核心加載了驅動之後,上層就可以通過驅動對底層裝置進行操作了。如例程5-5所示的代碼是一個簡單的對mydriver進行讀寫和ioctl的例子。

例程5‑5  test.c

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/ioctl.h>

#include <fcntl.h>

#include <stdio.h>

#include <unistd.h>

#if 0

#define DEV_MYDRIVER    "/dev/misc/mydriver"

#else

#define DEV_MYDRIVER     "/dev/mydriver"

int dev_cmd(const char * dev, int cmd, unsigned long arg)

    int  fd;

    fd = open(dev, O_RDWR);

    if ( fd < 0 )

        perror(dev);

        return -1;

    ioctl(fd, cmd, arg);

    close(fd);

void test_ioctl(void)

   dev_cmd(DEV_MYDRIVER,MYDRIVER_IOC_RESET,0);

void test_mydriver(void)

    int   i, j;

    int   fd;

    char  buf[34];

    i = 0;

    while ( 1 )

        i++;

        printf("\n***** count: %d *****\n", i);

        fd = open(DEV_MYDRIVER, O_RDWR);

        for ( j = 0; j < 50; j++ )

        {

            read(fd, buf, sizeof(buf));

            write(fd, buf, sizeof(buf));

        }

        close(fd);

int main(int argc, char * argv[])

    if ( argc < 2 )

        printf("test <a|c>\n");

        return 1;

    switch ( argv[1][0] )

        case 'a':

            test_mydriver();

        case 'c':

            test_ioctl ();

    return  0;

本章重點描述了基于ARM平台的驅動開發技術,也适當地介紹了一些有關ARM彙編的基本知識,讀者可以看一些有關ARM彙編的書籍做更為詳細的了解。對于ARM底層驅動開發,這裡也介紹了一些簡單的方法與個人的實際體會。通過本章的介紹,讀者應該可以輕松開始Linux下的驅動開發了,但是要深入了解Linux下的驅動開發技術,可以看一下《Linux驅動開發技術》一書。在實際的驅動開發中,建議讀者先仔細閱讀器件的資料手冊及針對該器件手冊的勘誤手冊,這能夠少走彎路;其次在器件廠家或者網絡上查找有沒有針對該器件的驅動。原則上不要自己從頭開始編寫所有的驅動,畢竟是站在巨人的肩膀上可以比巨人看得更遠。

繼續閱讀