《Linux Device Drivers》第六章 高級(jí)字符驅(qū)動(dòng)程序操作――note
來(lái)源:程序員人生 發(fā)布時(shí)間:2014-10-11 08:00:01 閱讀次數(shù):2225次
- ioctl
- 支持的操作,例如
- 簡(jiǎn)單數(shù)據(jù)傳輸
- 控制動(dòng)作,例如用戶(hù)空間發(fā)起彈出介質(zhì)動(dòng)作
- 反饋硬件的狀態(tài),例如報(bào)告錯(cuò)誤信息
- 參數(shù)配置,例如改變波特率
- 執(zhí)行自破壞
- 用戶(hù)空間的ioctl方法原型:int ioctl(int fd, unsigned long cmd, …);每個(gè)ioctl命令就是一個(gè)獨(dú)立的系統(tǒng)調(diào)用,而且是非公開(kāi)的
- 驅(qū)動(dòng)程序的ioctl方法原型:int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
- 選擇ioctl命令
- 為方便程序員創(chuàng)建唯一的ioctl命令號(hào),每一個(gè)命令號(hào)被分為多個(gè)位字段
- Linux內(nèi)核的約定方法為驅(qū)動(dòng)程序選擇ioctl編號(hào)
- include/asm/ioctl.h
- 定義了要使用的位字段
- 類(lèi)型(幻數(shù))
- 序數(shù)
- 傳送方向
- 參數(shù)大小
- Documentation/ioctl-number.txt
- <linux/ioctl.h>
- type
- 幻數(shù),這個(gè)字段有8位寬(_IOC_TYPEBITS)
- number
- direction
- _IOC_NONE(沒(méi)有數(shù)據(jù)傳輸)
- _IOC_READ
- _IOC_WRITE
- _IOC_READ | _IOC_WRITE(雙向傳輸數(shù)據(jù))
- size
- 所涉及的用戶(hù)數(shù)據(jù)大小
- 通常是13位或14位
- _IOC_SIZEBITS
- <asm/ioctl.h>
- _IO(type, nr)
- 用于構(gòu)造無(wú)參數(shù)的命令編號(hào)
- _IOR(type, nr, datatype)
- 用于構(gòu)造從驅(qū)動(dòng)程序中讀取數(shù)據(jù)的命令編號(hào)
- _IOW(type, nr, datatype)
- 用于構(gòu)造寫(xiě)入數(shù)據(jù)的命令
- _IOWR(type, nr, datatype)
- _IOC_DIR(nr)
- _IOC_TYPE(nr);
- _IOC_NR(nr)
- _IOC_SIZE(nr);
- 返回值
- ioctl的實(shí)現(xiàn)通常就是一個(gè)基于命令號(hào)的switch語(yǔ)句
- 不能匹配任何合法的操作?
- 有些內(nèi)核函數(shù)會(huì)返回-EINVAL
- POSIX標(biāo)準(zhǔn)規(guī)定,如果使用了不合適的ioctl命令參數(shù),應(yīng)該返回-ENOTTY
- 預(yù)定義命令
- 預(yù)定義命令分為三組
- 可用于任何文件(普通、設(shè)備、FIFO和套接字)的命令
- 只用于普通文件的命令
- 特定于文件系統(tǒng)類(lèi)型的命令
- 設(shè)備驅(qū)動(dòng)程序開(kāi)發(fā)人員只對(duì)第一組感興趣,它們的幻數(shù)都是“T”
- FIOCLEX
- 設(shè)置執(zhí)行時(shí)關(guān)閉標(biāo)志
- FIONCLEX
- 清除執(zhí)行時(shí)關(guān)閉標(biāo)志
- FIOASYNC
- 設(shè)置或復(fù)位文件異步通知
- 這兩個(gè)動(dòng)作都可以通過(guò)fcntl完成,實(shí)際上沒(méi)有人會(huì)使用FIOASYNC
- FIOQSIZE
- FIONBIO
- 使用ioctl參數(shù)
- <asm/uaccess.h>
- int access_ok(int type, const void *addr, unsigned long size);
- type
- addr
- size
- 返回一個(gè)布爾值:1表示成功,0表示失敗
- 如果返回失敗,驅(qū)動(dòng)程序通常要返回-EFAULT給調(diào)用者
- put_user(datum, ptr);
- __put_user(datum, ptr);
- get_user(local, ptr);
- __get_user(local, ptr);
- 權(quán)能與受限操作
- 基于權(quán)能(capability)的系統(tǒng)把特權(quán)操作劃分為獨(dú)立的組
- capget
- capset
- <linux/capability.h>
- CAP_DAC_OVERRIDE
- 超過(guò)文件或目錄的訪(fǎng)問(wèn)限制的能力
- CAP_NET_ADMIN
- 執(zhí)行網(wǎng)絡(luò)管理任何的能力
- CAP_SYS_MODULE
- CAP_SYS_RAWIO
- CAP_SYS_ADMIN
- CAP_SYS_TTY_CONFIG
- <sys/sched.h>
- int capable(int capability);
- 阻塞型I/O
- 休眠的簡(jiǎn)單介紹
- 當(dāng)一個(gè)進(jìn)程被置入休眠時(shí),它會(huì)被標(biāo)記為一種特殊狀態(tài)并從調(diào)度器的運(yùn)行隊(duì)列中移走
- 永遠(yuǎn)不要在原子上下文中進(jìn)入休眠
- 如果代碼在擁有信號(hào)量時(shí)休眠,任何其他等待該信號(hào)量的線(xiàn)程也會(huì)休眠,因此任何擁有信號(hào)量而休眠的代碼必須很短,并且還要確保擁有信號(hào)量并不會(huì)阻塞喚醒我們自己的那個(gè)進(jìn)程
- 當(dāng)我們被喚醒時(shí),我們永遠(yuǎn)無(wú)法知道休眠了多長(zhǎng)時(shí)間,或者休眠期間都發(fā)生了些什么事情
- 在Linux中,一個(gè)等待隊(duì)列通過(guò)一個(gè)“等待隊(duì)列頭(wait queue head)”來(lái)管理
- <linux/wait.h>
- wait_queue_head_t
- DECLARE_WAIT_QUEUE_HEAD(name);
- wait_queue_head_t my_queue;
- init_waitqueue_head(&my_queue);
- 簡(jiǎn)單休眠
- wait_event(queue, condition);
- wait_event_interruptible(queue, condition);
- wait_event_timeout(queue, condition, timeout);
- wait_event_interruptible_timeout(queue, condition, timeout);
- void wake_up(wait_queue_head_t *queue);
- void wake_up_interruptible(wait_queue_head_t *queue);
- 阻塞和非阻塞型操作
- 顯式的非阻塞I/O由filp->f_flags中的O_NONBLOCK標(biāo)志決定
- <linux/fcntl.h>
- 在執(zhí)行阻塞型操作的情況下,應(yīng)該實(shí)現(xiàn)下列動(dòng)作以保持和標(biāo)準(zhǔn)語(yǔ)義一致
- 如果一個(gè)進(jìn)程調(diào)用了read但是還沒(méi)有數(shù)據(jù)可讀,此進(jìn)程必須阻塞
- 如果一個(gè)進(jìn)程調(diào)用了write但緩沖區(qū)沒(méi)有空間,此進(jìn)程必須阻塞,而且必須休眠在與讀取進(jìn)程不同的等待隊(duì)列上
- 高級(jí)休眠
- 進(jìn)程如何休眠
- 將進(jìn)程置于休眠的第一個(gè)步驟通常是分配并初始化一個(gè)wait_queue_t結(jié)構(gòu),然后將其加入到對(duì)應(yīng)的等待隊(duì)列
- 第二個(gè)步驟是設(shè)置進(jìn)程的狀態(tài),將其標(biāo)記為休眠
- <linux/sched.h>
- TASK_RUNNING
- TASK_INTERRUPTIBLE
- TASK_UNINTERRUPUTIBLE
- void set_current_state(int new_state);
- 放棄處理器是最后的步驟,但在此之前還要做另外一件事情:我們必須首先檢查休眠等待的條件
- if (!condition) schedule();
- 手工休眠
- <linux/sched.h>
- DEFINE_WAIT(my_wait);
- wait_queue_t my_wait;
- init_wait(&my_wait);
- void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state);
- state是進(jìn)程的新?tīng)顟B(tài),應(yīng)該是TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
- schedule();
- void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);
- 獨(dú)占等待
- 當(dāng)某個(gè)進(jìn)程在等待隊(duì)列上調(diào)用wake_up時(shí),所有等待在該隊(duì)列上的進(jìn)程都被置為可運(yùn)行狀態(tài)
- 只會(huì)有一個(gè)被喚醒的進(jìn)程可以獲得期望的資源,而其他被喚醒的進(jìn)程只會(huì)再次休眠
- 一個(gè)獨(dú)占等待的行為和通常的休眠類(lèi)似,但有如下兩個(gè)重要的不同
- 等待隊(duì)列入口設(shè)置了WQ_FLAG_EXCLUSIVE標(biāo)志時(shí),則會(huì)被添加到等待隊(duì)列的尾部
- 在某個(gè)等待隊(duì)列上調(diào)用wake_up時(shí),它會(huì)在喚醒第一個(gè)具有WQ_FLAG_EXCLUSIVE標(biāo)志的進(jìn)程之后停止喚醒其他進(jìn)程
- 如果滿(mǎn)足下面兩個(gè)條件,在驅(qū)動(dòng)程序中利用獨(dú)占等待是值得考慮的
- 對(duì)某個(gè)資源存在嚴(yán)重競(jìng)爭(zhēng),并且喚醒單個(gè)進(jìn)程就能完整消耗該資源
- void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);
- 喚醒的相關(guān)細(xì)節(jié)
- <linux/wait.h>
- wake_up(wait_queue_head_t *queue);
- 喚醒隊(duì)列上所有非獨(dú)占等待的進(jìn)程,以及單個(gè)獨(dú)占等待者
- wake_up_interruptible(wait_queue_head_t *queue);
- 會(huì)跳過(guò)不可中斷休眠的那些進(jìn)程
- wake_up_nr(wait_queue_head_t *queue, int nr);
- 只會(huì)喚醒nr個(gè)獨(dú)占等待進(jìn)程
- wake_up_interruptible_nr(wait_queue_head_t *queue, int nr);
- 只會(huì)喚醒nr個(gè)獨(dú)占等待進(jìn)程
- wake_up_all(wait_queue_head_t *queue);
- wake_up_interruptible_all(wait_queue_head_t *queue);
- wake_up_interruptible_sync(wait_queue_head_t *queue);
- 舊的歷史:sleep_on
- void sleep_on(wait_queue_head_t *queue);
- void interruptible_sleep_on(wait_queue_head_t *queue);
- 永遠(yuǎn)不要使用它們
- poll和select
- poll、select和epoll系統(tǒng)調(diào)用
- poll、select和epoll的功能本質(zhì)上是一樣的:都允許進(jìn)程決定是否可以對(duì)一個(gè)或多個(gè)打開(kāi)的文件做非阻塞的讀取或?qū)懭?/li>
- select在BSD Unix中引入
- poll由System V引入
- unsigned int (*poll) (struct file *filp, poll_table *wait);
- poll_table結(jié)構(gòu),用于在內(nèi)核中實(shí)現(xiàn)poll、select及epool系統(tǒng)調(diào)用
- <linux/poll.h>
- void poll_wait(struct file *, wait_queue_head_t *, poll_table *);
- poll方法執(zhí)行的第二項(xiàng)任務(wù)是返回描述哪個(gè)操作可以立即執(zhí)行的位掩碼
- <linux/poll.h>
- POLLIN
- 如果設(shè)備可以無(wú)阻塞地讀取,就設(shè)置該位
- POLLRDNORM
- 如果“通常”的數(shù)據(jù)已經(jīng)就緒,可以讀取,就設(shè)置該位
- 一個(gè)可讀設(shè)備返回(POLLIN|POLLRDNORM)
- POLLRDBAND
- 這一位指示可以從設(shè)備讀取out-of-band的數(shù)據(jù)
- POLLPRI
- 可以無(wú)阻塞地讀取高優(yōu)先級(jí)的數(shù)據(jù)
- POLLHUP
- 當(dāng)讀取設(shè)備的進(jìn)程到達(dá)文件尾時(shí),驅(qū)動(dòng)程序必須設(shè)置POLLHUP位
- POLLERR
- POLLOUT
- 如果設(shè)備可以無(wú)阻塞地寫(xiě)入,就在返回值中設(shè)置該位
- POLLWRNORM
- 該位和POLLOUT的意義一樣,有時(shí)其實(shí)就是同一個(gè)數(shù)字
- 一個(gè)可寫(xiě)的設(shè)備將返回(POLLOUT|POLLWRNORM)
- POLLWRBAND
- 與POLLRDBAND類(lèi)似,這一位表示具有非零優(yōu)先級(jí)的數(shù)據(jù)可以被寫(xiě)入設(shè)備
- POLLRDBAND和POLLWRBAND只在與套接字相關(guān)的文件描述符中才是有意義的,設(shè)備驅(qū)動(dòng)程序通常用不到這兩個(gè)標(biāo)志
- 與read和write的交互
- 從設(shè)備讀取數(shù)據(jù)
- 如果輸入緩沖區(qū)有數(shù)據(jù),那么即使就緒的數(shù)據(jù)比程序所請(qǐng)求的少,并且驅(qū)動(dòng)程序保證剩下的數(shù)據(jù)馬上就能到達(dá),read調(diào)用仍然應(yīng)該以難以察覺(jué)的延遲立即返回
- 如果緩沖區(qū)中沒(méi)有數(shù)據(jù),那么默認(rèn)情況下read必須阻塞等待,直到至少有一個(gè)字節(jié)到達(dá);如果設(shè)置了O_NONBLOCK標(biāo)志,read應(yīng)立即返回,返回值是-EAGAIN。poll必須報(bào)告設(shè)備不可讀
- 如果已經(jīng)到達(dá)文件尾,read應(yīng)該立即返回0,此時(shí)poll應(yīng)該報(bào)告POLLHUP
- 向設(shè)備寫(xiě)數(shù)據(jù)
- 如果輸出緩沖區(qū)中有空間,則write應(yīng)該無(wú)延遲地立即返回,在這種情況下,poll報(bào)告設(shè)備可寫(xiě)
- 如果輸出緩沖區(qū)已滿(mǎn),那么默認(rèn)情況下write被阻塞直到有空間釋放;如果設(shè)置了O_NONBLOCK標(biāo)志,write應(yīng)立即返回,返回值是-EAGAIN。poll必須報(bào)告設(shè)備不可寫(xiě)
- 永遠(yuǎn)不要讓write調(diào)用在返回前等待數(shù)據(jù)的傳輸結(jié)束
- 刷新待處理輸出
- int (*fsync) (struct file *file, struct dentry *dentry, int datasync);
- 如果應(yīng)用程序需要確保數(shù)據(jù)已經(jīng)被傳送到設(shè)備上,就必須fsync方法
- datasync用于區(qū)分fsync和fdatasync這兩個(gè)系統(tǒng)調(diào)用
- 底層的數(shù)據(jù)結(jié)構(gòu)
- poll_table結(jié)構(gòu)是構(gòu)成實(shí)際數(shù)據(jù)結(jié)構(gòu)的一個(gè)簡(jiǎn)單封裝,包含poll_table_entry結(jié)構(gòu)的內(nèi)存頁(yè)鏈表
- 每個(gè)poll_table_entry結(jié)構(gòu)包括一個(gè)指向被打開(kāi)設(shè)備的struct file類(lèi)型的指針、一個(gè)wait_queue_head_t指針以及一個(gè)關(guān)聯(lián)的等待隊(duì)列入口
- 如果輪詢(xún)(poll)時(shí)沒(méi)有一個(gè)驅(qū)動(dòng)程序可以進(jìn)行非阻塞I/O,這個(gè)poll調(diào)用者就進(jìn)入休眠,直到休眠在其上的某個(gè)(或多個(gè))等待隊(duì)列喚醒它為止
- poll實(shí)現(xiàn)中的珍上有趣之處是,驅(qū)動(dòng)程序的poll方法在被調(diào)用者時(shí)為plol_table參數(shù)傳遞NULL指針。
- 在poll調(diào)用結(jié)束時(shí),poll_table結(jié)構(gòu)被重新分配,所有的先前添加到poll表中的等待隊(duì)列入口都會(huì)從這個(gè)表以及等待隊(duì)列中移除
- 異步通知
- 為了啟用文件袋異步通知機(jī)制,用戶(hù)程序必須執(zhí)行兩個(gè)步驟
- 首先,它們指定一個(gè)進(jìn)程作為文件的屬主,當(dāng)進(jìn)程使用fcntl系統(tǒng)調(diào)用執(zhí)行F_SETOWN命令時(shí),屬主進(jìn)程的進(jìn)程ID號(hào)就被保存在filp->f_owner中
- 然后,用戶(hù)程序必須在設(shè)備中設(shè)備FASYNC標(biāo)志,通過(guò)fcntl的F_SETFL命令完成的
- 例子
- struct sigaction action;
- memset(&action, 0, sizeof(action));
- action.sa_handler = sighandler;
- action.sa_flags = 0;
- sigaction(SIGIO, &action, NULL);
- fcntl(STDIN_FILENO, F_SETOWN, getpid());
- oflags = fcntl(STDIN_FILENO, F_GETFL);
- fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);
- 從驅(qū)動(dòng)程序的角度考慮
- 從內(nèi)核角度來(lái)看的詳細(xì)操作過(guò)程
- F_SETOWN被調(diào)用時(shí)對(duì)filp->f_owner賦值,此外什么也不做
- 在執(zhí)行F_SETFL啟用FASYNC時(shí),調(diào)用驅(qū)動(dòng)程序的fasync方法,只要filp->f_flags中的FASYNC標(biāo)志發(fā)生了變化,就會(huì)調(diào)用該方法,以便把這個(gè)變化通知驅(qū)動(dòng)程序,使其能正確響應(yīng)
- 當(dāng)數(shù)據(jù)到達(dá)時(shí),所有注冊(cè)為異步通知的進(jìn)程都會(huì)被發(fā)送一個(gè)SIGIO信號(hào)
- <linux/fs.h>
- struct fasync_struct
- int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);
- void kill_fasync(struct fasync_struct **fa, int sig, int band);
- sig通常是SIGIO
- band通常是POLL_IN,等價(jià)于POLLIN|POLLRDNORM
- 某些設(shè)備也針對(duì)設(shè)備可寫(xiě)入而實(shí)現(xiàn)了異步通知,在這種情況下,kill_fasync必須以POLL_OUT為模式調(diào)用
- 當(dāng)文件關(guān)閉時(shí)必須的調(diào)用fasync方法
- 定位設(shè)備
- llseek實(shí)現(xiàn)
- 如果設(shè)備操作未定義llseek方法,內(nèi)核默認(rèn)通過(guò)修改filp->f_pos而執(zhí)行定位
- 如果定位操作對(duì)應(yīng)于設(shè)備的一個(gè)物理操作,可能就需要提供自己的llseek方法
- 如果定位設(shè)備是沒(méi)有意義的,應(yīng)該在open方法中調(diào)用nonseekable_open,通知內(nèi)核設(shè)備不支持llseek
- int nonseekable_open(struct inode *inode, struct file *filp);
- 還應(yīng)該將file_operations結(jié)構(gòu)中的llseek方法設(shè)置為特殊的輔助函數(shù)no_llseek
- 設(shè)備文件的訪(fǎng)問(wèn)控制
- 獨(dú)享設(shè)備
- 最生硬的訪(fǎng)問(wèn)控制方法是一次只允許一個(gè)進(jìn)程打開(kāi)設(shè)備
- 限制每次只由一個(gè)用戶(hù)訪(fǎng)問(wèn)
- 需要兩個(gè)數(shù)據(jù)項(xiàng)
- 一個(gè)打開(kāi)計(jì)數(shù)
- 設(shè)備屬主的UID
- current->uid
- current->euid
- 替代EBUSY的阻塞型open
- 當(dāng)設(shè)備不能訪(fǎng)問(wèn)時(shí)返回一個(gè)錯(cuò)誤,通常這是最合理的方式,但有些情況下可能需要讓進(jìn)程等待設(shè)備
- 在打開(kāi)時(shí)復(fù)制設(shè)備
- 另一個(gè)實(shí)現(xiàn)訪(fǎng)問(wèn)控制的方法是,在進(jìn)程打開(kāi)設(shè)備時(shí)創(chuàng)建設(shè)備的不同私有副本
生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
------分隔線(xiàn)----------------------------
------分隔線(xiàn)----------------------------