字符设备驱动

1. 字符设备通过字符设备文件来存取。
"ls -l" 列出的第一列为“c”标识的,就是字符设备文件。
列出的设备文件项中由一个逗号分隔的两个数字,就是设备文件的主次设备号。

Q:字符设备文件与字符驱动程序如何建立起对应关系?
A:设备号。

主设备号:用来标识与设备文件相连的驱动程序,用来反映设备类型。
次设备号:被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

Q: 内核中如何描述设备号?
A: dev_t:
实质是unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。

Q: 如何从dev_t中分解出主次设备号?

A:

major = MAJOR(dev_t dev);  --- 提取主设备号。

minor = MINOR(dev_t dev); ---提取次设备号。

dev_t = MKDEV(major, minor);  ---根据主、次设备号,获得设备号。

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

2. 分配主设备号
Q: Linux 内核如何给设备分配主设备号?    
A:    可以采用静态申请,动态分配两种方法。

2.1 静态申请
a. 根据Documentation/devices.txt, 确定出一个没有使用的主设备号。
b. 使用 register_chrdev_region() 函数注册设备号。
缺点:一旦驱动被广泛使用,这个随机的主设备号可能会导致设备号冲突,而使驱动程序无法注册。

int register_chrdev_region(dev_t from, unsigned count, const char *name);
功能: 申请使用从 from 开始的 count 个设备号(主设备号不变,次设备号增加)。

参数:
from --- 希望申请使用的设备号;
count --- 希望申请使用设备号的数目;
name --- 设备名(体现在/proc/devices)

2.2 动态分配
使用 alloc_chrdev_region() 分配设备号,让内核来分配一个可以使用的设备号。
缺点:无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号)。
解决办法:安装驱动后,从 /proc/devices 中查询设备号。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

功能:请求内核动态分配 count 个设备号,且次设备号从baseminor开始。
参数:
dev --- 分配到的设备号,
baseminor --- 起始次设备号。
count --- 需要分配的设备号数目。
name --- 设备名(体现在 /proc/devices )

3. 注销设备号
无论使用何种方法分配设备号,都应该在不再使用它们时释放这些设备号。
void unregister_chrdev_region(dev_t from, unsigned count);
功能: 释放从 from 开始的 count 个设备号。

4. 创建设备文件

4.1 使用mknod 命令创建


mknod filename type major minor

filename --- 设备文件名
type --- 设备文件类型, 主要有c(char), b(block)两个类型
major --- 主设备号
minor --- 次设备号
例: mknod serial0 c 100 0

4.2 自动创建 --- 后面介绍

5. 几个重要结构

Linux 字符设备驱动程序设计中,有3种非常重要的数据结构:

5.1 struct file --- 代表一个打开的文件。
系统中每个打开的文件,在内核空间都有一个关联的 struct file。

它由内核在打开文件时创建,在文件关闭后释放。表示文件在内核中的信息。

struct file {
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;   //用来保存文件路径
#define f_dentry	f_path.dentry
#define f_vfsmnt	f_path.mnt
	const struct file_operations	*f_op;   //操作文件的方法
	spinlock_t		f_lock;  /* f_ep_links, f_flags, no IRQ */  //操作该文件时可用的文件锁
#ifdef CONFIG_SMP
	int			f_sb_list_cpu;  //多核
#endif
	atomic_long_t		f_count;   //打开次数计数
	unsigned int 		f_flags;   //打开标志 flags
	fmode_t			f_mode;   //文件属性
	loff_t			f_pos;    //指针当前位置
	struct fown_struct	f_owner;  //file owner, 用来记录文件所有者信息uid, pid等。
	const struct cred	*f_cred;  //security related 信息
	struct file_ra_state	f_ra;     // ra --- readahead 文件 预读取 状态信息

	u64			f_version;   //版本号
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;    //私有信息

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;   //链表,可以使用fs/eventpoll.c 来连接
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;   //内存地址空间 文件存储信息
#ifdef CONFIG_DEBUG_WRITECOUNT
	unsigned long f_mnt_write_state; 
#endif
};
重要成员:

