一、背景
字元裝置驅動是三種基本驅動類型(others:塊裝置,網絡裝置)之一,其基本特點是一個位元組一個位元組的以流式的形式進行裝置節點的通路,如序列槽(/dev/ttys0)、字元終端(/dev/console)等等。
在之後的章節介紹字元裝置中互相關聯的重要的資料結構和一些重要的API接口。同時,總結出一個通用的基本字元裝置驅動模闆。
二、裝置号與配置設定
三、重要資料結構
1.file_operations
file_operations 結構體定義如下。可以看到的是,這些函數指針對應于open、read、write等系統調用。當我們對裝置檔案節點進行相應的應用層調用時,該節點對應的如下檔案操作函數指針就會被調用到,進而完成裝置的操作。
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 (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
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 **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
};
2.file
目前,我們隻關注其中的f_op和private_date 這兩個成員。
對于file_operations指針來說,核心在執行open系統調用時對這個指針指派,進而完成核心file 句柄和f_op之間的綁定。之後相關的系統調用則可以通過該指針進行調用。
private_data 這個指針供驅動程式本身自行使用,通常它會儲存一些在跨系統調用中非常有用的狀态資訊或者資料結構,之後會看到例子。
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
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;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
3.inode
inode 結構對應于裝置檔案節點。對于當個檔案,可能會有多個打開的檔案描述符對應一個裝置檔案,但它們都指向單個的如下所示的inode結構,目前我們隻要關注i_rdev和i_cdev這個兩個元素。
struct inode {
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
/* ... */
dev_t i_rdev; /*<---*/
/* ... */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev; /*<---*/
char *i_link;
};
/* ... */
};
其中,i_rdev對應該節點的裝置編号,可以通過如下的宏以相容的方式獲得主/次裝置号:
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
static inline unsigned imajor(const struct inode *inode)
{
return MAJOR(inode->i_rdev);
}
i_cdev則表示字元裝置在核心中的具體内部結構,來一窺究竟,該結構體會在第四節碰到:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
好了,來總結下這幾個結構體之間的對應關系:

