1. 字符设备通过字符设备文件来存取。
"ls -l" 列出的第一列为“c”标识的,就是字符设备文件。
列出的设备文件项中由一个逗号分隔的两个数字,就是设备文件的主次设备号。
Q:字符设备文件与字符驱动程序如何建立起对应关系?
A:设备号。
主设备号:用来标识与设备文件相连的驱动程序,用来反映设备类型。
次设备号:被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。
Q: 内核中如何描述设备号?
A: dev_t:
实质是unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。
Q: 如何从dev_t中分解出主次设备号?
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。
loff_t f_pos ---- 文件读写位置
5.2 struct inode --- 用来记录文件的物理上的信息。
它和代表打开文件的file结构是不同的。
dev_t i_rdev --- 设备号。
5.3 struct file_operations --- 一个函数指针的集合。
表示能在设备上进行的操作。
结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留内NULL。
例: mem_fops
内核代码导读,应用程序如何访问驱动程序? read_write.c
7. 字符设备注册
字符设备的注册可分为如下3步:
7.1 分配 cdev;
struct cdev 的分配可以使用 cdev_alloc() 函数来完成。
7.2 初始化 cdev;
struct cdev的初始化使用 cdev_init()函数来完成。
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
参数:
cdev --- 待初始化的cdev结构
7.3 设备注册(添加 cdev);
struct cdev的注册使用cdev_add()函数来完成:
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
参数:
p --- 待添加到内核的字符设备结构
dev --- 设备号。
count --- 添加的设备个数。
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);
参数:
"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);
}