注:本文是《Linux設備驅動開發詳解:基於最新的Linux 4.0內核 by 宋寶華 》一書學習的筆記,大部分內容為書籍中的內容。
書籍可直接在微信讀書中查看:Linux設備驅動開發詳解:基於最新的Linux4.0內核-宋寶華-微信讀書 (qq.com)
Linux設備驅動中的異步通知和異步I/O
在設備驅動中使用異步通知可以使得在對設備訪問時,由驅動主動通知應用程序進行訪問。
使用非阻塞I/O的應用程序不需要輪訓設備是否可以訪問,而阻塞訪問可以被類似“中斷”的異步通知所取代。
除了異步通知以外,應用還可以在發起I/O請求后,立即返回。之后,再查詢I/O完成情況,或者I/O完成后被調回。這個過程叫作異步I/O。
1 異步通知簡介
異步通知:一旦設備就緒,則主動通知應用程序,應用程序不需要查詢設備狀態,較為准確的稱呼為“信號驅動的異步I/O”。
信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達。
阻塞I/O意味着一直等待設備可訪問后再訪問。
非阻塞I/O使用poll()意味着一直輪詢查看設備是否可訪問。
下圖呈現了阻塞I/O,結合輪詢的非阻塞I/O,以及基於SIGIO的異步通知在時間先后順序上的不同。

2 Linux異步通知編程
異步通知的核心就是信號,在arch\arm\include\uapi\asm\signal.h中定義了Linux使用的所有信號。
2.1 Linux信號
Linux中異步通知使用信號來實現,Linux中可用的信號及其定義如下:
| 信號 | 值 | 含義 |
|---|---|---|
| SIGHUP | 1 | 終端掛起或控制進程終止 |
| SIGINT | 2 | 終端中斷(Ctrl+C組合鍵) |
| SIGQUIT | 3 | 終端退出(Ctrl+\組合鍵) |
| SIGILL | 4 | 非法指令 |
| SIGTRAP | 5 | debug使用,有斷點指令產生 |
| SIGABRT | 6 | 由abort(3)發出的退出指令 |
| SIGIOT | 6 | IOT指令 |
| SIGBUS | 7 | 總線錯誤 |
| SIGFPE | 8 | 浮點運算錯誤 |
| SIGKILL | 9 | 殺死、終止進程 |
| SIGUSR1 | 10 | 用戶自定義信號1 |
| SIGSEGV | 11 | 段違例(無效的內存段) |
| SIGUSR2 | 12 | 用戶自定義信號2 |
| SIGPIPE | 13 | 向非讀管道寫入數據 |
| SIGALRM | 14 | 鬧鍾 |
| SIGTERM | 15 | 軟件終止 |
| SIGSTKFLT | 16 | 棧異常 |
| SIGCHLD | 17 | 子進程結束 |
| SIGCONT | 18 | 進程繼續 |
| SIGSTOP | 19 | 停止進程的執行,只是暫停 |
| SIGTSTP | 20 | 停止進程的運行(Ctrl+Z組合鍵) |
| SIGTTIN | 21 | 后台進程需要從終端讀取數據 |
| SIGTTOU | 22 | 后台進程需要向終端寫數據 |
| SIGURG | 23 | 有“緊急”數據 |
| SIGXCPU | 24 | 超過CPU資源限制,超額使用CPU時間 |
| SIGXFSZ | 25 | 文件大小超額 |
| SIGVTALRM | 26 | 虛擬時鍾信號 |
| SIGPROF | 27 | 時鍾信號描述 |
| SIGWINCH | 28 | 窗口大小改變 |
| SIGIO | 29 | 可以進行輸入/輸出操作 |
| SIGPOLL | SIGIO | |
| SIGPWR | 30 | 斷電重啟 |
| SIGSYS | 31 | 非法的系統調用 |
| SIGUNUSED | 31 | 未使用信號 |


