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來對其做讀寫的動作。

沒有留言:

張貼留言