嵌入式之Linux驅(qū)動(六)

姓名:鄭煜爍? 學(xué)號:19029100010? 學(xué)院:電子工程學(xué)院

轉(zhuǎn)自:https://blog.csdn.net/u012142460/article/details/79046234

【嵌牛導(dǎo)讀】Linux中的阻塞操作和非阻塞操作以及底層邏輯

【嵌牛鼻子】設(shè)備驅(qū)動中的阻塞與非阻塞IO

【嵌牛提問】阻塞模式還是非阻塞模式如何區(qū)分

【嵌牛正文】

我們在Linux學(xué)習(xí)(二十三)IO模型中了解了LINUX中IO模型固额,IO模型最簡單的可以分為阻塞IO和非阻塞IO饥瓷。并且學(xué)習(xí)了一個用如何使用阻塞操作和非阻塞操作。而應(yīng)用層之所以能實(shí)現(xiàn)阻塞操作和非阻塞操作,都是因?yàn)榈讓訉?shí)現(xiàn)了阻塞操作和非阻塞操作。我們這一節(jié)就來看看底層是如何實(shí)現(xiàn)的障癌。

阻塞操作是指在執(zhí)行設(shè)備操作時框舔,若不能獲得資源,則掛起進(jìn)程直到滿足可操作的條件后再進(jìn)行操作啊片。而非阻塞操作的進(jìn)程在不能進(jìn)行設(shè)備操作時只锻,并不掛起,要么放棄紫谷,要么不停地查詢齐饮,直到可以操為止。

舉個很簡單的例子笤昨,要從串口讀數(shù)據(jù)read(fd,&buf,1);如果此時串口沒有數(shù)據(jù)如何處理呢祖驱?應(yīng)用層在打開串口設(shè)備時,可以設(shè)置該設(shè)備是阻塞操作還是非阻塞操作的open("/dev/ttyS1",O_RDWR | O_NONBLOCK);? O_NONBLOCK代表非阻塞瞒窒,沒有這一項(xiàng)代表阻塞模式捺僻。應(yīng)用層提供了阻塞和非阻塞模式,很顯然根竿,底層的read函數(shù)就要實(shí)現(xiàn)阻塞和非阻塞兩種情況陵像。

文件結(jié)構(gòu)體指針struct file 中變量f_flags來表示該設(shè)備是阻塞模式還是非阻塞模式。

等待隊(duì)列

在linux驅(qū)動程序中寇壳,可以使用等待隊(duì)列來實(shí)現(xiàn)阻塞進(jìn)程的喚醒醒颖。

1、定義等待隊(duì)列頭部

wait_queue_head_t? my_queue;

2壳炎、初始化等待隊(duì)列頭部

init_waitqueue_head(&my_queue);

DECLARE_WAIT_QUEUE_HEAD(name) 這個可以作為定義并初始化等待隊(duì)列頭部

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)? //? queue? 等待隊(duì)列腰耙, condition 喚醒條件, 可以是一個表達(dá)式

wait_event_interruptible(queue, condition)? // 等待事件铲球,可以被中斷所打斷

wait_event_timeout(queue, condition, timeout)//等待事件發(fā)生 超時可自動喚醒

wait_event_interruptible_timeout(queue, condition, timeout)//等待事件挺庞,可以被中斷所打斷,超時可自動喚醒

上述四個函數(shù)都屬于等待事件函數(shù)稼病,condition是判斷條件选侨,如果條件成立,則不休眠然走,例如應(yīng)用層讀串口數(shù)據(jù)援制,串口此時有數(shù)據(jù),也就是條件成立芍瑞。那正常給應(yīng)用即可晨仑。如果無數(shù)據(jù),條件不成立,就會進(jìn)入休眠洪己,interruptible表示在休眠期間可以被信號打斷妥凳,timeout表示休眠期間,若超時就會被喚醒码泛。

6猾封、喚醒隊(duì)列