loff_t f_pos   ---- 文件读写位置

struct file_operations *f_op

fmode_t fmode; 有以下一些定义

/*
 * flags in file.f_mode.  Note that FMODE_READ and FMODE_WRITE must correspond
 * to O_WRONLY and O_RDWR via the strange trick in __dentry_open()
 */

/* file is open for reading */
#define FMODE_READ		((__force fmode_t)0x1)
/* file is open for writing */
#define FMODE_WRITE		((__force fmode_t)0x2)
/* file is seekable */
#define FMODE_LSEEK		((__force fmode_t)0x4)
/* file can be accessed using pread */
#define FMODE_PREAD		((__force fmode_t)0x8)
/* file can be accessed using pwrite */
#define FMODE_PWRITE		((__force fmode_t)0x10)
/* File is opened for execution with sys_execve / sys_uselib */
#define FMODE_EXEC		((__force fmode_t)0x20)
/* File is opened with O_NDELAY (only set for block devices) */
#define FMODE_NDELAY		((__force fmode_t)0x40)
/* File is opened with O_EXCL (only set for block devices) */
#define FMODE_EXCL		((__force fmode_t)0x80)
/* File is opened using open(.., 3, ..) and is writeable only for ioctls
   (specialy hack for floppy.c) */
#define FMODE_WRITE_IOCTL	((__force fmode_t)0x100)

/*
 * Don't update ctime and mtime.
 *
 * Currently a special hack for the XFS open_by_handle ioctl, but we'll
 * hopefully graduate it to a proper O_CMTIME flag supported by open(2) soon.
 */
#define FMODE_NOCMTIME		((__force fmode_t)0x800)

/* Expect random access pattern */
#define FMODE_RANDOM		((__force fmode_t)0x1000)

/* File is huge (eg. /dev/kmem): treat loff_t as unsigned */
#define FMODE_UNSIGNED_OFFSET	((__force fmode_t)0x2000)

/* File was opened by fanotify and shouldn't generate fanotify events */
#define FMODE_NONOTIFY		((__force fmode_t)0x1000000)

unsigned int f_flags;   有如下一些定义:

/*
 * FMODE_EXEC is 0x20
 * FMODE_NONOTIFY is 0x1000000
 * These cannot be used by userspace O_* until internal and external open
 * flags are split.
 * -Eric Paris
 */

/*
 * When introducing new O_* bits, please check its uniqueness in fcntl_init().
 */

#define O_ACCMODE	00000003
#define O_RDONLY	00000000
#define O_WRONLY	00000001
#define O_RDWR		00000002
#ifndef O_CREAT
#define O_CREAT		00000100	/* not fcntl */
#endif
#ifndef O_EXCL
#define O_EXCL		00000200	/* not fcntl */
#endif
#ifndef O_NOCTTY
#define O_NOCTTY	00000400	/* not fcntl */
#endif
#ifndef O_TRUNC
#define O_TRUNC		00001000	/* not fcntl */
#endif
#ifndef O_APPEND
#define O_APPEND	00002000
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK	00004000       //非阻塞
#endif
#ifndef O_DSYNC
#define O_DSYNC		00010000	/* used to be O_SYNC, see below */
#endif
#ifndef FASYNC
#define FASYNC		00020000	/* fcntl, for BSD compatibility */
#endif
#ifndef O_DIRECT
#define O_DIRECT	00040000	/* direct disk access hint */
#endif
#ifndef O_LARGEFILE
#define O_LARGEFILE	00100000
#endif
#ifndef O_DIRECTORY
#define O_DIRECTORY	00200000	/* must be a directory */
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW	00400000	/* don't follow links */
#endif
#ifndef O_NOATIME
#define O_NOATIME	01000000
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC	02000000	/* set close_on_exec */
#endif

