从内核中最简单的驱动程序入手,描述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简介
设备不一定随时都能给用户提供服务,这就有了资源可用和不可用两种状态,程序默认打开设备是以阻塞方式打开的,当以非阻塞打开设备时需要在open时加上 O_NONBLOCK 标志,非阻塞的访问如下图所示:
应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。
2. 示例代码
本文以虚拟串口驱动为例来对非阻塞型I/O进行演示(虚拟串口驱动讲解见虚拟串口驱动讲解)。首先打开虚拟串口设备读取里面的数据,如果kfifo里面没有数据则直接返回,然后往里写数据再读,详细的代码实现过程如下:
2.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>
#define VSER_CHRDEV_ANME ("vser_chrdev")
#define VSER_CLASS_ANME ("vser_cls")
#define VSER_DEVICE_ANME ("vser_dev")
struct vser_dev{
dev_t dev_no;
int major;
int minor;
struct cdev cdev;
struct class *cls;
struct device *dev;
};
struct vser_dev test_vser_dev;
DEFINE_KFIFO(vser_fifo, char, 16); // 声明定义一个虚拟串口
static int vser_open(struct inode *inode, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
filp->private_data = &test_vser_dev;
return 0;
}
static int vser_release(struct inode *indoe, struct file *filp)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
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 -- %d.\n", __FUNCTION__, __LINE__);
if ( kfifo_is_empty(&vser_fifo) ) // kfifo为空返回真
{
printk("kfifo_is_empty.\n");
if ( filp->f_flags & O_NONBLOCK ) // 判断是否以非阻塞方式打开
{
printk("O_NONBLOCK.\n");
ret = -EAGAIN; // try again
}
goto err0;
}
else
{
ret = kfifo_to_user(&vser_fifo, userbuf, size, &copied_num); // kfifo不为空将数据拷贝到用户空间
if (ret < 0)
{
printk("kfifo_to_user failed.\n");
ret = -EFAULT; // Bad Address
goto err0;
}
printk("%s copied_num = %d.\n", __FUNCTION__, copied_num);
}
return copied_num;
err0:
return ret;
}
static ssize_t vser_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
unsigned int copied_num = 0;
unsigned int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
if ( kfifo_is_full(&vser_fifo) ) // kfifo为满返回真
{
printk("kfifo_is_full.\n");
if ( filp->f_flags & O_NONBLOCK ) // 判断是否以非阻塞方式打开
{
printk("%s -- O_NONBLOCK.\n", __FUNCTION__);
ret = -EAGAIN;
goto err0;
}
}
ret = kfifo_from_user(&vser_fifo, userbuf, size, &copied_num); // kfifo不为满,则将用户空间数据拷贝到内核空间
if (ret == -EFAULT)
{
printk("kfifo_from_user failed.\n");
goto err0;
}
printk("%s -- copied_num = %d.\n", __FUNCTION__, copied_num);
return copied_num;
err0:
return ret;
}
struct file_operations vser_fops =
{
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
};
static int __init vser_init(void)
{
int ret;
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
if (test_vser_dev.major)
{
test_vser_dev.dev_no = MKDEV(test_vser_dev.major, 0);
ret = register_chrdev_region(test_vser_dev.dev_no, 1, VSER_CHRDEV_ANME);
if (ret < 0)
{
printk("register_chrdev_region failed.\n");
goto register_chrdev_region_err;
}
}
else
{
ret = alloc_chrdev_region(&test_vser_dev.dev_no, 0, 1, VSER_CHRDEV_ANME);
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.dev_no, 1);
if (ret < 0)
{
printk("cdev_add failed.\n");
goto cdev_add_err;
}
test_vser_dev.cls = class_create(THIS_MODULE, VSER_CLASS_ANME);
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.dev_no, NULL, VSER_DEVICE_ANME);
if ( IS_ERR(test_vser_dev.dev) )
{
printk("device_create failed.\n");
ret = PTR_ERR(test_vser_dev.dev);
goto device_create_err;
}
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(test_vser_dev.major, VSER_CHRDEV_ANME);
alloc_chrdev_region_err:
register_chrdev_region_err:
return ret;
}
static void __exit vser_exit(void)
{
printk("%s -- %d.\n", __FUNCTION__, __LINE__);
device_destroy(test_vser_dev.cls, test_vser_dev.dev_no);
class_destroy(test_vser_dev.cls);
cdev_del(&test_vser_dev.cdev);
unregister_chrdev(test_vser_dev.major, VSER_CHRDEV_ANME);
}
module_init(vser_init);
module_exit(vser_exit);
MODULE_LICENSE("GPL");
2.2 test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd, ret;
const char *dev_pathname = "/dev/vser_dev";
char read_buf[8] = "\0";
char write_buf[8] = "abc";
fd = open(dev_pathname, O_RDWR | O_NONBLOCK, 0666); // 以非阻塞方式打开设备文件
if (fd < 0)
{
perror("open");
return -1;
}
while(1)
{
ret = read(fd, read_buf, sizeof(read_buf));
if (ret == -1) // 读失败
{
perror("read");
printf("\n");
ret = write(fd, write_buf, strlen(write_buf));
if (ret == -1)
{
perror("write");
printf("\n");
}
}
else if (ret == 0)
{
printf("read end of file\n");
}
else
{
printf("read_buf = %s\n", read_buf);
break;
}
}
close(fd);
return 0;
}
2.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
2.4 测试结果
root@am335x-evm:~# insmod demo.ko
[ 34.492537] vser_init -- 124.
root@am335x-evm:~# ./app
[ 36.315438] vser_open -- 28.
[ 36.318426] vser_read -- 46.
[ 36.321332] kfifo_is_empty.
[ 36.330269] O_NONBLOCK.
read: Resource temporarily unavailable
[ 36.338019] vser_write -- 81.
[ 36.352124] vser_write -- copied_num = 3.
[ 36.356208] vser_read -- 46.
[ 36.359118] vser_read copied_num = 3.
read_buf = abc
[ 36.374458] vser_release -- 37.
root@am335x-evm:~# rmmod demo.ko
[ 56.308188] vser_exit -- 185.
test.c中以非阻塞的方式打开虚拟串口设备,最开始kfifo是空的,直接返回,然后往kfifo中写入数据,再次读时可以从kfifo中读出数据并打印出来