學(xué)習(xí)了驅(qū)動(dòng)程序的設(shè)計(jì),感覺在學(xué)習(xí)驅(qū)動(dòng)的同時(shí)學(xué)習(xí)linux內(nèi)核,也是很不錯(cuò)的過程哦,做了幾個(gè)實(shí)驗(yàn),該做一些總結(jié),只有不停的作總結(jié)才能印象深刻。 我的平臺(tái)是虛擬機(jī),fedora14,內(nèi)核版本為2.6.38.1.其中較之前的版本存在較大的差別,具體的實(shí)現(xiàn)已經(jīng)在上一次總結(jié)中給出了。今天主要總結(jié)的是ioctl和堵塞讀寫函數(shù)的實(shí)現(xiàn)。 一、ioctl函數(shù)的實(shí)現(xiàn) 首先說明在2.6.36以后ioctl函數(shù)已經(jīng)不再存在了,而是用unlocked_ioctl和compat_ioctl兩個(gè)函數(shù)實(shí)現(xiàn)以前版本的ioctl函數(shù)。同時(shí)在參數(shù)方面也發(fā)生了一定程度的改變,去除了原來ioctl中的struct inode參數(shù),同時(shí)改變了返回值。 但是驅(qū)動(dòng)設(shè)計(jì)過程中存在的問題變化并不是很大,同樣在應(yīng)用程序設(shè)計(jì)中我們還是采用ioctl實(shí)現(xiàn)訪問,而并不是unlocked_ioctl函數(shù),因此我們還可以稱之為ioctl函數(shù)的實(shí)現(xiàn)。 ioctl函數(shù)的實(shí)現(xiàn)主要是用來實(shí)現(xiàn)具體的硬件控制,采用相應(yīng)的命令控制硬件的具體操作,這樣就能使得硬件的操作不再是單調(diào)的讀寫操作。使得硬件的使用更加的方便。 ioctl函數(shù)實(shí)現(xiàn)主要包括兩個(gè)部分,首先是命令的定義,然后才是ioctl函數(shù)的實(shí)現(xiàn),命令的定義是采用一定的規(guī)則。 ioctl的命令主要用于應(yīng)用程序通過該命令操作具體的硬件設(shè)備,實(shí)現(xiàn)具體的操作,在驅(qū)動(dòng)中主要是對(duì)命令進(jìn)行解析,通過switch-case語句實(shí)現(xiàn)不同命令的控制,進(jìn)而實(shí)現(xiàn)不同的硬件操作。 ioctl函數(shù)的命令定義方法: int (*unlocked_ioctl)(struct file*filp,unsigned int cmd,unsigned long arg) 雖然其中沒有指針的參數(shù),但是通常采用arg傳遞指針參數(shù)。cmd是一個(gè)命令。每一個(gè)命令由一個(gè)整形數(shù)據(jù)構(gòu)成(32bits),將一個(gè)命令分成四部分,每一部分實(shí)現(xiàn)具體的配置,設(shè)備類型(幻數(shù))8bits,方向2bits,序號(hào)8bits,數(shù)據(jù)大小13/14bits。命令的實(shí)現(xiàn)實(shí)質(zhì)上就是通過簡單的移位操作,將各個(gè)部分組合起來而已。 一個(gè)命令的分布的大概情況如下: |---方向位(31-30)|----數(shù)據(jù)長度(29-16)----------------|---------設(shè)備類型(15-8)------|----------序號(hào)(7-0)----------| |----------------------------------------------------------------------------------------------------------------------------------------| 其中方向位主要是表示對(duì)設(shè)備的操作,比如讀設(shè)備,寫設(shè)備等操作以及讀寫設(shè)備等都具有一定的方向,2個(gè)bits只有4種方向。 數(shù)據(jù)長度表示每一次操作(讀、寫)數(shù)據(jù)的大小,一般而已每一個(gè)命令對(duì)應(yīng)的數(shù)據(jù)大小都是一個(gè)固定的值,不會(huì)經(jīng)常改變,14bits說明可以選擇的數(shù)據(jù)長度最大為16k。 設(shè)備類型類似于主設(shè)備號(hào)(由于8bits,剛好組成一個(gè)字節(jié),因此經(jīng)常采用字符作為幻數(shù),表示某一類設(shè)備的命令),用來區(qū)別不同的命令類型,也就是特定的設(shè)備類型對(duì)應(yīng)特定的設(shè)備。序號(hào)主要是這一類命令中的具體某一個(gè),類似于次設(shè)備號(hào)(256個(gè)命令),也就是一個(gè)設(shè)備支持的命令多達(dá)256個(gè)。 同時(shí)在內(nèi)核中也存在具體的宏用來定義命令以及解析命令。 但是大部分的宏都只是定義具體的方向,其他的都需要設(shè)計(jì)者定義。 主要的宏如下: #include _IO(type,nr) 表示定義一個(gè)沒有方向的命令, _IOR(type,nr,size) 表示定義一個(gè)類型為type,序號(hào)為nr,數(shù)據(jù)大小為size的讀命令 _IOW(type,nr,size) 表示定義一個(gè)類型為type,序號(hào)為nr,數(shù)據(jù)大小為size的寫命令 _IOWR(type,nr,size) 表示定義一個(gè)類型為type,序號(hào)為nr,數(shù)據(jù)大小為size的寫讀命令 通常的type可采用某一個(gè)字母或者數(shù)字作為設(shè)備命令類型。 是實(shí)際運(yùn)用中通常采用如下的方法定義一個(gè)具體的命令:
還有對(duì)命令進(jìn)行解析的宏,用來確定具體命令的四個(gè)部分(方向,大小,類型,序號(hào))具體如下所示:
上面的幾個(gè)宏可以用來命令,實(shí)現(xiàn)命令正確性的檢查。 ioctl的實(shí)現(xiàn)過程主要包括如下的過程: 1、命令的檢測(cè) 2、指針參數(shù)的檢測(cè) 3、命令的控制switch-case語句 1、命令的檢測(cè)主要包括類型的檢查,數(shù)據(jù)大小,序號(hào)的檢測(cè),通過結(jié)合上面的命令解析宏可以快速的確定。
2、主要是指針參數(shù)的檢測(cè)。指針參數(shù)主要是因?yàn)閮?nèi)核空間和用戶空間的差異性導(dǎo)致的,因此需要來自用戶空間指針的有效性。使用copy_from_user,copy_to_user,get_user,put_user之類的函數(shù)時(shí),由于函數(shù)會(huì)實(shí)現(xiàn)指針參量的檢測(cè),因此可以省略,但是采用__get_user(),__put_user()之類的函數(shù)時(shí)一定要進(jìn)行檢測(cè)。具體的檢測(cè)方法如下所示:
當(dāng)方向是讀時(shí),說明是從設(shè)備讀數(shù)據(jù)到用戶空間,因此要檢測(cè)用戶空間的指針是否可寫,采用VERIFY_WRITE,而當(dāng)方向是寫時(shí),說明是往設(shè)備中寫數(shù)據(jù),因此需要檢測(cè)用戶空間中的指針的可讀性VERIFY_READ。檢查通常采用access_ok()實(shí)現(xiàn)檢測(cè),第一個(gè)參數(shù)為讀寫,第二個(gè)為檢測(cè)的指針,第三個(gè)為數(shù)據(jù)的大小。 3、命名的控制: 命令的控制主要是采用switch和case相結(jié)合實(shí)現(xiàn)的,這于window編程中的檢測(cè)各種消息的實(shí)現(xiàn)方式是相同的。
這只是基本的框架結(jié)構(gòu),實(shí)際中根據(jù)具體的情況進(jìn)行修改。這樣就實(shí)現(xiàn)了基本的命令控制。 文件操作支持的集合如下:
需要注意不是ioctl,而是unlocked_ioctl。 二、設(shè)備的堵塞讀寫方式實(shí)現(xiàn),通常采用等待隊(duì)列。 設(shè)備的堵塞讀寫方式,默認(rèn)情況下的讀寫操作都是堵塞型的,具體的就是如果需要讀數(shù)據(jù),當(dāng)設(shè)備中沒有數(shù)據(jù)可讀的時(shí)候應(yīng)該等待設(shè)備中有設(shè)備再讀,當(dāng)往設(shè)備中寫數(shù)據(jù)時(shí),如果上一次的數(shù)據(jù)還沒有被讀完成,則不應(yīng)該寫入數(shù)據(jù),就會(huì)導(dǎo)致進(jìn)程的堵塞,等待數(shù)據(jù)可讀寫。但是在應(yīng)用程序中也可以采用非堵塞型的方式進(jìn)行讀寫。只要在打開文件的時(shí)候添加一個(gè)O_NONBLOCK,這樣在不能讀寫的時(shí)候就會(huì)直接返回,而不會(huì)等待。 因此我們?cè)趯?shí)際設(shè)計(jì)驅(qū)動(dòng)設(shè)備的同時(shí)需要考慮讀寫操作的堵塞方式。堵塞方式的設(shè)計(jì)主要是通過等待隊(duì)列實(shí)現(xiàn),通常是將等待隊(duì)列(實(shí)質(zhì)就是一個(gè)鏈表)的頭作為設(shè)備數(shù)據(jù)結(jié)構(gòu)的一部分。在設(shè)備初始化過程中初始化等待隊(duì)列的頭。最后在設(shè)備讀寫操作的實(shí)現(xiàn)添加相應(yīng)的等待隊(duì)列節(jié)點(diǎn),并進(jìn)行相應(yīng)的控制。 等待隊(duì)列的操作基本如下: 1、等待隊(duì)列的頭定義并初始化的過程如下: 方法一: struct wait_queue_head_t mywaitqueue; init_waitqueue_head(&mywaitqueue); 方法二: DECLARE_WAIT_QUEUE_HEAD(mywaitqueue); 以上的兩種都能實(shí)現(xiàn)定義和初始化等待隊(duì)列頭。 2、創(chuàng)建、移除一個(gè)等待隊(duì)列的節(jié)點(diǎn),并添加、移除相應(yīng)的隊(duì)列。 定義一個(gè)等待隊(duì)列的節(jié)點(diǎn):DECLARE_WAITQUEUE(wait,tsk) 其中tsk表示一個(gè)進(jìn)程,可以采用current當(dāng)前的進(jìn)程。 添加到定義好的等待隊(duì)列頭中。 add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait); 即:add_wait_queue(&mywaitqueue,&wait); 移除等待節(jié)點(diǎn) remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait); 即:remove_wait_queue(&mywaitqueue,&wait); 3、等待事件 wait_event(queue,condition);當(dāng)condition為真時(shí),等待隊(duì)列頭queue對(duì)應(yīng)的隊(duì)列被喚醒,否則繼續(xù)堵塞。這種情況下不能被信號(hào)打斷。 wait_event_interruptible(queue,condition);當(dāng)condition為真時(shí),等待隊(duì)列頭queue對(duì)應(yīng)的隊(duì)列被喚醒,否則繼續(xù)堵塞。這種情況下能被信號(hào)打斷。 4、喚醒等待隊(duì)列 wait_up(wait_queue_head_t *q),喚醒該等待隊(duì)列頭對(duì)應(yīng)的所有等待。 wait_up_interruptible(wait_queue_head_t *q)喚醒處于TASK_INTERRUPTIBLE的等待進(jìn)程。 應(yīng)該成對(duì)的使用。即wait_event于wait_up,而wait_event_interruptible與wait_up_interruptible。
#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) #define DEFINE_WAIT_FUNC(name, function) \ wait_queue_t name = { \ .private = current, \ .func = function, \ .task_list = LIST_HEAD_INIT((name).task_list), \ }
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); __wake_up_common(q, mode, nr_exclusive, 0, key); spin_unlock_irqrestore(&q->lock, flags); } static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int wake_flags, void *key) { wait_queue_t *curr, *next; list_for_each_entry_safe(curr, next, &q->task_list, task_list) { unsigned flags = curr->flags; if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) break; } } 等待隊(duì)列通常用在驅(qū)動(dòng)程序設(shè)計(jì)中的堵塞讀寫操作,并不需要手動(dòng)的添加節(jié)點(diǎn)到隊(duì)列中,直接調(diào)用即可實(shí)現(xiàn),具體的實(shí)現(xiàn)方法如下: 1、在設(shè)備結(jié)構(gòu)體中添加等待隊(duì)列頭,由于讀寫都需要堵塞,所以添加兩個(gè)隊(duì)列頭,分別用來堵塞寫操作,寫操作。
2、然后在模塊初始化中初始化隊(duì)列頭:
3、確定一個(gè)具體的條件,比如數(shù)據(jù)有無,具體的條件根據(jù)實(shí)際的情況設(shè)計(jì)。 /*等待條件*/ static bool havedata = false; 4、在需要堵塞的讀函數(shù),寫函數(shù)中分別實(shí)現(xiàn)堵塞,首先定義等待隊(duì)列的節(jié)點(diǎn),并添加到隊(duì)列中去,然后等待事件的喚醒進(jìn)程。但是由于讀寫操作的兩個(gè)等待隊(duì)列都是基于條件havedata的,所以在讀完成以后需要喚醒寫,寫完成以后需要喚醒讀操作,同時(shí)更新條件havedata,最后還要移除添加的等待隊(duì)列節(jié)點(diǎn)。
#if 0
#if 0
#if 0
5、應(yīng)用程序采用兩個(gè)不同的進(jìn)程分別進(jìn)行讀、寫,然后檢測(cè)順序是否可以調(diào)換,檢查等待是否正常。
|
|