從內核中最簡單的驅動程序入手,描述Linux驅動開發,主要文章目錄如下(持續更新中):
01 - 第一個內核模塊程序
02 - 註冊字符設備驅動
03 - open & close 函數的應用
04 - read & write 函數的應用
05 - ioctl 的應用
06 - ioctl LED燈硬件分析
07 - ioctl 控制LED軟件實現(寄存器操作)
08 - ioctl 控制LED軟件實現(庫函數操作)
09 - 註冊字符設備的另一種方法(常用)
10 - 一個cdev實現對多個設備的支持
11 - 四個cdev控制四個LED設備
12 - 虛擬串口驅動
13 - I2C驅動
14 - SPI協議及驅動講解
15 - SPI Linux驅動代碼實現
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路複用之 select
19 - I/O多路複用之 poll
20 - I/O多路複用之 epoll
21 - 異步通知
文章目錄
1. 異步IO簡介
Linux 應用程序可以通過阻塞或者非阻塞這兩種方式來訪問驅動設備,通過阻塞方式訪問的話應用程序會處於休眠態,等待驅動設備可以使用,非阻塞方式的話會通過 select或者poll 函數來不斷的輪詢,查看驅動設備文件是否可以使用。這兩種方式都需要應用程序主動的去查詢設備的使用情況,如果能提供一種類似中斷的機制,當驅動程序可以訪問的時候主動告訴應用程序那就最好了,對此Linux提供了一種異步通信機制來實現這種操作。
異步通知的核心是信號 ,當驅動程序可以訪問時會給應用程序發送一信號通知應用程序可以訪問,驅動給應用程序發送信號
Linux中所支持的信號種類如下:(include\uapi\asm\signal.h)
#define SIGHUP 1 // 終端掛起或進程終止
#define SIGINT 2 // ctrl + C
#define SIGQUIT 3 // ctrl + \
#define SIGILL 4 // 非法指令
#define SIGTRAP 5 // DEBUG使用,有斷點時產生
#define SIGABRT 6 // abort(3)發出的退出指令
#define SIGIOT 6 // IOT指令
#define SIGSTKFLT 7 // 棧異常
#define SIGFPE 8 // 浮點運算錯誤
#define SIGKILL 9 // 殺死進程(不可忽略) 常用的 kill -9中的9就是信號的編號9
#define SIGBUS 10 // 總線錯誤
#define SIGSEGV 11 // 無效的內存段
#define SIGXCPU 12 // 超過CPU資源限制
#define SIGPIPE 13 // 向非法管道寫入數據
#define SIGALRM 14 // 鬧鐘
#define SIGTERM 15 // 軟件終止
#define SIGUSR1 16 // 用戶自定義信號1
#define SIGUSR2 17 // 用戶自定義信號2
#define SIGCHLD 18 // 子進程結束
#define SIGPWR 19 // 斷點重啓
#define SIGVTALRM 20 // 虛擬時鐘信號
#define SIGPROF 21 // 時鐘信號描述
#define SIGIO 22 // 可以進行輸入/輸出操作
#define SIGPOLL SIGIO
#define SIGWINCH 23 // 窗口大小改變
#define SIGSTOP 24 // 停止進程的執行,只是暫停(不可忽略)
#define SIGTSTP 25 // ctrl + z
#define SIGCONT 26 // 進程繼續
#define SIGTTIN 27 // 後臺進程需要從終端讀取數據
#define SIGTTOU 28 // 後臺進程需要向終端寫數據
#define SIGURG 29 // 有晉級輸局
#define SIGXFSZ 30 // 文件大小超額
#define SIGUNUSED 31 // 未使用信號
2. 信號的處理操作
2.1 應用層的信號處理
應用層對異步通知的處理過程如下:
2.1.1 註冊信號處理函數
應用層註冊信號處理函數使用 signal,定義如下:
原 型: sighandler_t sighandler_t signal(int signum, sighandler_t handler);
功 能: 註冊信號的處理函數
@param1: 指定的信號標號
@param2: 信號的處理方式
a. 捕獲信號 signal(SIGINT, fun)
b. 忽略信號 signal(SIGINT, SIG_IGN)
c. 執行默認操作 signal(SIGINT, SIG_DFL)
@return: 成功:以前的信號處理函數,失敗: -1
如果信號的處理方式設置爲捕獲信號,則需要實現信號的處理函數 ,信號的處理函數定義如下:
原 型: void (*sighandler_t)(int);
功 能: 捕獲信號後,信號的處理函數
@param1: 傳遞的參數,當設置多個信號對應相同的處理函數時,該參數可用作判斷信號的類型
@return: 無返回值
2.1.2 將本應用程序的進程號告訴給內核
這一步使用的函數如下:
原 型: int fcntl(int fd, int cmd)
int fcntl(int fd, int cmd, long arg)
int fcntl(int fd, int cmd ,struct flock* lock)
功 能: 改變已打開的文件性質
@param1: 文件描述符
@param2: 命命令值,定義如下:
F_DUPFD // 複製文件描述符
F_GETFD // 獲取文件描述符標誌
F_SETFD // 設置文件描述符標誌
F_GETFL // 獲取文件狀態標誌
F_SETFL // 設置文件狀態標誌
F_GETLK // 獲取文件鎖
F_SETLK // 設置文件鎖
F_SETLKW // 類似於F_SETLK,但等待返回
F_SETOWN // 設置當前接收 SIGIO 和 SIGURG 信號的進程ID和進程組ID
F_GETOWN // 獲取當前接收 SIGIO 和 SIGURG 信號的進程ID和進程組ID
@param3: 本例中用作指定一個進程作爲文件的“屬主(filp->owner)”,這樣內核才知道信號要發給哪個進程。
@return: 返回值與命令值有關,失敗返回-1,成功則返回某個其他值
使用 fcntl(fd, F_SETOWN, getpid()) 將本應用程序的進程號告訴給內核
2.1.3 開啓異步通知
使用下面的代碼開啓異步通知
int flags;
flags = fcntl(fd, F_GETFL); // 獲取當前的進程狀態
fcntl(fd, F_SETFL, flags | FASYNC); // 開啓當前進程異步通知功能
2.2 驅動中的信號處理
驅動中信號的結構體定義如下:
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;
};
2.2.1 .fasync函數
相關的函數如下:
如果要實現異步通知,首先應該實現 file_operations 中的fasync接口
static int vser_fasync(int fd, struct file *filp, int on)
{
return 0;
}
struct file_operations vser_fops = {
.fasync = vser_fasync,
};
2.2.2 fasync_helper 函數
在fasync接口中通常調用 fasync_helper 函數對fasync_struct結構體進行初始化,fasync_helper 定義如下:
原 型: int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
功 能: 初始化 fasync_struct 結構體,設置 fasync 隊列,將fd,filp和定義的結構體傳給內核
@param1: 和fasync接口中參數一相同
@param2: 和fasync接口中參數二相同
@param3: 和fasync接口中參數三相同
@param4: 要初始化的fasync_struct結構體,注意要傳的是二級指針,通常定義一個fasync_struct結構體指針,傳它的地址
@return: 負數表示失敗,0表示fasync隊列未更改,正數表示添加或者刪除條目
在關閉驅動文件時需要釋放 fasync_struct,函數fasync_helper在內核中的實現如下,從中可以看出如果on等於0時,會執行fasync_remove_entry函數,因此可以同樣調用file_operations中的fasync接口,將傳入的參數改爲(-1, filp, 0)即可
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
2.2.3 kill_fasync 函數
當設備可以訪問時,驅動程序需要嚮應用程序發送信號,使用的函數是 kill_fasync 該函數的定義如下:
原 型: void kill_fasync(struct fasync_struct **fapp, int sig, int band)
功 能: 當設備可以訪問時,嚮應用程序發送信號
@param1: 要操作的 fasync_struct 結構體
@param2: 要發送的信號,常用的信號如上介紹的各種宏
@param3: 可讀時設置爲 POLL_IN,可寫時設置爲 POLL_OUT
@return: 無返回值
3. 示例代碼
3.1 demo.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/kfifo.h>
#include <linux/timer.h>
#define VSER_CHRDEV_NAME ("vser_chrdev")
#define VSER_CLASS_NAME ("vser_class")
#define VSER_DEVICE_NAME ("device_chrdev")
#define DELAY_MS (5000)
// 虛擬串口驅動相關結構體
struct vser_dev{
dev_t devno;
int major;
int minor;
struct cdev cdev;
struct class *cls;
struct device *dev;
unsigned int timeperiod; // 延時週期,ms
struct timer_list timer_t; // 低分辨率定時器
struct fasync_struct *vser_fasync; // 異步相關的結構體
};
struct vser_dev test_vser_dev;
DEFINE_KFIFO(virtual_serial_fifo, char, 32);
void timer_function(unsigned long arg)
{
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
printk("before kill_fasync.\n");
kill_fasync(&test_vser_dev.vser_fasync, SIGIO, POLL_IN);
printk("after kill_fasync.\n");
}
static int vser_fasync(int fd, struct file *filp, int on)
{
int ret;
struct vser_dev *test_vser_dev = filp->private_data;
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
ret = fasync_helper(fd, filp, on, &test_vser_dev->vser_fasync); // 初始化前面定義的 fasync_struct 結構體指針
if (ret < 0)
{
printk("fasync_helper failed.\n");
ret = EIO;
}
return 0;
}
static int vser_open(struct inode *inode, struct file *filp)
{
filp->private_data = &test_vser_dev;
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int vser_release(struct inode *inode, struct file *filp)
{
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
vser_fasync(-1, filp, 0); // 在release函數中同樣調用fasync_helper釋放fasync_struct
return 0;
}
static ssize_t vser_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
unsigned int copied_num, ret;
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
if ( !kfifo_is_empty(&virtual_serial_fifo) ) // fifo爲空返回true
{
ret = kfifo_to_user(&virtual_serial_fifo, userbuf, size, &copied_num);
if (ret < 0)
{
printk("kfifo_to_user failed.\n");
return ret;
}
printk("%s -- %s -- copied_num = %d.\n", __FILE__, __FUNCTION__, copied_num);
}
return size;
}
static ssize_t vser_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
int retval, copied_num;
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
retval = kfifo_from_user(&virtual_serial_fifo, userbuf, size, &copied_num);
if (retval == -EFAULT)
{
printk("kfifo_from_user failed.\n");
}
printk("%s -- %s -- copied_num = %d.\n", __FILE__, __FUNCTION__, copied_num);
return 0;
}
struct file_operations vser_fops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
.fasync = vser_fasync,
};
static int __init vser_init(void)
{
int ret;
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
if ( test_vser_dev.major )
{
test_vser_dev.devno = MKDEV(test_vser_dev.major, 0);
ret = register_chrdev_region(test_vser_dev.devno, 1, VSER_CHRDEV_NAME);
if ( ret < 0 )
{
printk("register_chrdev_region failed.\n");
goto register_chrdev_region_err;
}
}
else
{
ret = alloc_chrdev_region(&test_vser_dev.devno, 0, 1, VSER_CHRDEV_NAME);
test_vser_dev.major = MAJOR(test_vser_dev.devno);
test_vser_dev.minor = MINOR(test_vser_dev.devno);
if ( ret < 0 )
{
printk("alloc_chrdev_region failed.\n");
goto alloc_chrdev_region_err;
}
}
cdev_init(&test_vser_dev.cdev, &vser_fops);
ret = cdev_add(&test_vser_dev.cdev, test_vser_dev.devno, 1);
{
if ( ret < 0 )
{
printk("cdev_add failed.\n");
goto cdev_add_err;
}
}
test_vser_dev.cls = class_create(THIS_MODULE, VSER_CLASS_NAME);
if ( IS_ERR(test_vser_dev.cls) )
{
printk("class_create failed.\n");
ret = PTR_ERR(test_vser_dev.cls);
goto class_create_err;
}
test_vser_dev.dev = device_create(test_vser_dev.cls, NULL, test_vser_dev.devno, NULL, VSER_DEVICE_NAME);
if ( IS_ERR(test_vser_dev.dev) )
{
printk("device_create failed.\n");
ret = PTR_ERR(test_vser_dev.dev);
goto device_create_err;
}
init_timer(&test_vser_dev.timer_t); // 初始化低分辨率定時器
test_vser_dev.timeperiod = DELAY_MS;
test_vser_dev.timer_t.expires = get_jiffies_64() + msecs_to_jiffies(test_vser_dev.timeperiod);
test_vser_dev.timer_t.function = timer_function;
test_vser_dev.timer_t.data = (unsigned long)&test_vser_dev;
add_timer(&test_vser_dev.timer_t);
return 0;
device_create_err:
class_destroy(test_vser_dev.cls);
class_create_err:
cdev_del(&test_vser_dev.cdev);
cdev_add_err:
unregister_chrdev_region(test_vser_dev.devno, 1);
alloc_chrdev_region_err:
register_chrdev_region_err:
return ret;
}
static void __exit vser_exit(void)
{
printk("%s -- %s -- %d.\n", __FILE__, __FUNCTION__, __LINE__);
del_timer(&test_vser_dev.timer_t);
device_destroy(test_vser_dev.cls, test_vser_dev.devno);
class_destroy(test_vser_dev.cls);
cdev_del(&test_vser_dev.cdev);
unregister_chrdev_region(test_vser_dev.devno, 1);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
3.2 test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
const char *pathname = "/dev/device_chrdev";
int fd;
char write_buf[32] = "hello";
char read_buf[32] = "\0";
static void sigio_signal_func(int signum)
{
int err = 0;
err = read(fd, read_buf, sizeof(read_buf));
if(err < 0)
{
printf("read error\n"); // 讀取錯誤;
strcpy(write_buf, "linux linux");
write(fd, write_buf, sizeof(write_buf));
}
else
{
printf("sigio signal! buf = %s\r\n", read_buf);
}
}
int main(int argc, const char *argv[])
{
int flags = 0;
fd = open(pathname, O_RDWR, 0666);
if (fd < 0)
{
printf("open failed\n");
return fd;
}
write(fd, write_buf, sizeof(write_buf));
signal(SIGIO, sigio_signal_func); // 設置信號SIGIO的處理函數
fcntl(fd, F_SETOWN, getpid()); // 設置當前進程接收SIGIO信號
flags = fcntl(fd, F_GETFL); // 獲取當前的進程狀態
// 通過 fcntl 函數設置進程狀態爲 FASYNC,經過這一步,驅動程序中的 fasync 函數就會執行
fcntl(fd, F_SETFL, flags | FASYNC); // 設置進程啓用異步通知功能
while(1)
{
}
close(fd);
return 0;
}
3.3 Makefile
KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-04.00.00.04/board-support/linux-4.9.28/
PWD := $(shell pwd)
EXEC = app
OBJS = test.o
CC = arm-linux-gnueabihf-gcc
$(EXEC):$(OBJS)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
$(CC) $^ -o $@
.o:.c
$(CC) -c $<
install:
sudo cp *.ko app /tftpboot
sudo cp *.ko app /media/linux/rootfs1/home/root/
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
clean:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
rm app
obj-m += demo.o
3.4 測試結果
root@am335x-evm:~# insmod demo.ko
[ 68.640752] demo.c -- vser_init -- 127.
root@am335x-evm:~# ./app
[ 70.200820] demo.c -- vser_open -- 62.
[ 70.207501] demo.c -- vser_write -- 100.
[ 70.219652] demo.c -- vser_write -- copied_num = 32.
[ 70.230214] demo.c -- vser_fasync -- 46.
[ 73.681855] demo.c -- timer_function -- 34.
[ 73.688605] before kill_fasync.
[ 73.691770] after kill_fasync.
[ 73.698877] demo.c -- vser_read -- 80.
[ 73.706815] demo.c -- vser_read -- copied_num = 32.
sigio signal! buf = hello
^C
[ 86.483844] demo.c -- vser_fasync -- 46.
[ 86.490340] demo.c -- vser_release -- 69.
[ 86.500087] demo.c -- vser_fasync -- 46.
root@am335x-evm:~# rmmod demo.ko
[ 94.961113] demo.c -- vser_exit -- 199.