Johannes 《Linux内核模块与设备驱动开发:编写Linux驱动程序》 (7)open,release (8) write read

视频资料:https://www.bilibili.com/video/BV18SE26cEjn?spm_id_from=333.788.videopod.sections&vd_source=92b7efa4daa5bb4fdcdc9f2db97c307b&p=4

代码:

https://github.com/Johannes4Linux/Linux_Driver_Tutorial

lesson7 字符设备的打开和释放

lesson8 字符设备的读取和写入

其实这两课和lesson5程序很接近,不过Johannes 在内核层之外,还提供了应用层测试的例程。

lesson7 内核层 hello_cdev.c

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>

static int major;

//inode 表示内核设备文件的结构体,靠它来获得主/附设备号,flip 代表打开的设备文件
static int my_open(struct inode *inode, struct file *filp)
{
    //主设备号  imajor(inode) iminor(inode) imajor/iminor 都是宏
	pr_info("hello_cdev - Major: %d, Minor %d\n", imajor(inode), iminor(inode));
    
    //当前打开文件的位置
	pr_info("hello_cdev - filp->f_pos: %lld\n", filp->f_pos);
	//设备文件的权限 ls -lh 命令里面的 rwx
    pr_info("hello_cdev - filp->f_mode: 0x%x\n", filp->f_mode);
    //设备文件的标志位 比如层test.c 里面的 O_RDWR/O_WRONLY 等
	pr_info("hello_cdev - filp->f_flags: 0x%x\n", filp->f_flags);

	return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
	pr_info("hello_cdev - File is closed\n");
	return 0;
}
//设备文件支持的设备操作,这里只有open/release 操作
static struct file_operations fops = {//实例化fops
	.open = my_open,
	.release = my_release,
};

static int __init my_init(void)
{
	major = register_chrdev(0, "hello_cdev", &fops);//注册字符设备
	if (major < 0) {
		pr_err("hello_cdev - Error registering chrdev\n");
		return major;
	}
	pr_info("hello_cdev - Major Device Number: %d\n", major);
	return 0;
}

static void __exit my_exit(void)
{
	unregister_chrdev(major, "hello_cdev");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Johannes 4Linux");
MODULE_DESCRIPTION("A sample driver for registering a character device");

和lesson5 对比,十分类似,lesson5 只实例化了.read 读,这里有实例化.open/.release,视频开始的9分钟对于设备文件这些做了相关说明。

视频第十分钟做了个应用层的程序test.c说明,看看代码就行,略过。

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv) {
	int fd;

	if (argc < 2) {
		printf("I need the file to open as an argument!\n");
		return 0;
	}

	fd = open(argv[1], O_RDONLY);

	if (fd < 0) {
		perror("open");
		return fd;
	}

	close (fd);

	fd = open(argv[1], O_RDWR | O_SYNC);

	if (fd < 0) {
		perror("open");
		return fd;
	}

	close (fd);

	fd = open(argv[1], O_WRONLY | O_NONBLOCK);

	if (fd < 0) {
		perror("open");
		return fd;
	}

	close (fd);
	return 0;
}

两个程序都写完了,视频开始编译驱动程序,insmod之后,mknod /dev/hello0 从236 0 ;mknod /dev/hello0 236 10 ; 这就造了两个字符设备 /dev/hello0 ,主设备号236,次设备号分别是0,10;最后在应用层跑测试,分别做这两个字符设备,打印不同的次设备号,不同模式,表明测试程序成功。

mknod 生成设备文件现在已经不推荐了,不过linux还保留这个方法。

这几个课程加起来就是对字符设备常用的操作开,关,读,写的操作,构成了字符设备的开发架构。我认为字符设备其实都可以理解为在这个架构上开发的。综合lesson3/lesson7 ,仿照写一个字符设备控制led开关的程序

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/gpio/consumer.h>


static int major;
static struct gpio_desc *led;

#define IO_LED 21

#define IO_OFFSET 0


static int my_open(struct inode *inode, struct file *filp)
{
        int status;
	led = gpio_to_desc(IO_LED + IO_OFFSET);
	if (!led) {
		printk("gpioctrl - Error getting pin %d\n", IO_LED);
		return -ENODEV;
	}
        status = gpiod_direction_output(led, 0);
	if (status) {
		printk("gpioctrl - Error setting pin %d to output\n", IO_LED);
		return status;
	}
	gpiod_set_value(led, 1);
	return 0;
}

static int my_release(struct inode *inode, struct file *filp)
{
	gpiod_set_value(led, 0);
	pr_info("hello_cdev - File is closed\n");
	return 0;
}