void wake_up(wait_queue_head_t *queue);? // 喚醒等待隊(duì)列,對應(yīng)wait_event或wait_event_timeout

void wake_up_interruptible(wait_queue_head_t *queue); //喚醒等待隊(duì)列 對應(yīng)wait_event_interruptible或wait_event_interruptible_timeout噪珊。

我們先來看看等待如何使用把

1晌缘、定義初始化隊(duì)列頭

2、痢站、等待事件磷箕,條件不滿足,阻塞(切換到其他進(jìn)程)

3阵难、被喚醒后繼續(xù)執(zhí)行

這幾步的實(shí)現(xiàn)有手動和自動兩種方式岳枷,自動的更簡單一些。我們來介紹一下自動方式呜叫,在模塊初始化中初始化等待隊(duì)列空繁,read函數(shù)中,判斷一下是否有數(shù)據(jù)朱庆,無數(shù)據(jù)則阻塞等待盛泡。write函數(shù)中,寫完成后娱颊,喚醒等待隊(duì)列

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/cdev.h>

#include <linux/fs.h>

#include <linux/slab.h>

#include <linux/device.h>

#include <asm/atomic.h>

#include <linux/spinlock.h>

#include <linux/semaphore.h>

#include <asm/uaccess.h>

#include <linux/wait.h>

#include <linux/sched.h>

MODULE_LICENSE("GPL");

dev_t devno;

int major = 0;

int minor = 0;

int count = 1;

#define? KMAX? 1024

char kbuf[KMAX] = {};

int counter = 0;? //用它記錄kbuf中實(shí)際存儲的字節(jié)數(shù)量

struct cdev *pdev;

struct class * pclass;

struct device * pdevice;

struct semaphore? sem_r;

struct semaphore? sem_w;

wait_queue_head_t? wq;? //創(chuàng)建一個等待隊(duì)列頭? +++++++++++++++++++++++++++++++++++

int demo_open(struct inode * inodep, struct file * filep)

{

printk("%s,%d\n", __func__, __LINE__);

return 0;

}

int demo_release(struct inode *inodep, struct file *filep)

{

printk("%s,%d\n", __func__, __LINE__);

return 0;

}

// read(fd, buff, N) --> ... --> demo_read()

ssize_t demo_read(struct file * filep, char __user * buffer, size_t size, loff_t * offlen)

{

// 應(yīng)用程序傲诵,讀數(shù)據(jù)時,發(fā)現(xiàn)沒有資源箱硕,那么此時阻塞等代

if(counter == 0)

{

if(filep->f_flags & O_NONBLOCK)? //設(shè)備是阻塞還是非阻塞模式++++++++++++++++++++++++

{

return -EAGAIN;

}

if(wait_event_interruptible(wq,counter != 0))? //阻塞模式拴竹,是否有數(shù)據(jù)可讀+++++++++++++++

{

return -ERESTARTSYS;

}

}

down_interruptible(&sem_r);

if(size > counter)

{

size = counter;

}

if(copy_to_user(buffer, kbuf, size) != 0)

{

printk("Failed to copy_to_user.\n");

return -1;

}

counter = 0;

up(&sem_w);

return size;

}

// write(fd, buff, n) --> ... --> demo_write();

ssize_t demo_write(struct file *filep, const char __user *buffer, size_t size, loff_t * offlen)

{

down_interruptible(&sem_w);

if(size > KMAX)

{

return -ENOMEM;

}

if(copy_from_user(kbuf, buffer,size) != 0)

{

printk("Failed to copy_from_user.\n");

return -1;

}

printk("kbuf:%s\n", kbuf);

counter = size;

up(&sem_r);

// 喚醒等待隊(duì)列

wake_up_interruptible(&wq);? //寫入了數(shù)據(jù),fifo不為空剧罩,可以喚醒讀中的等待隊(duì)列++++++++++++++++++++++++

return size;

}

