LDD学习笔记 -- Linux字符设备驱动

这篇具有很好参考价值的文章主要介绍了LDD学习笔记 -- Linux字符设备驱动。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

字符驱动程序用于与Linux内核中的设备进行交互;
字符设备指的是像内存区域这样的硬件组件,通常称为伪设备;
用户空间应用程序通常使用open read write等系统调用与这些设备通信;

虚拟文件系统 VFS

把用户空间的系统调用连接到设备驱动的系统调用实现方法上。
内核的虚拟文件系统 virtual file system,在内核空间
设备驱动需要使用内核的API向虚拟文件系统注册

设备号

Major numbers(指示特定的驱动) + Minor numbers(表示指定的设备文件)

设备创建时候在VFS注册设备号,虚拟文件系统,将设备文件的设备号与驱动程序列表进行比较,选择正确的驱动程序,并将用户请求连接到对应驱动程序的文件操作方法。

相关Kernel APIs

kernel functions and data structures(Creation) (Deletion) kernel header file
alloc_chrdev_region() unregister_chrdev_region() include/linux/fs.h
cdev_init() cdev_add() cdev_del() include/linux/cdev.h
device_creat() class_creat() device_destory() class_destory include/linux/device.h
copy_to_user() copy_from_user() include/linux/uaccess.h
VFS structure definitions include/linux/cdev.h

动态申请设备号

alloc_chrdev_region() 可以动态申请主设备号,保证唯一性,传输设备号(dev_t [u32])地址和次设备号起始(一般0)和个数。

dev_t device_number;  //32bit
int minor_no = MINOR(device_number);  //后20bit  `kdev_t.h`
int major_no = MAJOR(device_number);  //前12bit

MKDEV(int major, int minor);

动态创建设备文件

当收到ueventudev根据uevent内存储的细节在dev目录下创建设备文件。

class_create :在sysf中创建一个目录/sys/Class/<your_class_name>
device_create:在上面目录下使用设备名创建一个子目录/sys/Class/<your_class_name>/<your_device_name> /dev 这里的dev文件存储设备名主副设备号等
udev:用户空间的应用,动态创建设备文件/sys/Class/<your_class_name>/<your_device_name> /dev --> dev/your_device_name

内核空间和用户空间的数据交换

用户空间的指针不是完全可信的,用户地址空间有时可能无效,虚拟内存管理器可以交换出这些内存位置。
内核级代码不能直接引用用户级内存指针;
使用内核数据复制工具copy_to_user copy_from_user。工具会检查用户空间指针是否有效

系统调用方法

read

用户级进程执行read系统调用从文件中读取。文件可以是普通文件,也可以是一个设备文件(处理具体设备)。

例如前面的伪字符设备,有一块内存数组(设备内存buffer)。当用户程序在该设备文件上发出read系统调用时,应该将数据从设备buffer传到用户buffer。该数据拷贝发生在内核端到用户端。

write

将数据从用户空间复制到内核空间,
用户程序想把一些数据写入设备内存buffer。

lseek

改变f_pos(struct file)变量的位置,将文件位置指针向前/向后移动。

写一个伪字符设备驱动

  1. 动态申请设备号
  2. 创建cdev结构体变量和file_operiations结构体变量
  3. 使用fops初始化字符设备结构体变量
  4. 向内核VFS注册设备
  5. 实现file operiation的方法
  6. 初始化file operiation变量
  7. 创建设备文件 class_create() device_create()
  8. 驱动清理函数功能实现
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <uapi/asm-generic/errno-base.h>

#define DEV_MEM_SIZE    512

/* pseudo device's memory */
char device_buffer[DEV_MEM_SIZE];

/* This hold the device number */
dev_t device_number;

/* Cdev variable */
struct cdev pcd_cdev;

