顯示具有 kernel driver 標籤的文章。 顯示所有文章
顯示具有 kernel driver 標籤的文章。 顯示所有文章

2014年12月12日 星期五

寫驅動程式所需的技能

我個人寫過很多種作業系統的驅動程式,但是寫驅動程式對一般寫程式的人員而言是一個非常神秘的事,不知道如何著手,也不知道需要那些技能,這邊就來談談這方面的事,好讓有心要寫驅動程式的人員才知道如何準備。
首先當然 C 要很熟,以前的人在寫 Linux 驅動程式時總是喜歡用較艱深的語法來寫,造成其他的人很難理解,現在則是𣎴會這樣了,大家都會用較簡易的方式來寫,所以如何來檢視自己的 C 是否熟悉,找出 Linux 驅動程式一個約有 100 行的程式碼,在10~20 分鐘之內將其看完,即可了解其程式羅輯,甚至其中若有錯誤也可將其指出,以及該程式的優缺點,那你的 C 就夠熟了。再者驅動程式中用很多的 function pointer 以及資料結構,另外對 pointer 也用的很多,所以這方面的 C 都要很熟悉,將會對於寫驅動程式有很大的幫助。
再來需要的技能就比較有點麻煩,因為是寫驅動程式,所以對硬體要有一點認識,舉例來說如果你要寫的是硬碟驅動程式,你當然得對硬碟的一些硬體動作或者行為必須有所了解,因為驅動程式就是在控制硬體行為,如此你才能知道如何去控制硬體才是正確的,又如你寫的是 Ethernet 驅動程式,你必須對於 Ethernet 的硬體行為以及基本的操作要有所了解,如此當你在寫它的驅動程式時,才能了解為何其硬體行為如何,出來的結果才知道正確或不正確,再者對於通訊協議的驅動程式,你可能對其協議內容也要有所了解,這樣對正確性以及除錯都有所幫助, 另外在做自我測試時才能知道如何架設測試環境,所有的軟體一定都要經過嚴謹的測試,才能確定其一定的正確性,尤其是在硬體的驅動程式,因為硬體有相容性的問題,而這些問題可能都需要經過軟體,以及硬體環境的架設才能得到正確的測試,而對硬體的了解都有助於你開發驅動程式的優質化。
另外的一個知識則是對作業系統的了解,驅動程序它要利用 kernel 所提供的 API 來和系統合作,所以它也可以說是作業系統的一部分,而且它是運行在系統中,因此如果驅動寫的不好,是會造成系統當掉,或者不穩定等等,所以對於該系統的一些內部行為要有所了解,如此也才能和系統好好地合作。另外一個的知識是系統了解,是在開完機之後的系統了解,如何設定系統,如果你的驅動程式是給系統管理用的,那你就要了解如何使用它,如此測試才能確保你的驅動是正確的,你也必須很清楚地了解,你的驅動程式到底是在何時載入系統的,這樣才能確保它可以正常地運行你的驅動程式。
這時則是我們最常説的,需要了解作業業系統的驅動程式要如何撰寫,每個作業系統的驅動程式架構都不太一樣,所以我們必須去了解它,但是在Linux中有一個好處,就是你可以取得所有的驅動程式原始碼,因此你可以參考其他人的程式,方便你快速了解,再者驅動程式所呼叫的API,和應用程式完全不同,因此你需要全部重新了解以及知道。
最後則是應用程式的了解,也就是要會寫應用程式來測試你的驅動程式,前面有提過驅動程式通常是給應用程式用的,為了要確保你的驅動程式是没有問題,你必須自己先寫一些應用測試程式來測你的驅動程式,當然如果你所在的單位有專門的測試單位,你可以讓測試單位來協助你測試,不過我還是建議自己還是先測試有没有問題,再交給測試單位測試,我想這是對自己的程式負責的態度。
結論我再一次條例式列出寫驅程式所需的技巧:
  • C 語言的熟悉
  • 作業系統的了解
  • 硬體的了解
  • 了解如何撰寫驅動程式
  • 作業系統的操作的了解
  • 應用程式的撰寫

以上是我個人認為要寫好一個驅動程式所必要的條件,通常我們要會寫一個驅動程式不太難,但是要寫好一個驅動程式就不太容易,所謂一個好的驅動程式,有幾個必要條件,第一要穩定,就是在任何情況下都不會當掉,第二就是要好維護,不要讓後面接手的人看不懂你的程式,也不知道要如何修改,第三就是要容易除錯,最後希望大家都能夠寫好一個驅動程式。

2011年4月6日 星期三

如何撰寫Driver支援proc檔案系統

