丰富杂项设备驱动的read和write
2023-01-04 本文已影响0人
付凯强
引言
通过丰富杂项设备驱动的write和read方法来丰富杂项设备驱动。
char_misc_device.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#define DEMO_NAME "char_misc_device"
static struct device *my_char_misc_device;
static char device_buffer[64];
#define MAX_DEVICE_BUFFER_SIZE 64
static int char_device_open(struct inode *inode, struct file *file) {
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n", __func__, major, minor);
return 0;
}
static ssize_t char_device_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
int actual_readed;
int max_free;
int need_read;
int ret;
max_free = MAX_DEVICE_BUFFER_SIZE - *ppos;
need_read = max_free > count ? count : max_free;
if (need_read == 0) {
dev_warn(my_char_misc_device, "no space for write");
}
ret = copy_to_user(buf, device_buffer + *ppos, need_read);
if (ret == need_read) {
return -EFAULT;
}
actual_readed = need_read - ret;
*ppos += actual_readed;
printk("%s,actual_readed = %d,pos=%lld\n", __func__, actual_readed, *ppos);
return actual_readed;
}
static int char_device_release(struct inode *inode, struct file *file) {
return 0;
}
static ssize_t char_device_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
int actual_write;
int free;
int need_write;
int ret;
free = MAX_DEVICE_BUFFER_SIZE - *ppos;
need_write = free > count ? count : free;
if (need_write == 0) {
dev_warn(my_char_misc_device, "no space for write");
}
ret = copy_from_user(device_buffer + *ppos, buf, need_write);
if (ret == need_write) {
return -EFAULT;
}
actual_write = need_write - ret;
*ppos += actual_write;
printk("%s actual_write = %d,ppos=%lld\n", __func__, actual_write, *ppos);
return actual_write;
}
static const struct file_operations char_device_fops = {
.owner = THIS_MODULE,
.open = char_device_open,
.release = char_device_release,
.read = char_device_read,
.write = char_device_write
};
static struct miscdevice char_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &char_device_fops,
};
static int __init simple_char_device_init(void) {
int ret;
ret = misc_register(&char_misc_device);
if (ret) {
printk("failed register misc device\n");
return ret;
}
my_char_misc_device = char_misc_device.this_device;
printk("successed regitster char device: %s\n", DEMO_NAME);
return 0;
}
static void __exit simple_char_device_exit(void) {
printk("removing device\n");
misc_deregister(&char_misc_device);
}
module_init(simple_char_device_init);
module_exit(simple_char_device_exit);
MODULE_AUTHOR("fkq");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("simple character device");
- char_device_read有4个参数。file表示打开的虚拟设备文件;buf表示用户空间的内存起始地址,__user用来提醒开发者这个地址空间属于用户空间的;count表示用户想读取多少字节的数据;ppos表示文件的位置指针。
char_device_read借助copy_to_user来复制杂项设备中的内容到用户空间buf缓冲区。
#include <linux/uaccess.h>
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
第一个参数to是用户空间的指针,第二个参数from是内核空间指针,第三个参数n表示从内核空间向用户空间拷贝数据的字节数。
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
- char_device_write参数与char_device_read一致,不再赘述。
char_device_write借助copy_from_user来复制用户空间缓冲区内容到杂项设备中。
unsigned long copy_from_user(void * to, const void __user * from, unsigned long n)
第一个参数to是内核空间的数据目标地址指针,第二个参数from是用户空间的数据源地址指针,第三个参数n是数据的长度。
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
此函数将from指针指向的用户空间地址开始的连续n个字节的数据传送到to指针指向的内核空间地址。
Makefile
KVERS = $(shell uname -r)
obj-m := char_misc_device.o
all:
$(MAKE) -C /lib/modules/$(KVERS)/build M=$(PWD) modules
clean:
$(MAKE) -C /lib/modules/$(KVERS)/build M=$(PWD) clean
rm -rf *.ko;
makefile的文件内容被拷贝到本地之后,格式可能发生变化,这个需要调整格式,不然编译会报错。
编译驱动
make
加载驱动
sudo insmod char_misc_device.ko
添加权限
sudo chmod 777 /dev/char_misc_device
char_device_test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define DEMO_DEV_NAME "/dev/char_misc_device"
int main() {
char buffer[64];
int fd;
int ret;
size_t len;
char message[] = "Testing the virtual FIFO device";
char *read_buffer;
len = sizeof(message);
fd = open(DEMO_DEV_NAME, O_RDWR);
if (fd < 0) {
printf("open device %s failed\n", DEMO_DEV_NAME);
return -1;
}
/*1. write the message to device*/
ret = write(fd, message, len);
if (ret != len) {
printf("can not write on device %d,ret=%d",fd,ret);
return -1;
}
read_buffer = malloc(2*len);
memset(read_buffer,0,2*len);
/*close the fd, and reopen it*/
close(fd);
fd = open(DEMO_DEV_NAME,O_RDWR);
if (fd < 0) {
printf("open device %s failded\n",DEMO_DEV_NAME);
return -1;
}
ret = read(fd, read_buffer, 2*len);
printf("read %d bytes\n",ret);
printf("read buffer=%s\n",read_buffer);
close(fd);
return 0;
}
$./char_device_test
read 64 bytes
read buffer=Testing the virtual FIFO device
kernel日志
$dmesg
[4241440.269793] successed regitster char device: char_misc_device
[4241457.314147] char_device_open: major=10, minor=56
[4241457.314163] char_device_write actual_write = 32,ppos=32
[4241457.314305] char_device_open: major=10, minor=56
[4241457.314314] char_device_read,actual_readed = 64,pos=64
ppos
ppos不管在char_device_read还是char_device_write,方法初始时都是0。
为什么开始的时候都是0,这个作为一个TODO.
close
char_device_test.c中wrtie之后close fd之后,又重新open fd。为什么这样操作,这个作为一个TODO.
内核版本
Linux version 4.15.0-142-generic
参考
奔跑吧Linux内核入门篇第二版(这一节书中内容有错误,参考以上内容进行修复)