除了SIGSTOP(19)和SIGKILL(9)兩個信號外,進程能夠忽略或捕獲其他的全部信號。一個信號被捕獲的意思是當一個信號到達時有相應的代碼處理它。如果一個信號沒有被這個進程所捕獲,內核將采用默認行為處理。
2.2 信號的接收
在用戶程序中,捕獲信號可以使用signal()函數來設置對應信號的處理函數,函數原型為:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
參數signum:指定信號的值
參數handler:指定針對信號值的處理函數,若為SIG_IGN,表示忽略該信號;若為SIG_DFL,表示采用系統默認方式處理信號;若為用戶自定義的函數,則信號捕獲后,該函數被執行。
返回值:如果調用成功,返回最后一次為信號signum綁定的處理函數的handler值,失敗則返回SIG_ERR。
在進程執行時,按下Ctrl+C將向其發出SIGINT信號,正在運行kill的進程將向其發出SIGTERM(15)信號,捕獲這兩個信號並輸出信號值的代碼如下:
void sigterm_handler(int signo)
{
printf("Have caught sig N.0.%d\n", signo);
exit(0);
}
int main(void)
{
signal(SIGINT, sigterm_handler);
signal(SIGTERM, sigterm_handler);
while(1);
return 0;
}
編譯、測試:
$ ./a.out
^C^CHave caught sig N.0.2
sigaction()函數:可用於改變進程接收到特定信號后的行為,函數原型為:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
參數signum:信號的值,可以為SIGKILL以及SIGSTOP外的任何一個特定有效的信號。
參數act:指向結構體sigaction的一個實例的指針,在結構體sigaction的實例中指定了對特定信號的處理函數,若為空,則進程會以缺省方式對信號處理。
參數oldact:指向的對象用來保存原來對相應信號的處理函數,可指定為NULL。
如果把第二、第三個參數設置為NULL,該函數可用於檢查信號的有效性。
返回值:成功返回0,失敗返回-1。
2.3 異步通知的應用程序實例
通過signal(SIGIO, input_handler)對標准輸入文件描述符STDIN_FIFLNO啟動信號機制。用戶輸入后,應用程序將接收到SIGIO信號,其處理函數input_handler()將被調用。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
void input_handler(int num)
{
char data[MAX_LEN];
int len;
/* 讀取並輸出STDIN_FIFLNO */
len = read(STDIN_FILENO, &data, MAX_LEN);
data[len] = 0;
printf("input data:%s\n", data);
}
int main()
{
int oflags;
/* 啟動信號驅動機制 */
signal(SIGIO, input_handler); //SIGIO信號安裝input_handler()作為處理函數
fcntl(STDIN_FILENO, F_SETOWN, getpid()); //設置本進程為STDIN_FIFENO文件的擁有者
oflags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //設置FASYNC
/* 最后進入一個死循環,保持進程不終止 */
while(1);
}
編譯、測試:
$ gcc sigio_handler.c
$ ./a.out
i am chinese.
input data:i am chinese.
i love linux driver
input data:i love linux driver
可以看出,用戶輸入一串字符之后,標准輸入設備釋放SIGIO信號,這個信號“中斷”驅使應用程序中的input_handler()得以執行,並在用戶輸入顯示出來。
因此,在用戶空間中處理一個設備釋放的信號,必須完成以下三項內容:
1)通過F_SETOWN I/O控制命令設置設備文件的擁有者為本進程,這樣從設備驅動發出的信號才能被本進程接收到。
fcntl(fd, F_SETOWN, getpid());
2)通過F_SETFL I/O控制命令設備設備文件以支持FASYNC,即異步通知模式。
flags = fcntl(fd, F_GETFL); /* 獲取當前的進程狀態 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啟當前進程異步通知功能 */
3)通過signal()函數連接信號和信號處理函數。
void xxx_signal_handler(int num)
{
... ...
}
2.4 設備驅動中異步通知
在設備驅動和應用程序的交互中,信號的源頭在設備驅動端,捕獲在應用程序端。因此,信號的釋放應該在設備驅動中進行。
設備支持異步通知機制,驅動程序需要支持以下三項工作:
1)支持F_SETOWN命令,能在這個控制命令中處理file->f_owner為對應進程的ID,這個已經由內核完成,設備驅動無須處理。
2)支持F_SETFL命令,每當FASYNC標志改變時,驅動程序中的fasync()函數得到執行,設備驅動中應該實現fasync()函數。
3)在設備資源可獲得時,調用kill_fasync()函數激發對應的信號。
驅動中的三項工作和應用程序中的三項工作是一一對應的,設備驅動異步通知處理和用戶空間交互過程如下:

