Linux下字符設備驅動程序的結構

首先,希望可以明確一下概念:

1、 要說的是,驅動,必然是與內核緊密相連的底層程序;並且接口對用戶是不可見的。所以你要想到的是如何把驅動和內核聯繫起來,第一個就想到了模塊(modules),模塊就像一個載體,一個容器,通過它,會把你寫好的程序插入(裝載)到內核可見的區域,從而使內核感知到你的驅動的存在,然後用戶空間才能通過系統調用的形式聯繫到驅動,從而完成它的任務,所以你首要了解模塊。

2、 驅動位於OS之下,爲OS提供硬件操作(當然驅動也有可能不是針對硬件的)的邏輯和底層抽象的封裝,所以他要上下兼顧,上邊必須符合OS的驅動調用接口,下邊要處理好硬件的操作。

3、 Linux比較特殊,把所有的設備都抽象成了文件,這樣的話操作接口會比較統一,並且給開發也帶來了很大的方便。Linux下設備文件有三種:字符設備、塊設備、網絡設備。

簡單的介紹一下做一個驅動(字符設備驅動)需要做哪些:

1、 表徵設備存在的結構體cdev:


struct cdev{
 		struct kobject kobj; 			 /* 內嵌的kobject對象*/
 		struct module *owner; 			 /*所屬模塊*/
struct file_operations *ops;	 /*文件操作結構體*/
struct list_head list;
dev_t dev; 		/*設備號,爲32 位,其中高12 位爲主設備號,低20 位爲次設備號*/
 		unsigned int count;
};


整個過程都是圍繞着這個結構體在進行。對他的各元素賦值並通過模塊加載到內核即可。我們逐一的說明一下。

對於,kobject的初始化是在cdev_init裏邊完成的,它在Fs/Char_dev.c :


void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}

這裏連帶着把kobject、list_head、file_operations也都做了賦值或初始化;這裏做爲參數你要有一個file_operations來用於賦值,,而file_operations包含了對該字符設備進行操作的所有可能用到的函數(接口)。從而就引出了file_operations,它是整個字符驅動的核心,也是要把所有處理邏輯綁定的對象:


static const struct file_operations globalmem_fops={  
    .owner = THIS_MODULE,  
    .read = globalmem_read,  
    .write = globalmem_write,  
    .open = globalmem_open,  
    .release = globalmem_release,  
};

這裏是個簡單的例子,我們要做的就是實現那些等號後邊的函數,並對其賦值。下邊是file_operations的原型,很完備的接口,只用實現你用到的就可以了(在include/linux/fs.h中):


struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

到這裏,通過cdev的初始化,我們已經把你寫的邏輯和cdev聯繫起來了(通過file_operations),然後通過:

    cdev.owner = THIS_MODULE;

直接複製把cdev和當前模塊聯繫了起來。然後是通過:

    dev_tdevno =MKDEV(222,0); 

用MKDEV宏生成一個主設備號222,此設備號0的dev_t類型的設備號變量devno,之後是把設備號和設備結構體cdev關聯起來,這樣設備才能間接找到它的處理函數,用這個來完成關聯:

    cdev_add(&dev->cdev,devno,1);

well,設備結構體準備好了,和具體的設備號也約定好了(這樣屬於該類型的設備會直接來找它進行具體的處理),另外,也和當前模塊掛接好了,還差一步就是讓內核知道它的存在,用下邊的函數:

    register_chrdev_region(devno,1,"globalmem");

把當前設備註冊到內核,這個和cdev沒有關係,只是對虛擬設備globalmem進行內核的註冊,而他們聯繫的紐帶就是devno。當然對應的還有註銷函數:

    unregister_chrdev_region(devno,1);

他們一般是成對出現的。

 

這樣就差不多了,整個過程及原理也就是這樣了,更詳細的,可以好好品讀一下Linux驅動開發詳解的第六章。講得非常詳細。另外看一下另外一篇blog,【Linux下第一個驅動程序】,是一個實例,並附有大量說明,這樣應該可以建立一個初步的概念。然後就是循循漸進了,哈哈。

 

【本文doc文檔下載】



發佈了67 篇原創文章 · 獲贊 1099 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章