/*
 * Before Linux 2.6.33 only O_DSYNC semantics were implemented, but using
 * the O_SYNC flag.  We continue to use the existing numerical value
 * for O_DSYNC semantics now, but using the correct symbolic name for it.
 * This new value is used to request true Posix O_SYNC semantics.  It is
 * defined in this strange way to make sure applications compiled against
 * new headers get at least O_DSYNC semantics on older kernels.
 *
 * This has the nice side-effect that we can simply test for O_DSYNC
 * wherever we do not care if O_DSYNC or O_SYNC is used.
 *
 * Note: __O_SYNC must never be used directly.
 */
#ifndef O_SYNC
#define __O_SYNC	04000000
#define O_SYNC		(__O_SYNC|O_DSYNC)
#endif

#ifndef O_NDELAY
#define O_NDELAY	O_NONBLOCK
#endif

5.2 struct inode  --- 用来记录文件的物理上的信息。
它和代表打开文件的file结构是不同的。

一个文件可以对应多个 file 结构,但只有一个 inode 结构

struct inode {
	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;
	struct list_head	i_dentry;
	unsigned long		i_ino;
	atomic_t		i_count;
	unsigned int		i_nlink;
	uid_t			i_uid;
	gid_t			i_gid;
	dev_t			i_rdev;
	unsigned int		i_blkbits;
	u64			i_version;
	loff_t			i_size;
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	blkcnt_t		i_blocks;
	unsigned short          i_bytes;
	umode_t			i_mode;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct mutex		i_mutex;
	struct rw_semaphore	i_alloc_sem;
	const struct inode_operations	*i_op;
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct super_block	*i_sb;
	struct file_lock	*i_flock;
	struct address_space	*i_mapping;
	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

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

	unsigned int		i_flags;

#ifdef CONFIG_IMA
	/* protected by i_lock */
	unsigned int		i_readcount; /* struct files open RO */
#endif
	atomic_t		i_writecount;
#ifdef CONFIG_SECURITY
	void			*i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
	void			*i_private; /* fs or device private pointer */
};
重要成员:

dev_t i_rdev --- 设备号。

5.3 struct file_operations --- 一个函数指针的集合。
表示能在设备上进行的操作。
结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留内NULL。

这个结构,把应用层的操作函数,转化成驱动层相关的操作函数。

/*
 * NOTE:
 * all file operations except setlease can be called without
 * the big kernel lock held in all filesystems.
 */
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 (*readdir) (struct file *, void *, filldir_t);
	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 *, 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 **);
};

例: mem_fops

struct file_operations mem_fops = {
.owner = THIS_MODULE,
.llseek = mem_seek,
.read = mem_read,
.write = mem_write,
.ioctl = mem_ioctl,
.open = mem_open,
.release = mem_release,
};
6. 应用 - 驱动模型,
内核代码导读,应用程序如何访问驱动程序? read_write.c
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
    ssize_t ret;

    if (!(file->f_mode & FMODE_READ))
        return -EBADF;
    if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
        return -EINVAL;
    if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
        return -EFAULT;

    ret = rw_verify_area(READ, file, pos, count);
    if (ret >= 0) {
        count = ret;
        if (file->f_op->read)
            ret = file->f_op->read(file, buf, count, pos);  //调用到file 结构中的 f_op 中的 .read.
        else
            ret = do_sync_read(file, buf, count, pos);
        if (ret > 0) {
            fsnotify_access(file);
            add_rchar(current, ret);
        }
        inc_syscr(current);
    }

    return ret;
}

7.  字符设备注册

字符设备使用 struct cdev 来描述。

struct cdev {
	struct kobject kobj;   //设备模型中对应的字符设备对象
	struct module *owner;   //该字符设备的拥有者
	const struct file_operations *ops;  //该字符设备所支持的方法
	struct list_head list;   //该字符设备在 所有字符设备链表中的描述
	dev_t dev;   //该字符设备的设备号
	unsigned int count;  //引用计数
};

字符设备的注册可分为如下3步:

