chen.yang cf1b8765bc 增加 并发控制示例.
Signed-off-by: chen.yang <chen.yang@yuzhen-iot.com>
2022-04-16 13:41:10 +08:00

488 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 8.6 字符设备驱动
Linux 系统中,字符设备、块设备、网络设备是从使用角度来划分的。
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和 LED 设备等。
块设备是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U 盘和 SD 卡等。
## 字符设备驱动示例
```cpp
/**
* @file demo_char.c
* @author Rick Chan (cy187lion@sina.com)
* @brief Linux char driver demo.
* @version 0.1.0
* @date 2020-04-27
*
* @copyright Copyright (c) 2020
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
MODULE_AUTHOR("Rick Chan");
MODULE_LICENSE("GPL");
#define DEMO_MAGIC 'R'
#define DEMO_IOC_SIZE 8
#define DEMO_CTL_IOC _IOC(_IOC_READ|_IOC_WRITE, DEMO_MAGIC, 0, DEMO_IOC_SIZE)
#define DEMO_CTL_IO _IO(DEMO_MAGIC, 1)
#define DEMO_CTL_IOR _IOR(DEMO_MAGIC, 2, uint16_t)
#define DEMO_CTL_IOW _IOW(DEMO_MAGIC, 3, int32_t)
#define DEMO_CTL_IOWR _IOWR(DEMO_MAGIC, 4, uint32_t)
#define DEMO_CTL_MAX 5
#define DEMO_MODULE_NAME "demo_char"
#define DEMO_DEV_CNT 2
// This is a test.
#define DEMO_DATA_SIZE 5
static int demo_major = 0;
struct demo_dev
{
struct cdev cdev;
struct device *dev;
// This is a test.
char demo_text[DEMO_DATA_SIZE];
};
struct class *class;
struct demo_dev* demo_devp;
static int demo_open(struct inode *inode, struct file *filp)
{
struct demo_dev *demo;
demo = container_of(inode->i_cdev, struct demo_dev, cdev);
filp->private_data = demo;
return 0;
}
static int demo_release(struct inode *inode, struct file *filp)
{
return 0;
}
static loff_t demo_llseek(struct file *filp, loff_t offset, int origin)
{
struct demo_dev *devp = filp->private_data;
loff_t ret;
(void)devp;
// This is a test.
switch(origin)
{
case 0: // 从文件开头开始偏移
if(offset<0)
{
ret = -EINVAL;
break;
}
if((unsigned int)offset>DEMO_DATA_SIZE)
{
ret = -EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: // 从当前位置开始偏移
if((filp->f_pos+offset)>DEMO_DATA_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;
}
return ret;
return filp->f_pos;
}
static ssize_t demo_read(struct file *filp, char __user *buffer, size_t count, loff_t *position)
{
struct demo_dev *devp = filp->private_data;
loff_t p = *position;
ssize_t ret = 0;
// This is a test.
// 分析和获取有效的读长度
if(DEMO_DATA_SIZE<=p) // 要读的偏移位置越界
return 0; // End of a file
if(DEMO_DATA_SIZE<(count+p)) // 要读的字节数太大
count = DEMO_DATA_SIZE-p;
if(copy_to_user((void*)buffer, &devp->demo_text[p], count))
ret = -EFAULT;
else
{
*position += count;
ret = count;
}
return ret;
}
static ssize_t demo_write(struct file *filp, const char __user *buffer, size_t count, loff_t *position)
{
struct demo_dev *devp = filp->private_data;
const char __user *p = buffer;
(void)devp;
return p-buffer;
}
static long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct demo_dev *devp = filp->private_data;
(void)devp;
// 检测 cmd 合法性
if (DEMO_MAGIC!=_IOC_TYPE(cmd))
return -EINVAL;
if (DEMO_CTL_MAX<_IOC_NR(cmd))
return -EINVAL;
switch(cmd)
{
case DEMO_CTL_IOC:
{
uint8_t tmp[DEMO_IOC_SIZE];
copy_from_user(&tmp, (void*)arg, _IOC_SIZE(cmd));
copy_to_user((void*)arg, &tmp, _IOC_SIZE(cmd));
}
break;
case DEMO_CTL_IO:
break;
case DEMO_CTL_IOR:
{
uint16_t tmp = 0x55AA;
copy_to_user((void*)arg, &tmp, _IOC_SIZE(cmd));
}
break;
case DEMO_CTL_IOW:
{
int32_t tmp = 0;
copy_from_user(&tmp, (void*)arg, _IOC_SIZE(cmd));
}
break;
case DEMO_CTL_IOWR:
{
uint32_t tmp = 0;
copy_from_user(&tmp, (void*)arg, _IOC_SIZE(cmd));
copy_to_user((void*)arg, &tmp, _IOC_SIZE(cmd));
}
break;
default:
return -ENOIOCTLCMD;
}
return 0;
}
static int demo_mmap(struct file *filp, struct vm_area_struct *vma)
{
struct demo_dev *devp = filp->private_data;
(void)devp;
return 0;
}
static struct file_operations demo_fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
.llseek = demo_llseek,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
.mmap = demo_mmap
};
static int demo_setup_cdev(struct demo_dev *devp, int index)
{
char name[16];
int err, devno = MKDEV(demo_major, index);
cdev_init(&devp->cdev, &demo_fops);
devp->cdev.owner = THIS_MODULE;
err = cdev_add(&devp->cdev, devno, 1);
if(err)
{
printk(KERN_ERR "demo add cdev:%d error:%d.\r\n", index, err);
goto out_cdev;
}
// 创建设备节点
memset(name, 0, 16);
sprintf(name, DEMO_MODULE_NAME"%d", index);
printk(KERN_INFO "demo new dev name:%s", name);
devp->dev = device_create(class, NULL, devp->cdev.dev, NULL, name);
// This is a test.
devp->demo_text[DEMO_DATA_SIZE-2] = '\n';
devp->demo_text[DEMO_DATA_SIZE-1] = 0;
sprintf(devp->demo_text, "%d", index);
return 0;
out_cdev:
cdev_del(&devp->cdev);
kfree(devp);
return err;
}
static int __init demo_init(void)
{
int err, i;
dev_t devno = MKDEV(demo_major, 0);
printk(KERN_ALERT "demo_init.demo_major: %d\n", demo_major);
if(demo_major) // 使用固定主设备号
err = register_chrdev_region(devno, DEMO_DEV_CNT, DEMO_MODULE_NAME);
else // 动态分配主设备号
{
err = alloc_chrdev_region(&devno, 0, DEMO_DEV_CNT, DEMO_MODULE_NAME);
demo_major = MAJOR(devno);
}
if(err<0)
return err;
demo_devp = kzalloc(DEMO_DEV_CNT*sizeof(struct demo_dev), GFP_KERNEL);
if(!demo_devp)
{
err = -ENOMEM;
goto out;
}
// 创建设备类, 子设备属于同一个设备类
class = class_create(THIS_MODULE, DEMO_MODULE_NAME);
for(i=0; i<DEMO_DEV_CNT; i++)
{
err = demo_setup_cdev(&demo_devp[i], i);
if(err)
goto out_class;
}
return 0;
out_class:
class_destroy(class);
out:
unregister_chrdev_region(devno, DEMO_DEV_CNT);
return err;
}
static void demo_clean_cdev(struct demo_dev *devp)
{
device_destroy(class, devp->cdev.dev);
cdev_del(&devp->cdev);
}
static void __exit demo_exit(void)
{
int i;
for(i=0; i<DEMO_DEV_CNT; i++)
demo_clean_cdev(&demo_devp[i]);
class_destroy(class);
kfree(demo_devp);
unregister_chrdev_region(MKDEV(demo_major, 0), DEMO_DEV_CNT);
}
// insmod 时可传入参数
module_param(demo_major, int, S_IRUGO);
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Rick Chan <cy187lion@sina.com>");
MODULE_DESCRIPTION("Char driver demo");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.0");
```
Makefile 文件如下:
```Makefile
obj-m:= \
demochar.o
demochar-objs:= \
demo_char.o
EXTRA_CFLAGS += \
-I$(PWD)
all:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
```
编译和验证方法:
```bash
make
sudo insmod demochar.ko demo_major=200
ls /dev/demo*
ll /sys/class/demo_char/
sudo rmmod demochar
```
## 字符设备驱动示例说明
### 设备分配设备号
MKDEV 是将主设备号和次设备号转换成 dev_t 类型的一个内核函数。dev_t 定义了设备号,为 32 位,其中高 12 位为主设备号,低 20 位为次设备号。使用下列宏可以从 dev_t 获得主设备号和次设备号。
```cpp
MAJOR(dev_t dev)
MINOR(dev_t dev)
```
以下两段代码:
```cpp
err = register_chrdev_region(devno, DEMO_DEV_CNT, DEMO_MODULE_NAME);
err = alloc_chrdev_region(&devno, 0, DEMO_DEV_CNT, DEMO_MODULE_NAME);
```
用于向系统静态/动态申请设备号。其原型如下:
```cpp
int register_chrdev_region(dev_t from, unsigned count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
```
是否为动态注册是在驱动加载时决定的module_param 用于指定该模块参数:
```cpp
module_param(demo_major, int, S_IRUGO);
```
如果在加载驱动时没有指定设备号,则使用动态分配,否则使用静态分配。
### 分配内存资源
kzalloc 可实现内核内存空间的分配:
```cpp
demo_devp = kzalloc(DEMO_DEV_CNT*sizeof(struct demo_dev), GFP_KERNEL);
```
用于分配 demo_dev 对象空间。
### 注册字符设备
cdev_add() 函数和 cdev_del() 函数分别向系统添加和删除一个 cdev 对象,完成字符设备的注册和注销。对应代码如下:
```cpp
err = cdev_add(&devp->cdev, devno, 1);
cdev_del(&devp->cdev);
```
### udev 文件系统
示例代码通过:
```cpp
class = class_create(THIS_MODULE, DEMO_MODULE_NAME);
devp->dev = device_create(class, NULL, devp->cdev.dev, NULL, name);
```
两个接口来与 udev 文件系统交互,产生用户态设备加载消息,生成设备节点。
### file_operations
示例通过以下代码注册了字符设备的操作接口函数:
```cpp
static struct file_operations demo_fops = {
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
.llseek = demo_llseek,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
.mmap = demo_mmap
};
cdev_init(&devp->cdev, &demo_fops);
```
### 访问用户态数据
内核中不能直接使用用户态数据,需要通过 copy_from_user() 和 copy_to_user() 将数据拷贝到内核,或将新数据拷贝回用户态。如 demo_read() 中的处理:
```cpp
static ssize_t demo_read(struct file *filp, char __user *buffer, size_t count, loff_t *position)
{
struct demo_dev *devp = filp->private_data;
loff_t p = *position;
ssize_t ret = 0;
// This is a test.
// 分析和获取有效的读长度
if(DEMO_DATA_SIZE<=p) // 要读的偏移位置越界
return 0; // End of a file
if(DEMO_DATA_SIZE<(count+p)) // 要读的字节数太大
count = DEMO_DATA_SIZE-p;
if(copy_to_user((void*)buffer, &devp->demo_text[p], count))
ret = -EFAULT;
else
{
*position += count;
ret = count;
}
return ret;
}
```
### IOCTL 操作
示例定义了以下 IOCTL 类型:
```cpp
#define DEMO_MAGIC 'R'
#define DEMO_IOC_SIZE 8
#define DEMO_CTL_IOC _IOC(_IOC_READ|_IOC_WRITE, DEMO_MAGIC, 0, DEMO_IOC_SIZE)
#define DEMO_CTL_IO _IO(DEMO_MAGIC, 1)
#define DEMO_CTL_IOR _IOR(DEMO_MAGIC, 2, uint16_t)
#define DEMO_CTL_IOW _IOW(DEMO_MAGIC, 3, int32_t)
#define DEMO_CTL_IOWR _IOWR(DEMO_MAGIC, 4, uint32_t)
```
用户态调用 ioctl 接口时,必须使用相同的宏,最后才会在 demo_ioctl() 中进行对应的分派处理。
* _IOC用于定义 IOCTL 命令。
* _IOR用于创建只读命令。
* _IOW用于创建只写命令。
* _IOWR用于创建读写命令。
## 练习
1. 为字符设备驱动编写应用程序,用于验证各个接口。
2. 在字符设备驱动的 open、read、write、llseek、unlocked_ioctl 和 release 接口中增加 printk 函数,用于验证来自于用户态的操作。
3. 完善 demo_write() 函数,实现将用户数据写入内核空间,并最后可再通过 demo_read() 读出。