loff_t pcd_llseek(struct file *filp, loff_t offset, int whence)
{
        pr_info("%s\n", __func__);
        loff_t temp;

        switch (whence)
        {
                case SEEK_SET:
                        if ((offset > DEV_MEM_SIZE) || (offset < 0))
                                return -EINVAL;
                        filp->f_pos = offset;
                        break;
                case SEEK_CUR:
                        temp = filp->f_pos + offset;
                        if ((temp > DEV_MEM_SIZE) || (offset < 0))
                                return -EINVAL;
                        filp->f_pos = temp;
                        break;
                case SEEK_END:
                        temp = DEV_MEM_SIZE + offset;
                        if ((temp > DEV_MEM_SIZE) || (offset < 0))
                                return -EINVAL;
                        filp->f_pos = temp;
                        break;
                
                default:
                        return -EINVAL;

        }

        pr_info("New value of the file position = %lld\n", filp->f_pos);

        return filp->f_pos;
        // return 0;

}

ssize_t pcd_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
{
        pr_info("%s :Read requested for %zu bytes\n", __func__, count);

        if((*f_pos + count) > DEV_MEM_SIZE)
                count = DEV_MEM_SIZE - *f_pos;

        if(copy_to_user(buff, &device_buffer[*f_pos], count)){
                return -EFAULT;
        }

        *f_pos += count;
        pr_info("Number of bytes successful read = %zu\n", count);
        pr_info("Update file position = %lld\n", *f_pos);

        return count;
}
ssize_t pcd_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
{
        pr_info("%s :Write requested for %zu bytes, current file position = %lld\n", __func__, count, *f_pos);

        if((*f_pos + count) > DEV_MEM_SIZE)
                count = DEV_MEM_SIZE - *f_pos;

        if(!count)
                return -ENOMEM;

        if(copy_from_user(&device_buffer[*f_pos], buff, count)){
                return -EFAULT;
        }

        *f_pos += count;
        pr_info("Number of bytes successful writtens = %zu\n", count);
        pr_info("Update file position = %lld\n", *f_pos);
        return count;
}
int pcd_open(struct inode *inode, struct file *filp)
{
        pr_info("%s\n", __func__);
        return 0;
}
int pcd_release(struct inode *inode, struct file *filp)
{
        pr_info("%s\n", __func__);
        return 0;
}


/* file operations variable */
struct file_operations pcd_fops = {
        .open = pcd_open,
        .write = pcd_write,
        .read = pcd_read,
        .llseek = pcd_llseek,
        .release = pcd_release,
        .owner = THIS_MODULE
};

struct class *class_pcd;
struct device *device_pcd;

static int __init pcd_driver_init(void)
{
        pr_info("pcd_driver_init\n");
        
        /* 1. Dynamically allocate a device number */
        alloc_chrdev_region(&device_number, 0, 1, "pcd");

        pr_info("Device number <major>:<minor> = %d:%d\n", MAJOR(device_number), MINOR(device_number));

        /* 2. Initialize the cdev structure with fops */
        cdev_init(&pcd_cdev, &pcd_fops);

        /* 3. Register a device(cdev structure) with VFS */
        pcd_cdev.owner = THIS_MODULE;
        cdev_add(&pcd_cdev, device_number, 1);

        /* creat device class under /sys/class / */
        class_pcd = class_create(THIS_MODULE, "pcd_class");

        /* populate the sysfs with device information */
        device_pcd = device_create(class_pcd, NULL, device_number, NULL, "pcd");

        pr_info("Module init was successful\n");

        return 0;
}

/* This is module clean-up entry point */
static void __exit pcd_driver_exit(void)
{
        pr_info("my hello module exit\n");
        device_destroy(class_pcd, device_number);
        class_destroy(class_pcd);
        cdev_del(&pcd_cdev);
        unregister_chrdev_region(device_number, 1);
        pr_info("module unloaded\n");
}

/* registration */
module_init(pcd_driver_init);
module_exit(pcd_driver_exit);

/* This is description information about the module */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NAME");
MODULE_DESCRIPTION("A pseudo device driver");

在主机上测试pcd(HOST)

使用echo 命令测试向PCD写数据
使用cat命令测试从PCD读数据

LDD学习笔记 -- Linux字符设备驱动,Linux,学习,笔记,linux
LDD学习笔记 -- Linux字符设备驱动,Linux,学习,笔记,linux

在目标板上测试pcd(TARGET)

需要在用户空间写一个应用程序(测试应用)来测试字符设备驱动程序。使用对应目标板的编译工具链编译.c文件成目标板上的可执行文件,有没有.exe后缀都可,自己知道就行。