7.1  分配 cdev;
struct cdev 的分配可以使用 cdev_alloc() 函数来完成。

struct cdev *cdev_alloc(void);

/**
 * cdev_alloc() - allocate a cdev structure
 *
 * Allocates and returns a cdev structure, or NULL on failure.
 */
struct cdev *cdev_alloc(void)
{
	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); //申请空间
	if (p) {
		INIT_LIST_HEAD(&p->list);  //添加进字符设备链表
		kobject_init(&p->kobj, &ktype_cdev_dynamic);   //实际上是使用了kobject_init 来完成的,在linux设备模型中的建立对象,并初始化。
	}
	return p;  //返回一个 字符设备结构体 cdev.
}

7.2 初始化 cdev;

struct cdev的初始化使用 cdev_init()函数来完成。
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
参数:
cdev --- 待初始化的cdev结构

fops --- 设备对应的操作函数集。

/**
 * cdev_init() - initialize a cdev structure
 * @cdev: the structure to initialize
 * @fops: the file_operations for this device
 *
 * Initializes @cdev, remembering @fops, making it ready to add to the
 * system with cdev_add().
 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);  //设备结构体置0
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops; //赋值操作方法
}

7.3 设备注册(添加 cdev);

struct cdev的注册使用cdev_add()函数来完成:
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
参数:
p --- 待添加到内核的字符设备结构
dev --- 设备号。
count --- 添加的设备个数。

/**
 * cdev_add() - add a char device to the system
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this
 *         device
 *
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.
 */
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;  //设备号
	p->count = count;   //设备数量
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);  //添加
}

8. 设备操作实现

完成了驱动程序的注册,下一步就是实现设备所支持的操作:
即实现f_ops 结构中的函数:

8.1 int (*open)(struct inode *, struct file *)
在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方式,
如果该项为NULL,则设备打开操作永远成功。
open方法是驱动程序用来为以后的操作,进行初始化准备工作,内容如下:
a. 初始化设备;
b. 标明次设备号;

8.2 void(*release)(struct inode *, struct file *)

当设备文件被关闭时调用这个函数,关闭设备
与open()一样, release也可以没有。

8.3 ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
从设备中读取数据。把设备中的数据读到用户空间。
一般定义为:
ssize_t xxx_read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
参数:
filp --- 文件指针
count --- 请求传输的数量
buff --- 指向数据缓存
offp --- 指出文件当前的访问位置。

8.4 ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
向设备发送数据,将数据传递给驱动程序。
一般定义为:
ssize_t xxx_write(struct file *filp, char __user *buff, size_t count, loff_t *offp);
参数与 xxx_read 一样。

注意: read 和write方法中buff参数,是用户空间指针,它不能被内核代码直接引用。
因为用户控件指针在内核空间可能根本是无效的 --- 没有该地址的映射。

内核提供了专门的函数用于访问用户控件的指针,如:
int copy_from_user(void *to, const void __user *from, int n);  //对应write
int copy_to_user(void __user *to, const void *from, int n);  //对应read
 

8.5 unsigned int (*poll)(struct file *, struct poll_table_struct *);
对应select系统调用

8.6 int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long)
控制设备。

8.7 int (*mmap)(struct file *, struct vm_area_struct *)
将设备映射到进程虚拟地址空间中。

8.8 off_t (*llseek)(struct file *, loff_t, int)
修改文件的当前读写位置,并将新位置作为返回值。

9. 设备注销
字符设备的注销使用 cdev_del() 函数完成。
int cdev_del(struct cdev *p);
参数:

p --- 要注销的字符设备结构。

/**
 * cdev_del() - remove a cdev from the system
 * @p: the cdev structure to be removed
 *
 * cdev_del() removes @p from the system, possibly freeing the structure
 * itself.
 */
void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count); //释放内存
	kobject_put(&p->kobj);  //把该字符设备信息,从设备模型中移除
}
static void cdev_unmap(dev_t dev, unsigned count)
{
	kobj_unmap(cdev_map, dev, count);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值