当前位置: 首页 > article >正文

Linux驱动学习(三)--字符设备架构与注册

1.内核如何维护设备号的?

chrdevs指针数组

在内核中有一个重要的全局变量:chrdevs指针数组,位于char_dev.c文件中

 chrdevs指针数组的每一个成员指向一个char_device_struct结构体,该结构体中,最重要的变量是cdev指针

cdev结构体

cdev结构体:

 该结构体定义位于cdev.h

在cdev中,kobj和owner成员不用太关注,内核会自己填充;dev填充设备号的成员;我们需要关心的最重要的成员是file_operations *ops

file_operations结构体

file_operations结构(位于fs.h)

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

常用的函数有:

  • owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE

  • llseek 函数用于修改文件当前的读写位置

  • read 函数用于读取设备文件

  • write 函数用于向设备文件写入(发送)数据

  • poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写

  • unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应

  • compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl

  • mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制

  • open 函数用于打开设备文件

  • release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应

  • fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中

cdev之间通过链表串起来 

需要我们自己实现的东西有:

  • cdev
  • read
  • write

 2.用户层如何找到驱动的?

 答案:借助文件系统

mknod命令

创建设备节点命令:

mknod /dev/loh c 237 0
  • loh:设备名称
  • c:字符设备
  • 237:主设备号
  • 0:次设备号

输入该命令过后,在内核中会产生一个inode结构体,inode会记录文件的相关信息

inode结构体

inode结构体(位于fs.h)

struct inode {
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;

#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif

	const struct inode_operations	*i_op;
	struct super_block	*i_sb;
	struct address_space	*i_mapping;

#ifdef CONFIG_SECURITY
	void			*i_security;
#endif

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;
	/*
	 * Filesystems may only read i_nlink directly.  They shall use the
	 * following functions for modification:
	 *
	 *    (set|clear|inc|drop)_nlink
	 *    inode_(inc|dec)_link_count
	 */
	union {
		const unsigned int i_nlink;
		unsigned int __i_nlink;
	};
	dev_t			i_rdev;
	loff_t			i_size;
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes;
	unsigned int		i_blkbits;
	blkcnt_t		i_blocks;

#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif

	/* Misc */
	unsigned long		i_state;
	struct mutex		i_mutex;

	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	struct hlist_node	i_hash;
	struct list_head	i_wb_list;	/* backing dev IO list */
	struct list_head	i_lru;		/* inode LRU list */
	struct list_head	i_sb_list;
	union {
		struct hlist_head	i_dentry;
		struct rcu_head		i_rcu;
	};
	u64			i_version;
	atomic_t		i_count;
	atomic_t		i_dio_count;
	atomic_t		i_writecount;
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct file_lock	*i_flock;
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
	};

	__u32			i_generation;

#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct hlist_head	i_fsnotify_marks;
#endif

#ifdef CONFIG_IMA
	atomic_t		i_readcount; /* struct files open RO */
#endif
	void			*i_private; /* fs or device private pointer */
};

 其中比较重要我们需要关心的是设备号成员:

有了这个设备号,我们就可以找到 chrdevs数组成员

3.汇总图


 

 此图参考:Linux字符设备驱动开发(一)

 4.字符设备注册

常用相关函数:

  1. void cdev_init(struct cdev *, const struct file_operations *);
  2. int cdev_add(struct cdev *, dev_t, unsigned);
  3. void cdev_del(struct cdev *);

 实验

加载模块

 mknod创建设备节点

应用程序

驱动代码

/*
 *cdev.c
 *Original Author: luoyunheng, 2025-02-20
 *
 * Linux驱动之字符设备注册
*/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>

static int major = 222;
static int minor = 0;

static dev_t devno;
static struct cdev dev;

static int hello_open(struct inode *inode, struct file *filep)
{
    printk("hello_open()\n");
    return 0;
}

static struct file_operations hello_ops = 
{
    .open = hello_open,
};

static int hello_init(void)
{
    int result;
    int error;

    printk("hello_init\n");

    devno = MKDEV(major, minor);
    result = register_chrdev_region(devno, 1, "loh");
    if (result < 0 ) {
        printk("register dev number failed\n");
        return result;
    }

    cdev_init(&dev, &hello_ops);
    error = cdev_add(&dev, devno, 1);
    if (error < 0) {
        printk("cdev_add fail\n");
        unregister_chrdev_region(devno, 1);
        return error;
    }

    return 0;
}

static void hello_exit(void)
{
    printk("hello_exit\n");
    cdev_del(&dev);
    unregister_chrdev_region(devno, 1);
    return;
}

module_init(hello_init);
module_exit(hello_exit);

 应用程序代码

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

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

	fd = open("/dev/loh", O_RDWR);
	if(fd < 0) {
		perror("");
		return 0;
	}	

	return 0;
}


http://www.kler.cn/a/565317.html

相关文章:

  • 【c++语法基础】c/c++内存管理
  • MacPorts 创建自定义 Portfile 安装 RoadRunner
  • MySQL 创建指定IP用户并赋予全部权限(兼容8.0以下及8.0以上版本)
  • 使用 VSCode 代替 BeyondStudio for NXP 开发 JN 5169
  • Springboot 自动化装配的原理
  • vm和centos
  • 计算机视觉基础|轻量化网络设计:MobileNetV3
  • 云计算及其他计算
  • C++ 设计模式 十九:观察者模式 (读书 现代c++设计模式)
  • Spark技术系列(三):Spark算子全解析——从基础使用到高阶优化
  • 机器学习数学通关指南——泰勒公式
  • 鲲鹏麒麟离线安装Docker
  • Dify在Ubuntu20.04系统的部署
  • OSPF在校园网络的应用
  • Ollama使用笔记【更新ing】
  • MSSQL2022的一个错误:未在本地计算机上注册“Microsoft.ACE.OLEDB.16.0”提供程序
  • 代码随想录算法【Day57】
  • 笔记:大模型Tokens是啥?为啥大模型按Tokens收费?
  • Ubuntu2204下使用NVIDIA GeForce RTX 4090进行DeepSeek-R1-Distill-Llama-8B模型微调
  • Spark map与mapPartitions算子源码级深度解析