1. 程式人生 > >[Linux]阻塞與非阻塞(等待佇列、輪詢)機制

[Linux]阻塞與非阻塞(等待佇列、輪詢)機制

基本概念

阻塞指執行裝置操作時,不能獲得資源則掛起程序,被掛起的程序進入休眠,從排程器的進行佇列中移走。
非阻塞指在不能獲得資源的情況下,要麼放棄,要麼不停地查詢,直到可以操作。

等待佇列(Wait Queue)

Linux 中採用等待佇列來實現阻塞程序的喚醒。
等待佇列以佇列為基礎資料結構,結合程序排程機制,用來同步對系統資源的訪問。

1. 定義等待佇列頭

wait_queue_head_t my_queue;

2.初始化等待佇列頭

init_waitqueue_head(&my_queue);
//或者
DECLAR_WAIT_QUEUE_HEAD(name)

3.定義等待佇列元素

DECLARE_WAITQUEUE(name,tsk)

4.新增/移除等待佇列

//將等待佇列元素 wait 新增到等待佇列頭 q 指向的雙向連結串列中
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
//將等待佇列元素 wait 從由 q 頭部指向的連結串列中移除
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

5.等待事件

//等待佇列頭 queue 被喚醒
//condition 必須滿足,否則繼續阻塞
wait_event(queue,condition) wait_event_interruptible(queue, condition) //可以被訊號喚醒 wait_event_timeout(queue, condition, timeout) //timeout 為阻塞等待超時時間, jiffy 為單位 wait_event_interruptible_timeout(queue, condition, timeout)

6.喚醒佇列

void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *
queue);

上述操作會喚醒以 queue 作為等待佇列頭部的佇列中的所有程序。
wake_up 可以喚醒處於 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的程序。
wake_up_interruptible 只能喚醒處於 TASK_INTERRUPTIBLE 的程序。

7.在等待佇列上睡眠

sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);

輪詢

select()+poll() 或 epoll

/*
    readfds、writefds、 exceptfds 是監視的 讀、寫、異常處理的檔案描述符集合
    numfs 是需要檢查的號碼的最高值+1
    readfds 如果有任何檔案變得可讀  或者  writefds 如果有任何檔案變得可寫,select() 就會返回
*/
int select(int numfs, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
FD_ZERO(fd_set *set)  //清除
FD_SET(int fd, fd_set *set) //新增
FD_CLR(int fd, fd_set *set) //刪除
FD_ISSET(int fd,fd_set *set) //判斷是否被置為
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

但是我們一般用 epoll()

//1.  建立 epoll 控制代碼(返回檔案描述符 epfd),size 是核心要監聽的 fd 的個數
int epoll_create(int size);
//2.  監聽事件型別
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/* op:
 EPOLL_CTL_ADD 新增
 EPOLL_CTL_MOD 修改
 EPOLL_CTL_DEL  刪除
   event:
 EPOLLIN 可讀
 EPOLLOUT 可寫
 EPOLLPRI 緊急可讀(有 socket 帶外資料到來)
 EPOLLERR fd 發生錯誤
 EPOLLHUP fd 被結束通話
 EPOLLET 設定為邊緣觸發(高速模式:如果 fd 從未就緒變為就緒,核心通過 epoll 告訴使用者,一次就緒通知)
 EPOLLONESHOT 一次性監聽(監聽完後不再監聽)
*/
//3. 等待事件發生
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
events 輸入
maxevents 表示個數 < epoll_create 建立時的 size
timeout 超時時間 0 立即 -1 永久

裝置驅動中的 poll() 模板

見例項

例項

等待佇列

static ssize_t xxx_write(struct file *file, const char *buffer, size_t count, loff_t *ppos)
{
    ...
    DECLARE_WAITQUEUE(wait, current);   /* 定義等待佇列元素 */
    add_wait_queue( &xxx_wait , &wait); /* 新增元素 wait 到等待佇列 xxx_wait */

    /* 等待裝置緩衝區可寫 */
    do{
        avail = device_writable(...);
        if( avail < 0){
            if(file->f_flags & O_NONBLOCK)  //是非阻塞
            {
                ret = -EAGAIN;
                goto out;
            }
            __set_current_state(TASK_INTERRUPTIBLE); /* 改變程序狀態 */
            schedule();                         /* 排程其他程序執行 */
            if(signal_pending(current))         /* 如果是因為訊號喚醒*/
            {
                ret = -ERESTARTSYS;
                goto out;
            }
        }
    } while( avail < 0);

    /* 寫裝置緩衝區 */
    device_write(...);
out:
    remove_wait_queue(&xxx_wait, &wait);
    set_current_sate(TASK_RUNNING);
    return ret;
}

輪詢

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

#define FIFO_CLEAR 0x1
#define BUFFER_LEN 20
void main(void)
{
    int fd, num;
    char rd_ch[BUFFER_LEN];
    fd_set rfds, wfds;  /* 讀/寫檔案描述符集 */

    /* 以非阻塞方式開啟/dev/globalfifo裝置檔案 */
    fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
    if (fd != -1) {
        /* FIFO清0 */
        if (ioctl(fd, FIFO_CLEAR, 0) < 0)
            printf("ioctl command failed\n");

        while (1) {
            FD_ZERO(&rfds);                     //FD_ZERO 清除檔案描述符合集
            FD_ZERO(&wfds);
            FD_SET(fd, &rfds);                  //FD_SET 將 fd 加入合集中
            FD_SET(fd, &wfds);

            select(fd + 1, &rfds, &wfds, NULL, NULL);
            /* 資料可獲得 */
            if (FD_ISSET(fd, &rfds))
                printf("Poll monitor:can be read\n");
            /* 資料可寫入 */
            if (FD_ISSET(fd, &wfds))
                printf("Poll monitor:can be written\n");
        }
    } else {
        printf("Device open failure\n");
    }
}