設備驅動中異步編程的函數涉及一個數據結構fasync_struct和兩個函數fasync_helper()和kill_fasync():
(1)處理FASYNC標志變更的函數
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
struct rcu_head fa_rcu;
};
/* 參數fd、filp、on通過fasync函數的三個參數傳入;
* 參數fapp:要初始化的fasync_struct結構體指針變量。
*/
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);
(2)釋放信號函數
void kill_fasync(struct fasync_struct **fp, int sig, int band);
在設備資源可以獲得時,應該調用kill_fasync()釋放SIGIO信號。在可讀時,第三個參數band設置為POLL_IN;在可寫時,第三個參數band設置為POLL_OUT。
fasync_struct數據結構體指針一般放在設備結構體中,支持異步通知的設備結構體模板如下:
struct xxx_dev {
struct cdev cdev;
... ....;
struct fasync_struct *async_queue; /* 異步結構體指針 */
... ...;
};
(3)設備驅動file_operations操作集中的fasync()函數
int (*fasync) (int fd, struct file *filp, int mode);
支持異步通知的設備驅動程序fasync()函數的模板:
static int xxx_fasync(int fd, struct file *filp, int mode)
{
struct xxx_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
在release函數中釋放fasync_struct,同樣調用fasync_helper實現:
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0);
}
支持異步通知的設備驅動信號釋放模板:
在資源可獲得時,調用kill_fasync()函數釋放SIGIO信號。
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct xxx_dev *dev = filp->private_data;
... ...;
/* 產生異步讀信號 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
最后在文件關閉時,在設備的release()函數中,調用設備驅動的fasync()函數將文件從異步通知的列表中刪除。支持異步通知的設備驅動release()函數模板如下:
static int xxx_release(struct inode *inode, struct file *filp)
{
/* 將文件從異步通知的列表中刪除 */
xxx_fasync(-1, filp, 0);
... ...;
return 0;
}
3 支持異步通知的globalfifo驅動
3.1 設備驅動globalfifo的改寫
首先,需要將異步數據結構體指針添加到globalfifo_dev設備結構體中:
/* 設備結構體 */
struct globalfifo_dev {
struct cdev cdev;
unsigned int current_len; /* 當前FIFO中有效數據的長度 */
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
struct fasync_struct *async_queue; /* 異步結構體指針 */
};
然后,編寫異步通知的globalfifo設備驅動的fasync()函數:
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
然后,改寫globalfifo設備驅動的寫函數,在globalfifo設備被正確寫入之后,可以進行讀取,需要支持釋放SIGIO信號,通過給應用程序捕獲。
/**
* 寫設備
* @param[in] filp:文件結構體指針
* @param[in] buf: 用戶空間內存地址,不能在內核中直接讀寫
* @param[in] size: 寫入的字節數
* @param[in/out] ppos: 寫的位置相當於文件頭的偏移
* @return 若成功返回實際寫的字節數,若出錯返回錯誤碼
*/
static ssize_t globalfifo_write(struct file *filp,
const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait, &wait);
while (dev->current_len == GLOBALFIFO_SIZE) {
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
/* 用戶空間緩存區到內核空間緩存區的復制 */
if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait);
if (dev->async_queue) {
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
printk(KERN_INFO "%s kill SIGIO\n", __func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
globalfifo設備驅動的release()函數需要調用globalfifo_fasync()函數將文件從異步通知列表中刪除:
static int globalfifo_release(struct inode *inode, struct file *filp)
{
/* 將文件從異步通知的列表中刪除 */
globalfifo_fasync(-1, filp, 0);
return 0;
}
完整的設備驅動代碼:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
/* 直接使用立即數當作命令不合理,暫定 */
#define MEM_CLEAR 0x1
#define GLOBALFIFO_MAJOR 230
#define GLOBALFIFO_SIZE 0x1000
static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO);
/* 設備結構體 */
struct globalfifo_dev {
struct cdev cdev;
unsigned int current_len; /* 當前FIFO中有效數據的長度 */
unsigned char mem[GLOBALFIFO_SIZE];
struct mutex mutex;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
struct fasync_struct *async_queue; /* 異步結構體指針 */
};
struct globalfifo_dev *globalfifo_devp;
static int globalfifo_fasync(int fd, struct file *filp, int mode)
{
struct globalfifo_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
static int globalfifo_open(struct inode *inode, struct file *filp)
{
/* 使用文件的私有數據作為獲取globalfifo_dev的實例指針 */
filp->private_data = globalfifo_devp;
return 0;
}
static int globalfifo_release(struct inode *inode, struct file *filp)
{
/* 將文件從異步通知的列表中刪除 */
globalfifo_fasync(-1, filp, 0);
return 0;
}
/**
* 設備ioctl函數
* @param[in] filp:文件結構體指針
* @param[in] cmd: 命令,當前僅支持MEM_CLEAR
* @param[in] arg: 命令參數
* @return 若成功返回0,若出錯返回錯誤碼
*/
static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct globalfifo_dev *dev = filp->private_data;
switch (cmd) {
case MEM_CLEAR:
mutex_lock(&dev->mutex);
dev->current_len = 0;
memset(dev->mem, 0, GLOBALFIFO_SIZE);
mutex_unlock(&dev->mutex);
printk(KERN_INFO "globalfifo is set to zero\n");
break;
default:
return -EINVAL;
}
return 0;
}
/**
* 查詢對一個或多個文件描述符的讀或寫是否會阻塞
* @param[in] filp:文件結構體指針
* @param[in] wait: 輪詢表指針
* @return 返回位掩碼指示是否非阻塞的讀或寫是可能的
*/
static unsigned int globalfifo_poll(struct file *filp,
struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;
mutex_lock(&dev->mutex);
/* 調用select而阻塞的進程可以被r_wait和w_wait喚醒 */
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);
if (dev->current_len != 0) {
/* 設備可以無阻塞的讀,正常數據可用來讀 */
mask |= POLLIN | POLLRDNORM;
}
if (dev->current_len != GLOBALFIFO_SIZE) {
/* 設備可以無阻塞的寫 */
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&dev->mutex);
return mask;
}
/**
* 讀設備
* @param[in] filp:文件結構體指針
* @param[out] buf: 用戶空間內存地址,不能在內核中直接讀寫
* @param[in] size: 讀取的字節數
* @param[in/out] ppos: 讀的位置相當於文件頭的偏移
* @return 若成功返回實際讀的字節數,若出錯返回錯誤碼
*/
static ssize_t globalfifo_read(struct file *filp,
char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
unsigned long count = size;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->r_wait, &wait);
while (dev->current_len == 0) {
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if (count > dev->current_len)
count = dev->current_len;
/* 內核空間到用戶空間緩存區的復制 */
if (copy_to_user(buf, dev->mem, count)) {
ret = -EFAULT;
goto out;
} else {
memcpy(dev->mem, dev->mem + count, dev->current_len - count);
dev->current_len -= count;
printk(KERN_INFO "read %lu bytes(s) from %u\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait);
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->r_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
/**
* 寫設備
* @param[in] filp:文件結構體指針
* @param[in] buf: 用戶空間內存地址,不能在內核中直接讀寫
* @param[in] size: 寫入的字節數
* @param[in/out] ppos: 寫的位置相當於文件頭的偏移
* @return 若成功返回實際寫的字節數,若出錯返回錯誤碼
*/
static ssize_t globalfifo_write(struct file *filp,
const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
struct globalfifo_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait, current);
mutex_lock(&dev->mutex);
add_wait_queue(&dev->w_wait, &wait);
while (dev->current_len == GLOBALFIFO_SIZE) {
if (filp->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->mutex);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->mutex);
}
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
/* 用戶空間緩存區到內核空間緩存區的復制 */
if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
ret = -EFAULT;
goto out;
} else {
dev->current_len += count;
printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait);
if (dev->async_queue) {
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
printk(KERN_INFO "%s kill SIGIO\n", __func__);
}
ret = count;
}
out:
mutex_unlock(&dev->mutex);
out2:
remove_wait_queue(&dev->w_wait, &wait);
set_current_state(TASK_RUNNING);
return ret;
}
/**
* 文件偏移設置
* @param[in] filp:文件結構體指針
* @param[in] offset: 偏移值大小
* @param[in] orig: 起始偏移位置
* @return 若成功返回文件當前位置,若出錯返回錯誤碼
*/
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig) {
case 0: /* 從文件頭位置設置偏移 */
if (offset < 0) {
ret = -EINVAL;
break;
}
if ((unsigned int)offset > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /* 從當前位置設置偏移 */
if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
ret = -EINVAL;
break;
}
if ((filp->f_pos + offset) < 0) {
ret = -EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = -EINVAL;
break;;
}
return ret;
}
static const struct file_operations globalfifo_fops = {
.owner = THIS_MODULE,
.llseek = globalfifo_llseek,
.read = globalfifo_read,
.write = globalfifo_write,
.unlocked_ioctl = globalfifo_ioctl,
.open = globalfifo_open,
.release = globalfifo_release,
.poll = globalfifo_poll,
.fasync = globalfifo_fasync,
};
static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
int err, devno = MKDEV(globalfifo_major, index);
/* 初始化cdev */
cdev_init(&dev->cdev, &globalfifo_fops);
dev->cdev.owner = THIS_MODULE;
/* 注冊設備 */
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index);
}
/* 驅動模塊加載函數 */
static int __init globalfifo_init(void)
{
int ret;
dev_t devno = MKDEV(globalfifo_major, 0);
/* 獲取設備號 */
if (globalfifo_major)
ret = register_chrdev_region(devno, 1, "globalfifo");
else {
ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
globalfifo_major = MAJOR(devno);
}
if (ret < 0)
return ret;
/* 申請內存 */
globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
if (!globalfifo_devp) {
ret = -ENOMEM;
goto fail_malloc;
}
globalfifo_setup_cdev(globalfifo_devp, 0);
mutex_init(&globalfifo_devp->mutex);
init_waitqueue_head(&globalfifo_devp->r_wait);
init_waitqueue_head(&globalfifo_devp->w_wait);
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(globalfifo_init);
/* 驅動模塊卸載函數 */
static void __exit globalfifo_exit(void)
{
cdev_del(&globalfifo_devp->cdev);
kfree(globalfifo_devp);
/* 釋放設備號 */
unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
}
module_exit(globalfifo_exit);
MODULE_AUTHOR("MrLayfolk");
MODULE_LICENSE("GPL v2");
3.2 用戶空間globalfifo驅動驗證
用戶空間應用程序實現在接收到globalfifo發出的信號后輸出信號值。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100
static void signalio_handler(int signum)
{
printf("receive a signal, signal number:%d\n", signum);
}
int main()
{
int fd, oflags;
/* 以非阻塞方式打開設備文件 */
fd = open("/dev/globalfifo", O_RDONLY | S_IRUSR | S_IWUSR);
if (fd != -1) {
/* 啟動信號驅動機制 */
signal(SIGIO, signalio_handler); //SIGIO信號安裝input_handler()作為處理函數
fcntl(fd, F_SETOWN, getpid()); //設置本進程為STDIN_FIFENO文件的擁有者
oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC); //設置FASYNC
while (1) {
sleep(100);
}
} else {
printf("device open failure\n");
}
return 0;
}
3.3 編譯測試
(1)編譯設備驅動ko,並插入ko
$ make
$ insmod globalfifo.ko
(2)創建設備節點
$ mknod /dev/globalfifo c 230 0
(3)編譯用戶程序,並且運行
$ gcc globalfifo_app.c
$ ./a.out
(4)向設備驅動寫入數據,signalio_handler()會被調用
$ echo "hello" > /dev/globalfifo
$ echo "hello" > /dev/globalfifo
$ echo "hello" > /dev/globalfifo
$ ./a.out
receive a signal, signal number:29
receive a signal, signal number:29
receive a signal, signal number:29
4 按鍵中斷使用信號
4.1 驅動程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/fcntl.h>
#define KEY_DEVICE_CNT 1 /* 設備個數 */
#define KEY_NAME "key_noblock" /*設備名字*/
/* 定義鍵值 */
#define KEY0_VALUE 0x01 /* KEY0按鍵值 */
#define INVALID_KEY 0xFF /* 無效鍵值 */
#define KEY_NUM 1 /* 按鍵數量 */
/* 中斷IO描述結構體 */
struct irq_key_desc {
int gpio; /* GPIO */
int irq_num; /* 中斷號 */
unsigned char value; /* 按鍵對應的鍵值 */
char name[10]; /* 名字 */
irqreturn_t(*handler)(int, void *); /* 中斷處理函數 */
};
/* KEY設備結構體 */
struct chrdev_key {
dev_t devid; /* 設備號 */
struct cdev cdev; /* cdev */
struct class *class; /* 類 */
struct device *device; /* 設備 */
int major; /* 主設備號 */
int minor; /* 次設備號 */
struct device_node *nd; /* 設備節點 */
atomic_t key_value; /* 有效的按鍵值 */
atomic_t release_key; /* 標記一次完成的按鍵是否完成 */
struct timer_list timer; /* 定時器 */
struct irq_key_desc irq_key[KEY_NUM]; /* 按鍵描述 */
unsigned char cur_key_num; /* 當前的按鍵號 */
wait_queue_head_t r_wait; /* 讀等待隊列頭 */
struct fasync_struct *async_queue; /* 異步相關結構體 */
};
struct chrdev_key chrdev_key;
/* 中斷服務函數:開啟定時器,延時10ms,定時器用於消抖
* 參數irq:中斷號
* 參數dev_id:設備結構
* 返回值:中斷處理結果
*/
irqreturn_t key0_irq_handler(int irq, void *dev_id)
{
struct chrdev_key *dev = (struct chrdev_key *)dev_id;
dev->cur_key_num = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
/* 定時器服務函數:消除按鍵抖動,定時器到了后再次讀取按鍵值,如果按鍵處於按下則表示按鍵有效
* arg:設備結構體變量
*/
void timer_func(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_key_desc *key_desc = NULL;
struct chrdev_key *dev = (struct chrdev_key *)arg;
num = dev->cur_key_num;
key_desc = &dev->irq_key[num];
value = gpio_get_value(key_desc->gpio);
if (value == 0) { /* 按下按鍵 */
atomic_set(&dev->key_value, key_desc->value);
} else { /* 按鍵松開 */
atomic_set(&dev->key_value, 0x80 | key_desc->value);
atomic_set(&dev->release_key, 1); /* 標記松開按鍵 */
}
/* 喚醒進程 */
if (atomic_read(&dev->release_key)) { /* 完成一次按鍵過程 */
if (dev->async_queue) {
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
}
}
/* 按鍵IO初始化 */
static int key_gpio_init(void)
{
unsigned char i = 0;
int ret = 0;
chrdev_key.nd = of_find_node_by_path("/key");
if (chrdev_key.nd == NULL) {
printk("Cannot find device node!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
chrdev_key.irq_key[i].gpio = of_get_named_gpio(chrdev_key.nd, "key-gpio", i);
if (chrdev_key.irq_key[i].gpio < 0) {
printk("Cannot get key%d!\r\n", i);
}
}
/* 初始化key使用的IO,並且設置為中斷模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(chrdev_key.irq_key[i].name, 0x0, sizeof(chrdev_key.irq_key[i].name));
sprintf(chrdev_key.irq_key[i].name, "KEY%d", i);
gpio_request(chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].name);
gpio_direction_input(chrdev_key.irq_key[i].gpio); /* 設置為輸入 */
chrdev_key.irq_key[i].irq_num = irq_of_parse_and_map(chrdev_key.nd, i);
printk("key%d: gpio:%d, irq_num:%d\r\n", i, chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].irq_num);
}
/* 申請中斷 */
chrdev_key.irq_key[0].handler = key0_irq_handler;
chrdev_key.irq_key[0].value = KEY0_VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(chrdev_key.irq_key[i].irq_num, chrdev_key.irq_key[i].handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
chrdev_key.irq_key[i].name, &chrdev_key);
if (ret < 0) {
printk("key%d irq %d request failed!\r\n", i, chrdev_key.irq_key[i].irq_num);
return -EFAULT;
}
printk("key%d irq %d request successfully!\r\n", i, chrdev_key.irq_key[i].irq_num);
}
/* 創建定時器 */
init_timer(&chrdev_key.timer);
chrdev_key.timer.function = timer_func;
/* 初始化等待隊列頭 */
init_waitqueue_head(&chrdev_key.r_wait);
return 0;
}
static int key_open(struct inode *inode, struct file *filp)
{
filp->private_data = &chrdev_key;
printk("Key open successful!\r\n");
return 0;
}
/* 從設備讀取數據
* 參數filp:要打開的設備文件(文件描述符)
* 參數buf:要返回給用戶空間的數據緩沖區
* 參數cnt:要讀取的數據長度
* 參數offt:相對於文件首地址的偏移
* 返回值:讀取的字節數,如果為負值,表示讀取失敗。
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char key_value = 0;
unsigned char release_key = 0;
struct chrdev_key *dev = filp->private_data;
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞訪問 */
if (atomic_read(&dev->release_key) == 0) { /* 沒有按鍵按下 */
return -EAGAIN;
}
}
/* 加入等待隊列,等待被喚醒,也就是有按鍵按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->release_key));
if (ret) {
goto wait_error;
}
key_value = atomic_read(&dev->key_value);
release_key = atomic_read(&dev->release_key);
if (release_key) { /* key按下 */
if (key_value & 0x80) {
key_value &= ~0x80;
ret = copy_to_user(buf, &key_value, sizeof(key_value));
} else {
key_value = INVALID_KEY;
ret = copy_to_user(buf, &key_value, sizeof(key_value));
goto data_error;
}
/* 按下標記清0 */
atomic_set(&dev->release_key, 0x0);
} else {
goto data_error;
}
return 0;
wait_error:
return ret;
data_error:
return -EINVAL;
}
static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct chrdev_key *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if (atomic_read(&dev->release_key)) { /* 按鍵按下 */
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}
return mask;
}
static int key_fasync(int fd, struct file *filp, int on)
{
struct chrdev_key *dev = filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
static int key_release(struct inode *inode, struct file *filp)
{
return key_fasync(-1, filp, 0);
}
/* 設備操作函數 */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.poll = key_poll,
.fasync = key_fasync,
.release = key_release,
};
static int __init chrdev_key_init(void)
{
/* 注冊字符設備驅動 */
/* 創建設備號 */
if (chrdev_key.major) { /* 定義了設備號 */
chrdev_key.devid = MKDEV(chrdev_key.major, 0);
register_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT, KEY_NAME);
} else { /* 沒有定義設備號 */
alloc_chrdev_region(&chrdev_key.devid, 0, KEY_DEVICE_CNT, KEY_NAME);
chrdev_key.major = MAJOR(chrdev_key.devid);
chrdev_key.minor = MINOR(chrdev_key.devid);
}
/* 初始化cdev */
chrdev_key.cdev.owner = THIS_MODULE;
cdev_init(&chrdev_key.cdev, &key_fops);
/* 添加一個cdev */
cdev_add(&chrdev_key.cdev, chrdev_key.devid, KEY_DEVICE_CNT);
/* 創建類 */
chrdev_key.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(chrdev_key.class)) {
return PTR_ERR(chrdev_key.class);
}
/* 創建設備 */
chrdev_key.device = device_create(chrdev_key.class, NULL, chrdev_key.devid, NULL, KEY_NAME);
if (IS_ERR(chrdev_key.device)) {
return PTR_ERR(chrdev_key.device);
}
/* 初始化按鍵 */
atomic_set(&chrdev_key.key_value, INVALID_KEY);
atomic_set(&chrdev_key.release_key, 0x0);
if (key_gpio_init() != 0) {
goto ERROR;
}
printk("Key device driver register successful!\r\n");
return 0;
ERROR:
/* 注銷字符設備驅動 */
cdev_del(&chrdev_key.cdev);
unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);
device_destroy(chrdev_key.class, chrdev_key.devid);
class_destroy(chrdev_key.class);
return -EINVAL;
}
static void __exit chrdev_key_exit(void)
{
unsigned char i = 0;
/* 刪除定時器 */
del_timer_sync(&chrdev_key.timer);
/* 釋放中斷 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(chrdev_key.irq_key[i].irq_num, &chrdev_key);
}
/* 注銷字符設備驅動 */
cdev_del(&chrdev_key.cdev);
unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);
device_destroy(chrdev_key.class, chrdev_key.devid);
class_destroy(chrdev_key.class);
printk("Key device driver unregister successful!\r\n");
}
module_init(chrdev_key_init);
module_exit(chrdev_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MrLayfolk");
4.2 應用程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>
#include <poll.h>
#include <signal.h>
static int fd = 0;
static void sigio_signal_handler(int signum)
{
int err = 0;
unsigned int key_value = 0;
err = read(fd, &key_value, sizeof(key_value));
if (err < 0) {
printf("read file error! error code = %d!\r\n", err);
} else {
printf("sigio signal caught, key value = %d.\r\n", key_value);
}
}
int main(int argc, char *argv[])
{
int flags = 0;
char *file_name= NULL;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
file_name = argv[1];
/* open file */
fd = open(file_name, O_RDWR);
if (fd < 0) {
printf("file %s open failded!\r\n", file_name);
return -1;
}
/* 設置SIGIO處理函數 */
signal(SIGIO, sigio_signal_handler);
fcntl(fd, F_SETOWN, getpid()); /* 將當前進程的進程號告訴內核 */
flags = fcntl(fd, F_GETFD); /* 獲取當前進程狀態 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 設置進程啟動異步通知功能 */
while (1) {
sleep(2);
}
close(fd);
return 0;
}