struct file_operations? fops = {

.owner =THIS_MODULE,

.open = demo_open,

.release = demo_release,

.read = demo_read,

.write = demo_write,

};

static int __init demo_init(void)

{

int ret = 0;

printk("%s,%d\n", __func__, __LINE__);

ret = alloc_chrdev_region(&devno,minor,count, "xxx");

if(ret)

{

printk("Failed to alloc_chrdev_region.\n");

return ret;

}

printk("devno:%d , major:%d? minor:%d\n", devno, MAJOR(devno), MINOR(devno));

pdev = cdev_alloc();

if(pdev == NULL)

{

printk("Failed to cdev_alloc.\n");

goto err1;

}

cdev_init(pdev, &fops);

ret = cdev_add(pdev, devno, count);

if(ret < 0)

{

? ? printk("Failed to cdev_add.");

goto err2;

}

pclass = class_create(THIS_MODULE, "myclass");

if(IS_ERR(pclass))

{

printk("Failed to class_create.\n");

ret = PTR_ERR(pclass);

goto err3;

}

pdevice = device_create(pclass, NULL, devno, NULL, "hello");

if(IS_ERR(pdevice))

{

printk("Failed to device_create.\n");

ret = PTR_ERR(pdevice);

goto err4;

}

sema_init(&sem_r, 0);

sema_init(&sem_w, 1);

// 初始化等待隊(duì)列+++++++++++++++++++++++++++++++++++++++++

init_waitqueue_head(&wq);

return 0;

err4:

class_destroy(pclass);

err3:

cdev_del(pdev);

err2:

kfree(pdev);

err1:

unregister_chrdev_region(devno, count);

return ret;

}

static void __exit demo_exit(void)

{

printk("%s,%d\n", __func__, __LINE__);

device_destroy(pclass, devno);

class_destroy(pclass);

cdev_del(pdev);

kfree(pdev);

unregister_chrdev_region(devno, count);

}

module_init(demo_init);

module_exit(demo_exit);

應(yīng)用層栓拜,一個進(jìn)程讀,一個進(jìn)程寫惠昔,寫進(jìn)程在寫數(shù)據(jù)之前先延遲5s幕与,此時讀進(jìn)程是無法讀取數(shù)據(jù)的,直到寫數(shù)據(jù)完成舰罚,講讀進(jìn)程喚醒纽门。大家可是試一下將該設(shè)備文件改成非阻塞模式薛耻,看看會有什么不同营罢。

#include <stdio.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>

#include <string.h>

#define? N? 128

int main(int argc, const char *argv[])

{

int fd;

char buf[N] = {};

char wbuf[N] = "This is a write test.";

pid_t pid;

//fd = open("/dev/hello", O_RDWR|O_NONBLOCK);

fd = open("/dev/hello", O_RDWR);

if(fd < 0)

{

perror("Failed to open.");

return -1;

}

else

{

printf("open success.\n");

}

if((pid = fork()) < 0)

{

perror("Failed to fork.");

return -1;

}

else if(pid == 0)

{

if(read(fd, buf, N) < 0)

{

perror("Failed to read");

return -1;

}

printf("buf:%s\n", buf);

}

else

{

sleep(5);

write(fd, wbuf, strlen(wbuf)+1);

printf("Wrote done.\n");

}

close(fd);

return 0;

}

我們來看一下內(nèi)核的處理過程,我們在自動模式中只定義了一個等待隊(duì)列頭,我們使用wait_event系列函數(shù)時饲漾,就會創(chuàng)建一個等待隊(duì)列元素DECLARE_WAITQUEUE(name, tsk)蝙搔,加入到等待隊(duì)列中。等待隊(duì)列結(jié)構(gòu)體都包含什么呢考传?我們來看看

struct __wait_queue {

unsigned int flags;

#define WQ_FLAG_EXCLUSIVE0x01

void *private;? ? ? ? //指到當(dāng)前進(jìn)程結(jié)構(gòu)體

wait_queue_func_t func;? //喚醒回調(diào)函數(shù)

struct list_head task_list;? // 一個循環(huán)雙鏈表

};