前言
在Linux的系統中,通常我們都會有一個目錄 – proc,其實它是一個虛擬的記憶體檔案系統,它通常是一個應用程式和kernel直接溝通的一個介面,它是由各個driver所產生,換另一句話就是要Driver有撰寫才會有這樣的功能,另外其檔名及內容都是driver自行解決,目前主要都是來設定driver自己本身的一些功能,這樣可以讓驅動程式更多元化,或者可以讀取驅動程式現在的情形為何,這可以來維護系統的運作,現在有很多驅動程式已經支援這個功能,所以有許多的系統管理程式,也是透過這個介面來管理系統核心。現在我們來介紹如何在driver中撰這些程式碼。
如何啓動這個目錄
因為它是一個虛擬的記憶體檔案系統,所以將使用Linux系統中管理檔案系統一樣的方法,就是使用mount的指令將其掛載在作業系統的檔案系統中,以下為單獨載入的指令:
> mount –t proc proc /porc
另外在kernel的選項中必須要啓動以下選項:
CONFIG_PROC_FS=y
CONFIG_PROC_SYSCTL=y
Kernel API
在介紹如何撰寫前,先來了解有那些kernel API可供使用,其所用的include file是,而API如下:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent)
這是建立一個在相對目錄之下的一個檔名或目錄,其input/output說明如下:
Input :
const char *name – 想要建立的檔名,相對於後面的parent的目錄之下,若後面的parent為NULL,則其相對目錄為/proc之下,另外這個檔名也可以包含目錄。
mode_t mode – 前面所要建立之名稱為檔案或者是目錄,0為檔案,S_IFDIR為目錄。
struct proc_dir_entry *parent – 這次要建立的檔名或者目錄,其上層的目錄為何,若這個參數為NULL則表示為/proc目錄。
Output :
其返回值為一個資料結構的位址point,這個資料結構會在後面介紹,這個point位址將會給予保留,因為我們需要在其中幾個call back欄位填入我們要實現的功能,也就是提供給AP如何溝通的介面。
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
這是移除已建立的檔案或目錄,其中輸入的參數皆和建立時相同。
以下為一個簡單的範例:
struct proc_dir_entry *MyProcEntry;
MyProcEntry = create_proc_entry(“driver/myproc”, 0, NULL);
MyProcEntry->read = MyProcRead;
MyProcEntry->write = MyProcWrite;
……………………………….
remove_proc_entry(“driver/myproc”, NULL);
資料結構
如前面所提,其中最重要的資料結構宣告如下:
struct proc_dir_entry {
        unsigned int low_ino;
        unsigned short namelen;
        const char *name;
        mode_t mode;
        nlink_t nlink;
        uid_t uid;
        gid_t gid;
        loff_t size;
        const struct inode_operations *proc_iops;
        /*
         * NULL ->proc_fops means "PDE is going away RSN" or
         * "PDE is just created". In either case, e.g. ->read_proc won't be
         * called because it's too late or too early, respectively.
         *
         * If you're allocating ->proc_fops dynamically, save a pointer
         * somewhere.
         */
        const struct file_operations *proc_fops;
        struct proc_dir_entry *next, *parent, *subdir;
        void *data;
        read_proc_t *read_proc;
        write_proc_t *write_proc;
        atomic_t count;         /* use count */
        int pde_users;  /* number of callers into module in progress */
        spinlock_t pde_unload_lock; /* proc_fops checks and pde_users bumps */
        struct completion *pde_unload_completion;
        struct list_head pde_openers;   /* who did ->open, but not ->release */
};
在這個資料結構中有兩個重要的call back function,一個是read_proc,另一個是write_proc,其實就是寫入及讀出。以下為其簡單的範例:
static int      my_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{
        char    *out = page;
        int     len;
        dbg_printk("page=0x%p,off=0x%x,count=%d\n", page, off, count);
        out += sprintf(out, "This is a read proc testing.\n");
        dbg_printk("out=0x%p\n", out);
        len = out - page - off;
        dbg_printk("len=%d,out=0x%p\n", len, out);
        if (len < count)  {
                *eof = 1;
                if (len <= 0)
                        return 0;
        }  else
                len = count;
        *start = page + off;
        dbg_printk("start=0x%p\n", start);
        return len;
}
static int      my_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data)
{
        char    *buf;
        dbg_printk("count=%d\n", count);
        buf = kmalloc(count+1, GFP_KERNEL);
        if ( copy_from_user(buf, (char*)buffer , count) ) {
                kfree(buf);
                return -EFAULT;
        }
        buf[count] = 0;
        dbg_printk("string=%s\n", buf);
        kfree(buf);
        return count;
}
在read call back function中所傳入的page為要存放輸出內容的buffer所在page address,其off為相對於page的offset位置,通常為0,不管read or write其所return的值為實際處理的大小,因為其處理方式為page的方式,所以一次可處理的資料不會大於一個page的大小,現在default值為4096bytes,以上的範例若是成功將可以產生一個虛擬檔案/proc/driver/myproc,你將可以使用簡單的echo & cat來對其做讀寫的動作。