四、重要API
1. 字元裝置注冊類
靜态初始化函數,用來填充cdev結構體的fops。其中的kobject裝置模型的基礎結構,之後會涉及。對應着cdev_alloc為動态配置設定
/**
* 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);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
/**
* 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);
}
return p;
}
在設定好cdev結構體之後,通過調用cdev_add 來激活該裝置。注意到注釋中有提到,調用該函數後會立即激活該裝置,是以在調用該函數之前需要確定使用者已經可以正常的操作裝置。對應着有cdev_del API。
/**
* 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)
{
int error;
p->dev = dev;
p->count = count;
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
結合上面的API,裝置注冊的示例代碼如下:
struct scull_dev {
/* ... */
struct cdev cdev; /* Char device structure */
};
/*
* Set up the char_dev structure for this device.
*/
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
2. 檔案操作類
2.1 open方法
當向系統注冊了cdev 之後,當使用者想要通路裝置檔案時,open 系統調用會回調cdev->ops->open,并以目前的inode與filp為入口參數。進而裝置就有機會做初始化工作。
在open 中,需要完成如下的工作:
1.檢測裝置是否已準備好,如果沒有準備好,傳回錯誤碼,友善使用者檢測錯誤
2.如果裝置是首次打開,則對其進行初始化(比如可以檢視相應的指針是否已設定)
3.如有必要,更新f_op指針(不常用)
4.配置設定并填寫filp->private_data,至于為什麼,因為其他的檔案操作接口隻存在filp,而沒有了inode結構
作為一個例子,參考scull_open。其中通過container_of獲得我們需要的外圍大結構體(container_of的說明放在六、補充裡,但其實很常用奧~)
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* add your code here*/
return 0; /* success */
}
2.2 release方法
對應于open,存在着release方法來執行清理工作,其完成如下工作:
1.釋放open配置設定的,存在于filp->private裡動态配置設定的内容
2.在最後一次關閉操作時關閉裝置。
ldd3提到了幾個算是常識點,
1.dup或者fork并不會調用open系統調用而獲得filp副本(指針),當對應的程序退出調用close系統調用時,
并不會真正的調用裝置release方法。原因是核心在獲得副本時,僅僅是增加了filp的引用計數。
隻有在引用計數歸0,才會真正調用release 方法;
2.在linux 中,程序會殺死時,系統會自動調用打開的file的close系統調用
2.3 write and read 方法
write 和read 方法主要實作使用者資料和裝置資料之間的互動,核心提供了如下的兩個inline函數來進行資料互動:
static inline unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (access_ok(VERIFY_READ, from, n))
n = __copy_from_user(to, from, n);
else /* security hole - plug it */
memset(to, 0, n);
return n;
}
static inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (access_ok(VERIFY_WRITE, to, n))
n = __copy_to_user(to, from, n);
return n;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
/* ... */
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
/* ... */
}
五、模闆
/*cdev_template.h*/
/*______________________________________________*/
#ifndef TEMPLATE_MAJOR
#define TEMPLATE_MAJOR0 /* dynamic major by default */
#endif
#ifndef TEMPLATE_NR_DEVS
#define TEMPLATE_NR_DEVS4 /* scull0 through scull3 */
#endif
struct template_dev {
/*
add your data here
*/
struct cdev cdev; /* Char device structure */
};
/*cdev_template.c*/
/*______________________________________________*/
/*Include file*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_*_user */
/*
* Our parameters which can be set at load time.
*/
int template_major = SCULL_MAJOR;
int template_minor = 0;
int template_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */
module_param(template_major, int, S_IRUGO);
module_param(template_minor, int, S_IRUGO);
module_param(template_nr_devs, int, S_IRUGO);
MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
struct template_dev *template_devices; /* allocated in template_init_module */
/*
* Open and close
*/
int template_open(struct inode *inode, struct file *filp)
{
struct template_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct template_dev, cdev);
filp->private_data = dev; /* for other methods */
/*
add your code here
*/
return 0; /* success */
}
int template_release(struct inode *inode, struct file *filp)
{
/*
add your code here
*/
}
/*
* Data management: read and write
*/
ssize_t template_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
/*
add your code here
*/
}
ssize_t template_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
/*
add your code here
*/
}
/*
* The ioctl() implementation
*/
int template_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
/*
add your code here
*/
}
struct file_operations template_fops = {
.owner = THIS_MODULE,
.read = template_read,
.write = template_write,
.ioctl = template_ioctl,
.open = template_open,
.release = template_release,
};
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(template_major, template_minor);
/* Get rid of our char dev entries */
if (template_devices) {
for (i = 0; i < template_nr_devs; i++) {
/*
add your clean code here
*/
cdev_del(&template_devices[i].cdev);
}
kfree(template_devices);
}
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);
}
/*
* Set up the char_dev structure for this device.
*/
static void template_setup_cdev(struct template_dev *dev, int index)
{
int err, devno = MKDEV(template_major, template_minor + index);
cdev_init(&dev->cdev, &template_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &template_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
int template_init_module(void)
{
int result, i;
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (template_major) {
dev = MKDEV(template_major, template_minor);
result = register_chrdev_region(dev, template_nr_devs, "template");
} else {
result = alloc_chrdev_region(&dev, template_minor, template_nr_devs,
"template");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", template_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
template_devices = kmalloc(template_nr_devs* sizeof(struct template_dev), GFP_KERNEL);
if (!template_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(template_devices , 0, template_nr_devs* sizeof(struct template_dev));
/*
add your code here before cdev register
*/
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
template_setup_cdev(&template_devices[i], i);
}
return 0; /* succeed */
fail:
template_cleanup_module();
return result;
}
module_init(template_init_module);
module_exit(template_cleanup_module);
六、補充
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})