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

发布于:2025-02-25 ⋅ 阅读:(132) ⋅ 点赞:(0)

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);
};

常用的函数有:

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

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.汇总图

 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;
}


网站公告

今日签到

点亮在社区的每一天
去签到