Chinaunix首页 | 论坛 | 博客
  • 博客访问: 357822
  • 博文数量: 73
  • 博客积分: 4096
  • 博客等级: 上校
  • 技术积分: 1102
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-21 14:53
文章分类
文章存档

2009年(3)

2008年(70)

我的朋友

分类: LINUX

2008-11-27 15:13:40

 
LDD3 第三章 字符设备驱动程序的学习
 
本章主要通过介绍字符设备scull(Simple Character Utility for Loading Localities,区域装载的简单字符工具)的驱动程序编写,来学习LINUX下设备驱动的基本知识。scull可以为真正的设备驱动程序提供模板。
 
一、主设备号和次设备号
    每个设备文件都有两个设备号,第一个是主设备号,用于标识驱动程序;
    第二个是次设备号,用于标识使用同一个设备驱动程序的不同的硬件设备,比如,有两个软盘,就可以用次设备号来区分它们。
    设备文件的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到设备驱动程序。
    先来看看LINUX下设备驱动的一些基本知识。
    设备驱动程序作为内核的一部分,它完成如下功能:
    1、对设备初始化和释放
    2、把数据从内核传送到硬件和从硬件读取数据
    3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据
    4、检测和处理设备出现的错误
    LINUX操作系统下有两类主要的设备文件类型:字符设备和块设备(还有一个是网络设备)
    字符设备和块设备的区别是:
    在对字符设备发出读/写请求时,实际的硬件I/O就紧接着发生了。
    块设备则不是,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
    注意:当用户进程调用驱动程序时,系统进入和心态,这时不再是抢占式调度,也就是说,系统必须在驱动程序的子函数返回后才能进行其它的工作。如果驱动程序陷入死循环,那么整个内核系统就会崩溃只有重启机器了。
    内核用dev_t类型()来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20位表示次设备号。
    在实际的使用中,是通过(中定义的宏来转换格式的。
   
    #define MINORBITS 20
    #define MINORMASK ((1U << MINORBITS) - 1 )
    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
   

   (dev_t)-->主设备号、次设备号

 MAJOR(dev_t dev)

 MINOR(dev_t dev)

 
   主设备号、次设备号-->(dev_t)  MKDEV(int major,int minor)  
     
   注意:2.6内核能容纳有大量设备,而以前的内核版本对此有限制,最多255个主编号和255个次编号。这个限制数目,查看文件/proc/devices的内容,就会发现,字符设备和块设备的最大的编号是254。
 
   在建立一个字符设备之前,驱动程序首先要做的事情是获得设备的编号。其主要函数位于()中声明:

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

 

 
 
阅读(1340) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~