arm-buildroot-linux-gnueabihf-gcc ./pcd_drv_test.c -o pcd_dev_test

将上面设备驱动编译出的目标板的.ko文件和我们的测试应用文件都放到目标板上。


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./pcd_drv_test -w hello fpn233~
 * ./pcd_drv_test -r
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[512];
	int len;
	
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/pcd", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/pcd\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 512 ? len : 512;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 512);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	
	close(fd);
	
	return 0;
}

LDD学习笔记 -- Linux字符设备驱动,Linux,学习,笔记,linux

LDD学习笔记 -- Linux字符设备驱动,Linux,学习,笔记,linux文章来源地址https://www.toymoban.com/news/detail-787395.html

到了这里,关于LDD学习笔记 -- Linux字符设备驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 嵌入式Linux(8):字符设备驱动--注册字符类设备

    杂项设备 注册杂项设备: 注销杂项设备: 字符类设备 文件:include/linux/cdev.h 步骤流程: 定义一个cdev结构体。 使用cdev_init函数初始化cdev结构体成员变量。 参数: 第一个:要初始化的cdev结构体 第二个:文件操作集: cdev-ops = fops;//实际就是把文件操作集写ops 使用cdev_add函数

    2023年04月22日
    浏览(40)
  • Linux 驱动之字符设备

    什么是设备号 Linux 规定每一个字符设备或者块设备都必须有一个专属的设备号。一个设备号由主设备号和次设备号组成。主设备号用来表示某一类驱动,如鼠标,键盘都可以归类到 USB 驱动中。而次设备号是用来表示这个驱动下的各个设备。比如第几个鼠标,第几个键盘等。

    2024年02月16日
    浏览(30)
  • 【嵌入式Linux学习笔记】platform设备驱动和input子系统

    对于Linux这种庞大的操作系统,代码重用性非常重要,所以需要有相关的机制来提升效率,去除重复无意义的代码,尤其是对于驱动程序,所以就有了platform和INPUT子系统这两种工作机制。 学习视频地址:【正点原子】STM32MP157开发板 platform 驱动框架分为总线、设备和驱动。总

    2024年02月07日
    浏览(44)
  • Linux 驱动之高级字符设备

    什么是IO呢? IO 的英文全称是 input 和output,翻译过来就是输入和输出。 在冯.诺依曼结构中,将计算机分成分为5个部分: 运算器、控制器、存储器、输入设备、输出设备 。其中输入设备指的是向计算机输入数据或者信息,如鼠标,键盘都是输入设备。输出设备指的是用于接收

    2023年04月14日
    浏览(32)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(22)
  • Linux下字符设备驱动开发以及流程介绍

    首先我们介绍一下什么是字符设备,然后讲解一下字符设备开发的具体的流程,分别详细介绍每一个流程中涉及到的结构体以及知识点,最后我们编写代码实现字符设备的开发以及测试。 Linux内核设计哲学是把所有的东西都抽象成文件进行访问,这样对设备的访问都是通过文

    2024年02月01日
    浏览(30)
  • Linux字符设备驱动(设备文件,用户空间与内核空间进行数据交互,ioctl接口)

    在Linux系统中“一切皆文件”,上一篇讲述了cdev结构体就描述了一个字符设备驱动,主要包括设备号和操作函数集合。但是要怎么操作这个驱动呢?例如,使用open()该打开谁,read()该从哪读取数据等等。所以就需要创建一个设备文件来代表设备驱动。 应用程序要操纵外部硬件

    2024年02月12日
    浏览(29)
  • 【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程

    开发环境: windows + ubuntu18.04 + 迅为rk3568开发板 相较于字符设备,杂项设备有以下两个优点: 节省主设备号:杂项设备的主设备号固定为 10,在系统中注册多个 misc 设备驱动时,只需使用子设备号进行区分即可。 使用简单:相比如普通的字符设备驱动, misc驱动只需要将基本信

    2024年01月21日
    浏览(35)
  • Linux驱动开发笔记(四):设备驱动介绍、熟悉杂项设备驱动和ubuntu开发杂项设备Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/134533533 红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中… 上一篇:《Linux驱动开发笔记(三

    2024年02月05日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包