private指向當(dāng)前進(jìn)程結(jié)構(gòu)task_struct吃型,喚醒時知道時要喚醒哪一個進(jìn)程。

func :喚醒時的回調(diào)函數(shù)僚楞。

隊(duì)列插入完成后勤晚,如下圖。


來源 CSDN

這兩個變量的值就是在DECLARE_WAITQUEUE完成賦值的

#define DEFINE_WAIT_FUNC(name, function)\

wait_queue_t name = {\

.private= current, \? ? ? //設(shè)置為當(dāng)前進(jìn)程

.func = function,\? ? ? //回掉函數(shù)

.task_list= LIST_HEAD_INIT((name).task_list),\

}

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

current代表的就是當(dāng)前進(jìn)程泉褐,喚醒時的回調(diào)函數(shù)就是autoremove_wake_function赐写。

好,現(xiàn)在看看wait_event_interruptible

#define wait_event_interruptible(wq, condition) \

({ \

int __ret = 0;\

if (!(condition))\

__wait_event_interruptible(wq, condition, __ret);\

__ret; \

})

繼續(xù)向下追

#define __wait_event_interruptible(wq, condition, ret)\

do { \

DEFINE_WAIT(__wait);\? ? ? 初始化一個等待隊(duì)列元素

\

for (;;) { \

prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);\? //加入等待隊(duì)列設(shè)置進(jìn)程狀態(tài)

if (condition)\

break; \

if (!signal_pending(current)) {\

schedule();\? ? ? ? ? ? ? //進(jìn)程調(diào)度

continue; \

} \

ret = -ERESTARTSYS;\

break; \

} \

finish_wait(&wq, &__wait);\

} while (0)

對于wait_event和 wait_event_interruptible就是在prepare_to_wait中設(shè)置的參數(shù)TASK_INTERRUPTIBLE不一樣

void

prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)

{

unsigned long flags;

wait->flags &= ~WQ_FLAG_EXCLUSIVE;

spin_lock_irqsave(&q->lock, flags);

if (list_empty(&wait->task_list))

__add_wait_queue(q, wait);? ? ? ? //添加到等待隊(duì)列中

? ? ? set_current_state(state);? //修改當(dāng)前進(jìn)程狀態(tài)

spin_unlock_irqrestore(&q->lock, flags);

}

這個函數(shù)主要是 1膜赃、添加到等待隊(duì)列頭中挺邀,這里和添加/移除等待隊(duì)列實(shí)際上是一個東西

? ? ? ? ? ? ? ? ? ? ? 2、設(shè)置 一下當(dāng)前進(jìn)程狀態(tài)

執(zhí)行完prepare_to_wait后回到__wait_event_interruptible中跳座,判斷condition條件是否滿足端铛,直接break,然后執(zhí)行finish_wait(&wq, &__wait);還原進(jìn)程狀態(tài)疲眷。若不滿足禾蚕,則schedule()出讓CPU控制權(quán)。

這里還有一點(diǎn)咪橙,為什么這里用的for(;;)來完成進(jìn)程調(diào)度和休眠的呢夕膀?這里我們要理解一點(diǎn),喚醒是將等待隊(duì)列中的所有進(jìn)程都喚醒美侦,但是每個進(jìn)程設(shè)置的condition條件是不一樣的产舞,如果判斷到底是不是喚醒了當(dāng)前進(jìn)程呢?那就再判斷一下condition條件唄菠剩,如果確實(shí)滿足了易猫,那真的是當(dāng)前進(jìn)程被喚醒了,然后使用finish_wait結(jié)束休眠具壮。如果不滿足准颓,說明還需要繼續(xù)等待,再次調(diào)用schedule()出讓CPU控制權(quán)棺妓。(在這之前需要判斷一下誰把我喚醒的攘已,if (!signal_pending(current)),如果是被信號喚醒的怜跑,不用進(jìn)程調(diào)度样勃,直接返回一個錯誤碼)