static struct file_operations fops = {
	.open = my_open,
	.release = my_release,
};

static int __init my_init(void)
{
	major = register_chrdev(0, "hello_cdev", &fops);
	if (major < 0) {
		pr_err("hello_cdev - Error registering chrdev\n");
		return major;
	}
	pr_info("hello_cdev - Major Device Number: %d\n", major);
	return 0;
}

static void __exit my_exit(void)
{

	unregister_chrdev(major, "hello_cdev");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("DOK");
MODULE_DESCRIPTION("A sample driver for led");

lesson8 代码

复制代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>

static int major;
static char text[64];//文本缓冲区
//flip 打开的设备文件 user_buf 数据空指针,本例没用 len 缓冲区长度  off 文件位置偏移里
static ssize_t my_read(struct file *filp, char __user *user_buf, size_t len, loff_t *off)
{
/* 
not_copied 从应用层读取的字符长度
to_copy = (len + *off) < sizeof(text) ? len : (sizeof(text) - *off) 计算需要复制的可复制字符长度,确保文本缓冲区位置不溢出 
delta =to_copy-not_copied 文本指针移动长度
*/
	int not_copied, delta, to_copy = (len + *off) < sizeof(text) ? len : (sizeof(text) - *off);

	pr_info("hello_cdev - Read is called, we want to read %ld bytes, but actually only copying %d bytes. The offset is %lld\n", len, to_copy, *off);

	if (*off >= sizeof(text))
		return 0;
    //复制文本缓冲区内容到用户空间,user_buf文本缓冲区  &text(*off)文本当前位置  to_copy 复制长度
	not_copied = copy_to_user(user_buf, &text[*off], to_copy);
    delta = to_copy - not_copied;
	if (not_copied) 
		pr_warn("hello_cdev - Could only copy %d bytes\n", delta);

	*off += delta;

	return delta;
}

static ssize_t my_write(struct file *filp, const char __user *user_buf, size_t len, loff_t *off)
{
	int not_copied, delta, to_copy = (len + *off) < sizeof(text) ? len : (sizeof(text) - *off);

	pr_info("hello_cdev - Write is called, we want to write %ld bytes, but actually only copying %d bytes. The offset is %lld\n", len, to_copy, *off);

	if (*off >= sizeof(text))
		return 0;

	not_copied = copy_from_user(&text[*off], user_buf, to_copy);
	delta = to_copy - not_copied;
	if (not_copied) 
		pr_warn("hello_cdev - Could only copy %d bytes\n", delta);

	*off += delta;
	return delta;
}

static struct file_operations fops = {
	.read = my_read,
	.write = my_write
};

static int __init my_init(void)
{
//动态配置字符设备,不需要设备号
	major = register_chrdev(0, "hello_cdev", &fops);
	if (major < 0) {
		pr_err("hello_cdev - Error registering chrdev\n");
		return major;
	}
	printk("hello_cdev - Major Device Number: %d\n", major);
	return 0;
}

static void __exit my_exit(void)
{
	unregister_chrdev(major, "hello_cdev");
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Johannes 4Linux");
MODULE_DESCRIPTION("A sample driver for registering a character device");

重点了解 copy_to_user /copy_from_user 两个函数实现应用层的读写控制内核层

这个和正点原子里面led 读写驱动很像了

相关推荐
kuroomi2 小时前
Docker 与容器基础
linux·运维·docker
fangjianj8 小时前
LAMP 项目部署
linux
mengge.cloud8 小时前
0909华为云计算类综合服务小白实验教程
linux·运维·服务器·网络·华为云·云计算
fangjianj9 小时前
自有服务及软件包
linux
风哥2号11 小时前
数据库教程FGMT03‑生产环境Linux+Oracle19c+ASM安装配置与项目实战
linux·数据库
一木 之林11 小时前
七、一-AI 工程实践、插件化调试与软件交付
java·linux·c++
zhengqweasd11 小时前
Linux网络(一):服务器数据包从网卡到应用程序,完整收包流程详解
linux·服务器·网络
脚踏实地,坚持不懈!11 小时前
Linux 6.18.7 内存分配与回收 —— 代码梳理
linux·运维·网络
byte轻骑兵11 小时前
【BlueZ 】hci 模块:用户态 HCI 层的核心封装与消息处理
linux·人工智能·bluez·电脑蓝牙·嵌入式蓝牙
Android系统攻城狮13 小时前
Linux Gstreamer深度解析之gst_audio_format_build_integer调用流程与实战(十二)
linux·运维·服务器·音视频·车载音视频·gstreamer进阶