@上一篇介紹了linux阻塞與非阻塞的基本概念,以及應用程式的小demo和kernel層對應的api函數。那接下來就以執行個體來分析,如何在linux驅動層添加等待隊列和輪詢的方法,以及差別。
**
一:簡介
**
在linux驅動中,存在很常見的兩種裝置通路模式,是以在編寫驅動的時候,一定要考慮到阻塞和非阻塞。這樣做有以下好處
1. linux驅動标準的的寫法,讓你寫的驅動正式,拿的出手,也能鍛煉個人的規劃能力、程式設計能力、思維能力;
2. 提高個人的審美能力,當你去看一個驅動,裡面的各種架構,元件的交叉的邏輯之美,你會不禁贊歎,linux核心原來是這樣,會有一種恍然大悟的感覺;
3.以上兩點均是個人見解,阻塞和非阻塞有它存在的意義:針對不同的驅動裝置,應運而生,提高驅動對裝置資源通路的及時性,有效性,提高cpu的使用效率;
4.在應用程式對具體裝置進行程式設計時,加阻塞和非阻塞對cpu的使用率,占有率是有巨大的影響。
舉個例子:我們編寫一個應用程式,讀寫按鍵的狀态。在應用程式中,在while中輪詢通路裝置節點。如果應用中不加入阻塞方式,驅動中不加載阻塞方式,你應用程式在執行時,可以top看一下,執行檔案在系統中占用cpu的使用率,接近100%,這樣極大影響系統的整體性能,也非常不合理。加入阻塞方式,使用率會幾乎為0%,這樣才是我們尋求的正确方式。
**
二:等待隊列
**
1. 執行個體分析:
以kernel/usb/class/usblp.c 為例,在probe中init rwait wwrite 等待隊列頭,注冊裝置類—>裝置類裡加入裝置檔案操作接口—>在接口裡添加檔案poll屬性—>編寫對應的poll函數,當應用程式讀通路時,宏定義一個read等待隊列,如果沒有資料可讀,切換狀态進入休眠狀态,等待喚醒;如果可讀,設定狀态為runing,删除read等待隊列。寫同樣,如下:
#include <linux/poll.h>
struct usblp {
......
wait_queue_head_t rwait, wwait;
......
};
static void usblp_bulk_read(struct urb *urb)
{
......
//如果有資料可讀,就喚醒read隊列
wake_up(&usblp->rwait);
......
}
static void usblp_bulk_write(struct urb *urb)
{
......
//如果有資料可寫,就喚醒write隊列
wake_up(&usblp->wwait);
......
}
/* No kernel lock - fine */
static __poll_t usblp_poll(struct file *file, struct poll_table_struct *wait)
{
__poll_t ret;
unsigned long flags;
struct usblp *usblp = file->private_data;
/* Should we check file->f_mode & FMODE_WRITE before poll_wait()? */
poll_wait(file, &usblp->rwait, wait);
poll_wait(file, &usblp->wwait, wait);
spin_lock_irqsave(&usblp->lock, flags);
ret = ((usblp->bidir && usblp->rcomplete) ? EPOLLIN | EPOLLRDNORM : 0) |
((usblp->no_paper || usblp->wcomplete) ? EPOLLOUT | EPOLLWRNORM : 0);
spin_unlock_irqrestore(&usblp->lock, flags);
return ret;
}
static ssize_t usblp_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
......
if ((rv = usblp_wwait(usblp, !!(file->f_flags & O_NONBLOCK))) < 0)
goto raise_wait;
......
wake_up(&usblp->wwait);
/*
* Step 2: Wait for transfer to end, collect results.
*/
rv = usblp_wwait(usblp, !!(file->f_flags&O_NONBLOCK));
......
}
static int usblp_wwait(struct usblp *usblp, int nonblock)
{
DECLARE_WAITQUEUE(waita, current);//建立一個等待隊列
int rc;
int err = 0;
//如果不可write,就将目前任務添加到wwait中寶股份,
add_wait_queue(&usblp->wwait, &waita);
for (;;) {
if (mutex_lock_interruptible(&usblp->mut)) {
rc = -EINTR;
break;
}
//設定可以被TASK_INTERRUPTIBLE 信号打斷。
set_current_state(TASK_INTERRUPTIBLE);
rc = usblp_wtest(usblp, nonblock);
mutex_unlock(&usblp->mut);
if (rc <= 0)
break;
//進行任務切換,目前程序進入睡眠狀态
if (schedule_timeout(msecs_to_jiffies(1500)) == 0) {
if (usblp->flags & LP_ABORT) {
err = usblp_check_status(usblp, err);
if (err == 1) { /* Paper out */
rc = -ENOSPC;
break;
}
} else {
/* Prod the printer, Gentoo#251237. */
mutex_lock(&usblp->mut);
usblp_read_status(usblp, usblp->statusbuf);
mutex_unlock(&usblp->mut);
}
}
}
//如果可write,将目前任務狀态設定為running,調用remove_wait_queue将程序從等待隊列中删除
set_current_state(TASK_RUNNING);
remove_wait_queue(&usblp->wwait, &waita);
return rc;
}
static int usblp_rwait_and_lock(struct usblp *usblp, int nonblock)
{
DECLARE_WAITQUEUE(waita, current);
int rc;
add_wait_queue(&usblp->rwait, &waita);
for (;;) {
if (mutex_lock_interruptible(&usblp->mut)) {
rc = -EINTR;
break;
}
set_current_state(TASK_INTERRUPTIBLE);
if ((rc = usblp_rtest(usblp, nonblock)) < 0) {
mutex_unlock(&usblp->mut);
break;
}
if (rc == 0) /* Keep it locked */
break;
mutex_unlock(&usblp->mut);
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(&usblp->rwait, &waita);
return rc;
}
static const struct file_operations usblp_fops = {
.owner = THIS_MODULE,
.read = usblp_read,
.write = usblp_write,
.poll = usblp_poll,
.unlocked_ioctl = usblp_ioctl,
.compat_ioctl = usblp_ioctl,
.open = usblp_open,
.release = usblp_release,
.llseek = noop_llseek,
};
static struct usb_class_driver usblp_class = {
.name = "lp%d",
.devnode = usblp_devnode,
.fops = &usblp_fops,
.minor_base = USBLP_MINOR_BASE,
};
static int usblp_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
......
init_waitqueue_head(&usblp->rwait);
init_waitqueue_head(&usblp->wwait);
......
retval = usb_register_dev(intf, &usblp_class);
......
return 0;
}
static void usblp_disconnect(struct usb_interface *intf)
{
......
wake_up(&usblp->wwait);
wake_up(&usblp->rwait);
......
}
static struct usb_driver usblp_driver = {
.name = "usblp",
.probe = usblp_probe,
.disconnect = usblp_disconnect,
.suspend = usblp_suspend,
.resume = usblp_resume,
.id_table = usblp_ids,
.supports_autosuspend = 1,
};
......
2.注意事項
a.将任務或者程序加入到等待隊列頭;
b.在合适的點喚醒等待隊列,一般在中斷中。
三:輪詢
**
執行個體分析:
以kernel/usb/misc/ldusb.c 為例,在probe中init read write 等帶隊列,注冊裝置類—>裝置類裡加入裝置檔案操作接口—>在接口裡添加檔案poll屬性—>編寫對應的poll函數,當應用程式讀寫通路時,判斷是否為非阻塞通路,根據poll函數pollin狀态,就會調用相關的wake up函數,實作資料通路,實作非阻塞式方式。
#include <linux/poll.h>//kernel輪詢頭檔案
/* Structure to hold all of our device specific stuff */
struct ld_usb {
......
wait_queue_head_t read_wait; //讀隊列頭
wait_queue_head_t write_wait; //寫隊列頭
......
};
/**
* ld_usb_write
*/
static ssize_t ld_usb_write(struct file *file, const char __user *buffer,
size_t count, loff_t *ppos)
{
......
/* wait until previous transfer is finished */
//判斷是否為非阻塞式通路,如果是write有效,沒有傳回-EAGAIN。
if (dev->interrupt_out_busy) {
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
goto unlock_exit;
}
//同read
retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy);
if (retval < 0) {
goto unlock_exit;
}
}
......
}
/**
* ld_usb_read
*/
static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
loff_t *ppos)
{
......
//判斷是否為非阻塞式通路,如果是read有效,沒有傳回-EAGAIN。
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
goto unlock_exit;
}
//加入等待隊列,等待喚醒,也就是有資料讀,實作非阻塞read
retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done);
......
};
/**
* ld_usb_poll
* 用于處理非阻塞通路函數
* 參數file:要打開的裝置檔案
* 參數wait:等待清單(poll_table)
*/
static __poll_t ld_usb_poll(struct file *file, poll_table *wait)
{
struct ld_usb *dev;
__poll_t mask = 0;
dev = file->private_data;
if (!dev->intf)
return EPOLLERR | EPOLLHUP;
//将等待隊列頭加到poll_table中
poll_wait(file, &dev->read_wait, wait);
poll_wait(file, &dev->write_wait, wait);
if (dev->ring_head != dev->ring_tail) //如果有資料,就傳回EPOLLIN,表示有資料
mask |= EPOLLIN | EPOLLRDNORM;
if (!dev->interrupt_out_busy)
mask |= EPOLLOUT | EPOLLWRNORM;
}
/* file operations needed when we register this driver */
static const struct file_operations ld_usb_fops = {
.owner = THIS_MODULE,
.read = ld_usb_read,
.write = ld_usb_write,
.open = ld_usb_open,
.release = ld_usb_release,
.poll = ld_usb_poll,
.llseek = no_llseek,
};
/*
* usb class driver info in order to get a minor number from the usb core,
* and to have the device registered with the driver core
*/
static struct usb_class_driver ld_usb_class = {
......
.fops = &ld_usb_fops,
......
};
static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
......
//初始化read,write等待隊列頭
init_waitqueue_head(&dev->read_wait);
init_waitqueue_head(&dev->write_wait);
//注冊類,實作poll,供應用程式通路
......
retval = usb_register_dev(intf, &ld_usb_class);
......
}
/**
* ld_usb_disconnect
*
* Called by the usb core when the device is removed from the system.
*/
static void ld_usb_disconnect(struct usb_interface *intf)
{
......
usb_deregister_dev(intf, &ld_usb_class);
......
//喚醒poller
wake_up_interruptible_all(&dev->read_wait);
wake_up_interruptible_all(&dev->write_wait);
......
};
/* usb specific object needed to register this driver with the usb subsystem */
static struct usb_driver ld_usb_driver = {
.name = "ldusb",
.probe = ld_usb_probe,
.disconnect = ld_usb_disconnect,
.id_table = ld_usb_table,
};
module_usb_driver(ld_usb_driver);
四:差別
a.等待隊列:在read、write函數中,初始化化一個等待隊列,并且實作狀态切換,進入睡眠狀态。
b.輪詢:在read、write中判斷函數是否為非阻塞式通路,在poll函數根據pollin狀态喚醒等待隊列,讀寫資料。