上面是休眠和喚醒后的過程吠勘,那看看如果完成喚醒的。

#define wake_up_interruptible(x)__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

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) &&? //喚醒函數(shù)回調(diào)

(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

break;

}

}

還記得curr->func是什么峡眶,前面提到了剧防,這是喚醒回調(diào)函數(shù)。

執(zhí)行的是autoremove_wake_function

休眠時將當(dāng)前進(jìn)程加入到了等待隊(duì)列當(dāng)中辫樱,在喚醒時自然要將其從喚醒隊(duì)列當(dāng)中移除峭拘。

int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

int ret = default_wake_function(wait, mode, sync, key);

if (ret)

list_del_init(&wait->task_list);? //將當(dāng)前進(jìn)程從等待隊(duì)列當(dāng)中移除

return ret;

}

喚醒函數(shù)繼續(xù)

int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,

void *key)

{

return try_to_wake_up(curr->private, mode, wake_flags);

}

在這里將指定的進(jìn)程喚醒。

總結(jié)一下:

休眠的基本步驟:

1狮暑、當(dāng)前進(jìn)程加入等待隊(duì)列頭指定的隊(duì)列中

2鸡挠、修改當(dāng)前的進(jìn)程狀態(tài)

3、調(diào)度

幾個關(guān)鍵函數(shù):

wait_event_interruptible->>>>__wait_event_interruptible->>>DEFINE_WAIT->>>prepare_to_wait->>>schedule->>finish_wait

喚醒時的步驟:

1搬男、將指定進(jìn)程從等待隊(duì)列頭指定的隊(duì)列中刪除

2宵凌、修改指定進(jìn)程的狀態(tài)

3、喚醒指定的進(jìn)程

幾個關(guān)鍵函數(shù)

__wake_up->>>__wake_up_common->>>autoremove_wake_function->>>default_wake_function-->try_to_wake_up

————————————————

版權(quán)聲明:本文為CSDN博主「念念有余」的原創(chuàng)文章止后,遵循CC 4.0 BY-SA版權(quán)協(xié)議瞎惫,轉(zhuǎn)載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/u012142460/article/details/79046234

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末译株,一起剝皮案震驚了整個濱河市瓜喇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歉糜,老刑警劉巖乘寒,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匪补,居然都是意外死亡伞辛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門夯缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚤氏,“玉大人,你說我怎么就攤上這事踊兜「捅酰” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵捏境,是天一觀的道長于游。 經(jīng)常有香客問我,道長垫言,這世上最難降的妖魔是什么贰剥? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮筷频,結(jié)果婚禮上蚌成,老公的妹妹穿的比我還像新娘柱告。我一直安慰自己,他們只是感情好笑陈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著葵袭,像睡著了一般涵妥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坡锡,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天蓬网,我揣著相機(jī)與錄音,去河邊找鬼鹉勒。 笑死帆锋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的禽额。 我是一名探鬼主播锯厢,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脯倒!你這毒婦竟也來了实辑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤藻丢,失蹤者是張志新(化名)和其女友劉穎剪撬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悠反,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡残黑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斋否。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梨水。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖茵臭,靈堂內(nèi)的尸體忽然破棺而出冰木,到底是詐尸還是另有隱情,我是刑警寧澤笼恰,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布踊沸,位于F島的核電站,受9級特大地震影響社证,放射性物質(zhì)發(fā)生泄漏逼龟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一追葡、第九天 我趴在偏房一處隱蔽的房頂上張望腺律。 院中可真熱鬧奕短,春花似錦、人聲如沸匀钧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽之斯。三九已至日杈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佑刷,已是汗流浹背莉擒。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘫絮,地道東北人涨冀。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像麦萤,于是被迫代替她去往敵國和親鹿鳖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容