Ioctl命令組成、阻塞IO實現、IO多路複用 >>Linux裝置驅動程式
阿新 • • 發佈:2019-01-05
我又來了,一天學習一點點,一天成長一點點就會很開心的說!
今天的狀態很好,況且還有好喝的咖啡陪伴著我,元氣滿滿喲!^. ^?
文章目錄
- [0x100]內容概述
- [0x200] 編寫ioctl的命令編號
- [0x300] 休眠與喚醒
- [0x400]其他 IO處理方式
[0x100]內容概述
- ioctl 命令編號組成
- 等待佇列 與 程序核心空間休眠
- IO多路複用呼叫與位掩碼作用
- 非同步I/O通知核心實現函式
[0x200] 編寫ioctl的命令編號
[0x210]命令編號組成
- NUMR : 命令順序編號;
- TYPE : 命令標識型別:通常需要查詢 ioctl-numbers.txt 來確定是否衝突;
- DIRT : 命令執行方向:拷貝到使用者空間 或者 從使用者空間拷貝,或者 雙向;
- SIZE : 使用者資料大小:通常用於校驗資料是否超容,核心不關心;
#include <asm-generic/ioctl.h>
/*參考 核心版本 3.4.39*/
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#define _IOC_SIZEBITS 14
#ifndef _IOC_DIRBITS
#define _IOC_DIRBITS 2
/*整體來說:總共 32位命令號組成*/
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) /*序列編號 = 8位 1 0000 0000-1=FFFF FFFF*/
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) /*型別編號 = 8位*/
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) /*資料容量 = 14位*/
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) /*讀寫標識 = 2位*/
#define _IOC_NRSHIFT 0 /*NR Bit_0~Bit_7*/
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) /*TYPE Bit_8~Bit_15*/
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) /*SIZE Bit_16~Bit_29*/
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) /*DIR Bit_30~Bit_31*/
[0x220]建立命令編號
/*封裝 讀寫指令*/
# define _IOC_NONE 0U
# define _IOC_WRITE 1U
# define _IOC_READ 2U
/*IOCMD 位移當前數值對應位置 dir 起始29 type 起始16 nr 起始8 size 起始0*/
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
/*核心空間的使用者資料檢測 是否大於0 且小於SIZESHIFT 正確返回資料大小,錯誤呼叫 */
extern unsigned int __invalid_size_argument_for_IOC;
#define _IOC_TYPECHECK(t) \
((sizeof(t) == sizeof(t[1]) && \
sizeof(t) < (1 << _IOC_SIZEBITS)) ? \
sizeof(t) : __invalid_size_argument_for_IOC)
/*建立命令種類起始序號*/
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
/*建立不同種類的命令編號 */
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
[0x230]這裡有個例子
#define PYM_START 't'
#define PYM_IOCMD _IO(PYM_START,0)
#define PYM_RDCMD _IOR(PYM_START,1,int)
#define PYM_WRCMD _IOW(PYM_START,2,int)
#define PYM_IRDWR _IORW(PYM_START,3,int)
[0x300] 休眠與喚醒
資料未就緒、緩衝區已滿、裝置未準備就緒等情況;
阻塞IO主要用於 針對以上條件無法執行裝置讀寫操作的情況,程序調用於核心空間休眠;
[0x310] 產生休眠的必要條件
- 概念 :由於無法達成下一步執行條件,當前程式碼自主放棄CPU 執行用於其他程序執行,等待固定事件再次呼叫;
- 規則1:休眠不能用於原子上下文,以及遮蔽中斷的程序中;
- 規則2:必須確定喚醒條件真實存在,才能執行休眠;
- 規則3:必須存在wait_queue 用來記錄等待喚醒的程序狀態資訊;
[0x311] 等待佇列相關資料結構
#include <linux/poll.h>
#include <linux/wait.h>
/*關聯等待佇列處理函式*/
typedef struct poll_table_struct {
poll_queue_proc _qproc;
unsigned long _key;
} poll_table;
/*將等待佇列入口與等待佇列處理函式,等待程序關聯起來*/
struct poll_wqueues {
poll_table
{ poll_queue_proc _qproc; /*等待佇列處理函式*/
unsigned long _key;
}pt;
struct poll_table_page /*等待佇列入口連結串列結構*/
{ struct poll_table_page * next;
struct poll_table_entry /*等待佇列入口的結構關聯等待佇列頭,和等待佇列項*/
{ struct file *filp; /*檔案描述符指標*/
unsigned long key;
wait_queue_t /*等待佇列項*/
{ unsigned int flags;
#define WQ_FLAG_EXCLUSIVE 0x01 /*獨立等待佇列項位*/
void *private;
wait_queue_func_t func; /*等待佇列項處理函式*/
struct list_head task_list;
}wait;
wait_queue_head_t /*等待佇列頭*/
{ spinlock_t lock;
struct list_head task_list;
}*wait_address;
} * entry;
struct poll_table_entry entries[0];
}; *table;
struct task_struct *polling_task; /*程序資訊結構*/
int triggered;
int error;
int inline_index;
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES]; /*輪詢入口項*/
};
typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
[0x320] 自動處理休眠與喚醒介面
[0x321]休眠巨集介面
- 置位條件等待 :condition == 0 則為休眠;
- 計時時限喚醒 :超時前等待條件喚醒,超時後忽略條件繼續執行;
- 裝置中斷喚醒 :非終止類中斷針對喚醒,否則一直阻塞等待
#include <linux/wait.h>
/*設定 休眠前必須設定 等待佇列頭*/
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
/*如果condistion 為 0 休眠 過載 __wait_event(),如果是1 結束休眠*/
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
/* condistion=0 休眠 否則由schedule_timeout(ret)返回放棄cpu時間
* ret = 小於0 或者等於 0 結束等待
*/
#define __wait_event_timeout(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
ret = schedule_timeout(ret); \
if (!ret) \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
/*通過 singal_pending 判斷有訊號處理非零,如果是中斷過載當前系統呼叫,是零則重新迴圈,直到資料已就緒*/
#define __wait_event_interruptible(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
[0x322]喚醒巨集介面
#include <linux/wait.h >
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
/*喚醒中斷等待除了獨佔等待的所有程序*/
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
/*喚醒中斷等待nr個獨佔等待*/
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
/*喚醒中斷等待的所有程序*/
#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
/*喚醒等待nr個獨佔等待*/
#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)
/*喚醒等待的所有程序*/
#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)
/*implement at kernel-3.4.39/kernel/sched/core.c*/
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);
}
[0x330]兩種建立休眠程序方式
[0x331]手動建立程序休眠過程
- 定義wait_queue_head_t,
- 定義wait_queue_t,
- 初始化等待佇列項 [init_wait(&my_wait_queue)];
- 新增等待佇列項並調整程序狀態TASK_INTERRUPTIBLE[prepare_to_wait()]
- 放棄 CPU 處理處理程式碼==[schedule()]==;
- 裝置返回為 TASK_RUNNING 清理等待佇列項 ,finish_wait()
- 檢查是否為訊號中斷 singal_pending(current);
- 執行讀寫處理
#include <linux/wait.h>
/* implement kernel_dir/kernel/wait.c*/
/*沒有WQ_FLAG_EXCLUSIVE 正常處理 */
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
/*自旋鎖 儲存當前irq 狀態 禁止irq 中斷*/
spin_lock_irqsave(&q->lock, flags);
/*檢查當前等待佇列連結串列是否為空*/
if (list_empty(&wait->task_list))
/*頭插*/
__add_wait_queue(q, wait);
/*設定程序狀態為傳入值*/
set_current_state(state);
/*自旋解鎖 恢復儲存irq 狀態 恢復irq 中斷*/
spin_unlock_irqrestore(&q->lock, flags);
}
/*優先處理擁有WQ_FLAG_EXCLUSIVE的程序 獨佔等待*/
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
unsigned long flags;
wait->flags |= WQ_FLAG_EXCLUSIVE;
/*自旋鎖 儲存當前irq 狀態 禁止irq 中斷*/
spin_lock_irqsave(&q->lock, flags);
/*檢查當前等待佇列連結串列是否為空*/
if (list_empty(&wait->task_list))
/*尾插*/
__add_wait_queue_tail(q, wait);
/*設定程序狀態為傳入值*/
set_current_state(state);
/*自旋解鎖 恢復儲存irq 狀態 恢復irq 中斷*/
spin_unlock_irqrestore(&q->lock, flags);
}
/*結束執行 清理工作*/
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);
if (!list_empty_careful(&wait->task_list)) {
spin_lock_irqsave(&q->lock, flags);
list_del_init(&wait->task_list);
spin_unlock_irqrestore(&q->lock, flags);
}
}
[0x332]自動建立休眠程序例項
- 配置等待佇列頭 DECLARE_WAIT_QUEUE_HEAD();
- 設定condition 為 0 休眠;
- 於copy_to_user() 之前 呼叫 wait_event_interruptible();
- 發現數據後執行 設定 condition 為 1,執行 wake_up_interruptible();
- 執行 copy_to_user()後,將condition 再次設定為0,週期以此類推;
struct scull_pipe
{
wait_queue_head_t inq,outq; //等待佇列頭 輸入 輸出
char *buffer,*end; // 使用者空間緩衝區開始 與結束位置;
char *rp,*wp; // 當前讀位置 、寫位置
struct semaphore sem; // 訊號量結構
struct cdev cdev; // 字元裝置結構體
};
static ssize_t scull_read(struct file *filep,char __user *buf,size_t count,loff_t f_ops)
{
/*使用檔案結構私有資料為結構體分配空間*/
struct scull_pipe * pym_dev = filep->private_data;
/*單入讀函式*/
if(down_interruptible(&pym_dev->sem))
return -ERESTARTSYS
/*寫指標等於讀指標表示沒有輸出,進入迴圈*/
while(pym_dev->wp == pym_dev->rp)
{
/*為了防止後續的問題返回,導致訊號量狀態消失,先解鎖*/
up(&pym_dev->sem);
/*檢查struct file 結構體中的檔案開啟標誌位 是否需要阻塞IO,如果不是退出重試,也可以返回 -EINVAL*/
if(filep->f_flags & O_NONBLOCK)
return -EAGAIN;
/*阻塞IO函式等待condition位= 1 或者中斷訊號*/
if(wait_event_interruptible(pym_dev->inq,(pym_dev->wp != pym_dev->rp)))
return -ERESTARTSYS;
/*有資料可讀,訊號量上鎖讀取資料*/
if(down_interruptible(&pym_dev->sem))
return -ERESTARTSYS;
}
/*寫指標移動有輸出,可以開始使用者可以開始讀*/
if(pym_dev->wp > pym_dev->rp)
/*如果wp-rp 位置 獲取需要寫入的資料位元組值,與核心空間的緩衝區比較,以小的數值為應用讀取位元組值*/
count = min(count,(pym_dev->wp - pym_dev->rp));
else
/*若wp已經釋放,則從當前緩衝區末尾獲取位元組值,與核心空間的緩衝區比較,以小的數值為應用讀取位元組值*/
count = min(count,(pym_dev->end - pym_dev->rp));
/*拷貝字元到使用者空間*/
if(copy_to_user(buf, pym_dev->rp,count))
{
up(&pym_dev->sem);
return -EFAULT;
}
/*移動讀指標到讀取位置後*/
pym_dev->rp += count;
/*判斷是否為緩衝區末尾*/
if(pym_dev->rp == pym_dev->end)
{
/*如果是 則返回指標到緩衝區頭部*/
pym_dev->rp = pym_dev->buffer;
}
up(&pym_dev->sem);
/*讀緩衝區存在空閒,喚醒其它寫函式執行*/
wake_up_interruptible(pym_dev->outq);
return count;
}
[0x400]其他 IO處理方式
[0x410] IO多路複用
- 概念定義:允許程序阻塞等待或者非阻塞多個IO資料流,直到其中任何一個檔案IO資料流準備就緒,執行相關操作;
- 函式原型:[unsigned int (* poll) (struct file *filp,poll_table *wait)] ;
- 應用函式: select、poll、epoll;
[0x411] IO 可用位掩碼
- 裝置可讀 或者 裝置可寫 : POLLIN | POLLRDNORM 或者 POLLOUT | POLLWRNORM;
- 程序到達裝置檔案末尾 : POLLHUP
- 標識裝置無阻塞可讀寫 : POLLERR
- out_of_band 裝置讀寫 : POLLRDBAND 或者 POLLWRBAND
[0x412]POLL讀時返回規則
- 阻塞讀:輸入緩衝區有空間 資料就緒= POLLIN,無資料可用= 休眠等待;
- 非阻塞:輸入緩衝區有空間 資料就緒= POLLIN,無資料可用= -EAGIN 或者 POLL返回裝置不可讀;
- 資料達到末尾= POLLHUP
[0x413]POLL寫時返回規則
- 輸出緩衝區有空間:資料就緒= POLLOUT;
- 阻塞寫 :輸出緩衝區無空間資料就緒= 寫函式將被阻塞;
- 非阻塞 :輸出緩衝區無空間資料就緒= 返回 -EAGIN 或者 -ENOSPC;
[0x414]輪詢入口表中新增等待佇列項
#include <linux/poll.h>
void poll_wait(struct file * filep,wait_queue_head_t *qh,poll_table *wq);
將多個檔案描述符放入等待佇列中,返回哪個描述符可以立即操作的位掩碼;
args 1 : 檔案描述符
args 2 : 等待佇列頭指標
args 3 : 等待佇列的結構指標
[0x420] 非同步通知
[0x421] 產生非同步通知的必要條件
- 概念 :訊號中斷提示處理的非同步I/O 方式替代阻塞和輪詢處理I/O的方式;
- 規則1:對應檔案結構體filep->f_flags 被設定為FASYNC 標誌;
- 規則2:定義struct fasync_struct ;
- 規則2:準備工作呼叫 fasync_helper(),形成一個 struct fasync_struct 結構連結串列,類似等待佇列的結構;
- 規則3:條件觸發呼叫 kill_fasync(),通知程序執行I/O;
[0x422] 相關函式處理與資料結構
#include <linux/fs.h>
/*implement kernel_dir/fs/fcntl.c */
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* 結構的單向連結串列 */
struct file *fa_file; /* 確定是否開啟了 非同步通知 f_flags */
struct rcu_head fa_rcu;
};
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
struct fasync_struct *new;
/*在核心空間分配地址*/
new = fasync_alloc();
if (!new)
return -ENOMEM;
/*執行連結串列插入*/
if (fasync_insert_entry(fd, filp, fapp, new)) {
fasync_free(new);
return 0;
}
return 1;
}
struct fasync_struct *
fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{
struct fasync_struct *fa, **fp;
/*如果fp指標存在,執行連結串列連線*/
spin_lock(&filp->f_lock);
spin_lock(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file != filp)
continue;
spin_lock_irq(&fa->fa_lock);
fa->fa_fd = fd;
spin_unlock_irq(&fa->fa_lock);
goto out;
}
/*首次進入 fp指標不存在,進行fp指標初始化*/
spin_lock_init(&new->fa_lock);
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
rcu_assign_pointer(*fapp, new);
filp->f_flags |= FASYNC;
out:
spin_unlock(&fasync_lock);
spin_unlock(&filp->f_lock);
/*返回struct fasync_struct */
return fa;
}