Linux設備驅動--異步通知


注:本文是《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的異步通知在時間先后順序上的不同。

image-20220130202942548

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 未使用信號

image-20220130210010063

image-20220130210133027

除了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()函數激發對應的信號。

驅動中的三項工作和應用程序中的三項工作是一一對應的,設備驅動異步通知處理和用戶空間交互過程如下:

image-20220201111407146

設備驅動中異步編程的函數涉及一個數據結構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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM