分类: LINUX
2014-07-18 19:18:06
原文地址:LINUX设备驱动开发版本3----参研 悟道(2) 作者:zhj1011
(dev_t)-->主设备号、次设备号 |
MAJOR(dev_t dev) MINOR(dev_t dev) |
|
主设备号、次设备号-->(dev_t) | MKDEV(int major,int minor) | |
int register_chrdev_region(dev_t first, unsigned int count,
char *name); //指定设备编号
返回值:分配成功,返回0;
出错,返回一个负的错误码。
first:是你要分配的起始设备编号,first的次编号部分常常是0。
count:是你请求的连续设备编号的总数。如果count太大,你要求的范围可能溢出到下一个次编号;只要你要求的编号范围可用,一切都仍然会正确工作。
name:是应当连接到这个编号范围的设备的名字,这个名字会出现在文件/proc/devices和sysfs中。
sysfs:从Fedora 2开始,在根目录下会有一个/sys目录,mount一下看看,这个目录挂载了一个sysfs的文件系统。
sysfs是和proc、devfs和devpty同类别的文件系统。LINUX 2.6的内核引入了sysfs文件系统。
sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以被从用户的空间存取到。
这是被设计用来处理那些以前驻留在/proc/的设备和驱动程序指定的选件以及用来处理那些以前由devfs提供支持的动态加载设备。
在早期的sysfs实现中,一些驱动和应用仍然被当作老的proc条目,但是sysfs是未来的发展方向。
sysfs被加载在/sys/系统中。它所包括的目录可以使用不同的方式来管理连接在系统上的设备。
/proc/devices文件的内容是:
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttys
5 /dev/tty
5 /dev/console
5 /dev/ptmx
....
.......
251 usb_endpoint
252 usbmon
253 bsg
254 pcmcia
Block devices:
1 ramdisk
2 fd
7 loop
8 sd
9 md
11 sr
65 sd
66 sd
67 sd
...
......
134 sd
135 sd
253 device-mapper
254 mdp
如果我们事先知道我们需要的是哪个设备编号,register_chrdev_region工作的很好。
但是,我们常常是不知道所用设备使用的是哪个主编号,那么,我们就需要内核动态的帮我们分配一个主编号。函数如下:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned int count, char *name); //动态生成设备编号
dev是一个只输出的参数,它在函数成功完成时,持有你的分配范围的第一个数。
firstminor:是请求的第一个要用的次编号;它的值常常是0。
count:是你请求的连续设备编号的总数。如果count太大,你要求的范围可能溢出到下一个次编号;只要你要求的编号范围可用,一切都仍然会正确工作。
name:是应当连接到这个编号范围的设备的名字,这个名字会出现在/proc/devices文件中。
参数count和name同函数register_chrdev_region的参数一样。
void unregister_chrdev_region(dev_t first, unsigned int count); //释放设备编号
调用这个函数的地方,常常是你的模块的cleanup函数。用于不再使用分配的设备编号时,释放设备编号。
分配设备编号的最佳方法是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
以下是LDD3的example中example/scull/main.c中用来获取主设备号的代码。
int scull_init_module(void)
{
...
......
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) //如果人为指定了主设备号,即scull_major的值是非零的,或非空的。
{
dev = MKDEV(scull_major, scull_minor);//通过宏MKDEV,由我们指定的主设备号和次设备号生成dev。
result = register_chrdev_region(dev, scull_nr_devs, "scull");
//通过函数register_chrdev_region()指定设备编号。成功返回0,失败返回负数。
} else //如果scull_major的值为零或空,即我们没有指定主设备号。那么下面要通过动态的方式获取设备号。
{
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev); //通过宏MAJOR由dev获得主设备号。
}
if (result < 0) //指定设备号或动态申请设备号失败,显示出错。
{
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
...
......
}
在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。
二、一些重要的数据结构
大部分基本的驱动程序操作涉及到三个重要的内核数据结构,分别是:file_operations、file和inode,它们基本的定义在(
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 *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 *, struct dentry *, 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 (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 (*dir_notify)(struct file *filp, unsigned long arg);
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);
};
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;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
unsigned long 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;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
struct inode {
struct hlist_node i_hash;
struct list_head i_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 long 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;
unsigned int i_blkbits;
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;
};
int i_cindex;
__u32 i_generation;
#ifdef CONFIG_DNOTIFY
unsigned long i_dnotify_mask; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
#endif
#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode */
struct mutex inotify_mutex; /* protects the watches list */
#endif
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned int i_flags;
atomic_t i_writecount;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
void *i_private; /* fs or device private pointer */
};
三。字符设备的注册
内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个struct cdev。
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;
};
注册一个独立的cdev设备的基本过程:
1、为struct cdev分配空间(如果已经将struct cdev嵌入到自己的设备的特定结构体中,并已经分配了空间,这一步就可以直接跳过,不必再做)
struct *my_cdev = cdev_alloc();
2、初始化struct cdev
void cdev_init(struct cdev *cdev, const struct file_operation *fops)
3、初始化cdev.owner
cdev.owner = THIS_MODULE;
4、cdev设置完成,通知内核struct cdev的信息(在执行这一步之前,必须确定你对struct cdev的以上设置已经完成)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
从系统中移除一个字符设备:void cdev_del(struct cdev *p)
以下是scull中的初始化代码(之前已经为struct scull_dev分配了空间):example/scull/main.c
/*
* 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; //这句可以省略,在cdev_init中已经做过
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
在介绍读写操作之前,我们最好先来看看如何以及为什么scull进行内存分配,
所谓“如何”是:需要全面理解代码。
所谓“为什么”是:演示了驱动编写者需要做的选择。
scull使用的内存区,也称为一个设备,长度是可变的,有点像C的应用程序中的动态申请内存的方式(malloc,free)。
scull驱动使用2个核心函数来管理LINUX内核中的内存。定义在(
void *kmalloc(size_t size,int flags);
void kfree(void *ptr);
kmalloc:分配size字节的内存;成功:返回指向所分配内存的指针;失败:返回NULL。
flags:参数用来描述内存应当恩如何分配。
kmalloc分配的内存应当用kfree来释放,如同C应用程序下的malloc和free一样。
3.5、open和release