阻塞操作是指,在執行設備操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。非阻塞操作的進程在不能進行設備操作時,並不掛起。被掛起的進程進入sleep狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。
在Linux驅動程序中,我們可以使用等待隊列(wait queue)來實現阻塞操作。wait queue很早就作為一個基本的功能單位出現在Linux內核裡了,它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠用於實現核心的異步事件通知機制。等待隊列可以用來同步對系統資源的訪問,上節中所講述Linux信號量在內核中也是由等待隊列來實現的。
下面我們重新定義設備"globalvar",它可以被多個進程打開,但是每次只有當一個進程寫入了一個數據之後本進程或其它進程才可以讀取該數據,否則一直阻塞。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <asm/semaphore.h>
MODULE_LICENSE("GPL");
#define MAJOR_NUM 254
static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);
struct file_operations globalvar_fops =
{
read: globalvar_read, write: globalvar_write,
};
static int global_var = 0;
static struct semaphore sem;
static wait_queue_head_t outq;
static int flag = 0;
static int __init globalvar_init(void)
{
int ret;
ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
if (ret)
{
printk("globalvar register failure");
}
else
{
printk("globalvar register success");
init_MUTEX(&sem);
init_waitqueue_head(&outq);
}
return ret;
}
static void __exit globalvar_exit(void)
{
int ret;
ret = unregister_chrdev(MAJOR_NUM, "globalvar");
if (ret)
{
printk("globalvar unregister failure");
}
else
{
printk("globalvar unregister success");
}
}
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
//等待數據可獲得
if (wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
flag = 0;
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
return sizeof(int);
}
static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off)
{
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
if (copy_from_user(&global_var, buf, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
flag = 1;
//通知數據可獲得
wake_up_interruptible(&outq);
return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);
編寫兩個用戶態的程序來測試,第一個用於阻塞地讀/dev/globalvar,另一個用於寫/dev/globalvar。只有當後一個對/dev/globalvar進行了輸入之後,前者的read才能返回。
讀的程序為:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd, num;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
read(fd, &num, sizeof(int)); //程序將阻塞在此語句,除非有針對globalvar的輸入
printf("The globalvar is %d\n", num);
//如果輸入是0,則退出
if (num == 0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure\n");
}
}
寫的程序為:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
main()
{
int fd, num;
fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
if (fd != - 1)
{
while (1)
{
printf("Please input the globalvar:\n");
scanf("%d", &num);
write(fd, &num, sizeof(int));
//如果輸入0,退出
if (num == 0)
{
close(fd);
break;
}
}
}
else
{
printf("device open failure\n");
}
}
打開兩個終端,分別運行上述兩個應用程序,發現當在第二個終端中沒有輸入數據時,第一個終端沒有輸出(阻塞),每當我們在第二個終端中給globalvar輸入一個值,第一個終端就會輸出這個值,如下圖:
第一個終端就會輸出這個值
關於上述例程,我們補充說一點,如果將驅動程序中的read函數改為:
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
//獲取信號量:可能阻塞
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
//等待數據可獲得:可能阻塞
if (wait_event_interruptible(outq, flag != 0))
{
return - ERESTARTSYS;
}
flag = 0;
//臨界資源訪問
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
//釋放信號量
up(&sem);
return sizeof(int);
}