阻塞操作是指在執(zhí)行設(shè)備操作時(shí)若不能獲得資源,則掛起進(jìn)程,直到滿足可操作的條件后再進(jìn)行操作。被掛起的進(jìn)程進(jìn)入休眠狀態(tài),被從調(diào)度器的運(yùn)行隊(duì)列移走,直到等待條件被滿足。而非阻塞操作的進(jìn)程不能進(jìn)行設(shè)備時(shí)并不掛起,它或者放棄,或者不停地查詢,直到可以進(jìn)行操作為止。
阻塞的進(jìn)程會(huì)進(jìn)入睡眠狀態(tài),必須有一個(gè)地方能夠喚醒休眠的進(jìn)程。喚醒進(jìn)程的地方最大可能發(fā)生在中斷里面,因?yàn)橛布Y源獲得的同時(shí)往往伴隨一個(gè)中斷。
等待隊(duì)列
可以使用等待隊(duì)列(wait queue)來喚醒阻塞進(jìn)程。wait queue很早就作為一個(gè)基本的功能單位出現(xiàn)在linux內(nèi)核里了,它以隊(duì)列為基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),與進(jìn)程調(diào)度機(jī)制緊密結(jié)合,能夠用于實(shí)現(xiàn)內(nèi)核中的異步通知機(jī)制。等待隊(duì)列可以用來同步對(duì)系統(tǒng)資源的訪問,信號(hào)量在內(nèi)核中也依賴等待隊(duì)列來實(shí)現(xiàn)。
1.定義等待隊(duì)列頭
wait_queue_head my_queue;
2. 初始化等待隊(duì)列頭
init_waitqueue_head(&my_queue);
DELCARE_WAIT_QUEUE_HEAD(name)/定義并初始化
3. 定義等待隊(duì)列
DECLARE_WAITQUEUE(name,tsk)
4. 添加/移除等待隊(duì)列
void fastcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
5. 等待事件
wait_event(queue,condition);
wait_event_interruptible(queue,condition);
wait_event_timeout(queue,timeout);
wait_event_interruptible_timeout(queue,condition,timeout);
wait_event()代碼:
#define wait_event(wq,condition)
do {
if (condition)
break;
__wait_event(wq,condition);//添加到等待隊(duì)列
} while (0)
#define __wait_event(wq,condition)
do {
DEFINE_WAIT(__wait);
for (;;) {
prepare_to_wait(&wq,&__wait,TASK_UNINTERRUPTIBLE);
if (condition)
break;
schedule();//放棄cpu
}
finsh_wait(&wq,&__wait);
} while(0)
void fastcall prepare_to_wait(wait_queue_head_t *q,wait_queue_t *wait,int state)
{
unsigned long flags;
wait->flags &= ~WQ_FLAG_EXCLUSIVE;
sping_lock_irqsave(&q->lock,flags);
if (list_empty(&wait->task_list))
__add_wait_queue(q,wait);//添加等待隊(duì)列
if (is_sync_wait(wait))
set_current_state(state);//改變當(dāng)前進(jìn)程的狀態(tài)為休眠
spin_unlock_irqrestore(&q->lock,flags);
}
void fastcall finsh_wait(wait_queue_head_t *q,wait_queue_t *wait)
{
unsigned long flags;
__set_current_state(TASK_RUNNING);//恢復(fù)當(dāng)前進(jìn)程的狀態(tài)為TASK_RUNING
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);
}
6. 喚醒隊(duì)列
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);
wake_up()喚醒wait_event()或wait_event_timeout(),wake_up_interruptible()或wake_up_interruptible_timeout().wake_up()可喚醒處于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的進(jìn)程,而wake_up_interruptibel()只能喚醒處于TASK_INTERRUPTIBLE的進(jìn)程。
7.在等待隊(duì)列上睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);
sleep_on()將目前進(jìn)程的狀態(tài)置成TASK_UNINTERRUPTIBLE,并定義一個(gè)等待隊(duì)列,之后把它附屬到等待隊(duì)列頭q,直到資源可獲得,q引導(dǎo)的等待隊(duì)列被喚醒。
interruptible_sleep_on()與sleep()類似,其作用是將目前進(jìn)程的狀態(tài)置成TASK_INTERRUPTIBLE,并定義一個(gè)等待隊(duì)列,之后把它附屬到等待隊(duì)列頭q,直到資源可獲得的,q引導(dǎo)的等待隊(duì)列被喚醒或者收到信號(hào)。
sleep_on()與wake_up()成對(duì)使用,interruptible_sleep_on()與wake_up_interruptible()成對(duì)使用。
sleep_on():
void fastcall __sched sleep_on(wait_queue_head_t *q)
{
SLEEP_ON_VAR
/*
#define SLEEP_ONVAR
unsigned long flags;
wait_queue_t wait;
init_waitqueue_entry(&wait,current);*/
current->state = TASK_UNINTERRUPTIBLE
SLEEP_ON_HEAD
/*
#define SLEEP_ONHEAD
spin_lock_irqsave(&q->lock,flags);
__add_wait_queue(q,&wait);
sping_unlock(&q->lock,flags);*/
schedule();
SLEEP_ON_TALL
/*
#define SLEEP_ON_TALL
spin_lock_irq(&q->lock);
__remove_wait_queue(q,&wait);*/
spin_unlock_irqrestore(&q->lock,flags);*/
}
interruptible_sleep_on():
void fastcall __sched interruptible_sleep_on(wait_queue_head_t *q)
{
SLEEP_ON_VAR
current->state = TASK_INTERRUPTIBLE;
SLEEP_ON_HEAD
schedule();
SLEEP_ON_TALL
}
note:在內(nèi)核中使用set_current_state()或__add_current_queue()函數(shù)來實(shí)現(xiàn)目前進(jìn)程狀態(tài)的改變,直接采用current->state = TASK_INTERRUPTIBLE類似的賦值語句也是可行。通常而言,set_current_state()在任何環(huán)境下都可以使用,不會(huì)存在并發(fā)問題,但是效率要低于__add_current_queue().因此,許多設(shè)備驅(qū)動(dòng)并不調(diào)用sleep_on()或interruptible_sleep_on(),而是直接進(jìn)程狀態(tài)改變和切換。
輪詢操作
輪詢
使用非阻塞I/O的應(yīng)用程序通常會(huì)使用select()和poll()系統(tǒng)調(diào)用查詢?cè)O(shè)備是否可對(duì)設(shè)備進(jìn)行無阻塞的訪問。selecct()和poll()系統(tǒng)調(diào)用最終會(huì)引發(fā)設(shè)備驅(qū)動(dòng)中的poll()函數(shù)被執(zhí)行。
應(yīng)用程序中的輪詢編程
應(yīng)用程序最廣泛用到select()系統(tǒng)調(diào)用。
int select(int numfds,fd_set *reafds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
struct timeval
{
int tv_sec;//second
int tv_usec;//1/1000 second
}
FD_ZERO(fd_set *set);//清零set
FD_SET(int fd, fd_set *set);//加fd到set
FD_CLR(int fd, fd_set *set);//清除fd從set
FD_ISSET(int fd, fd_set *set);//判斷fd是否被置位
設(shè)備驅(qū)動(dòng)中的輪詢編程
設(shè)備驅(qū)動(dòng)中poll()函數(shù):
unsigned int (*poll)(struct file *filp, struct poll_table *watit);
struct poll_table 輪詢表。
此函數(shù),對(duì)可能引起設(shè)備文件狀態(tài)變化的等待隊(duì)列調(diào)用poo_wait()函數(shù),將對(duì)應(yīng)的等待隊(duì)列頭添加到poll_table; 返回表示是是否能對(duì)設(shè)備進(jìn)行無阻塞讀寫訪問的掩碼。
poll_wait()注冊(cè)等待隊(duì)列:
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);
只是把當(dāng)前進(jìn)程加入到wait等待隊(duì)列表中。
驅(qū)動(dòng)程序poll()返回設(shè)備資源可獲取狀態(tài),即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏位“或”的結(jié)果。
阻塞與非阻塞訪問是IO訪問的兩種I/O操作暫時(shí)不可進(jìn)行時(shí)會(huì)讓進(jìn)程睡眠。
在設(shè)備驅(qū)動(dòng)中阻塞IO一般基于等待隊(duì)列,等待隊(duì)列可用于同步驅(qū)動(dòng)中事件發(fā)生的先后順序。使用非阻塞IO的應(yīng)用程序也可借助輪詢函數(shù)來查詢?cè)O(shè)備是否能立即被訪問,用戶空間調(diào)用select()和poll()接口,設(shè)備驅(qū)動(dòng)提供poll()函數(shù),設(shè)備驅(qū)動(dòng)的poll()本身不會(huì)阻塞,但是poll()和select系統(tǒng)調(diào)用則會(huì)阻塞地等待文件描述集合中的至少一個(gè)可訪問或超時(shí)。