Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1743072
  • 博文数量: 1493
  • 博客积分: 38
  • 博客等级: 民兵
  • 技术积分: 5834
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-19 17:28
文章分类

全部博文(1493)

文章存档

2016年(11)

2015年(38)

2014年(137)

2013年(253)

2012年(1054)

2011年(1)

分类:

2012-05-17 08:54:48

11.3.3 块设备驱动程序的几个函数

所有对块设备的读写都是调用generic_file_read ( )generic_file_write ( ) 函数,这两个函数的原型如下:

点击(此处)折叠或打开

  1. ssize_t generic_file_read(struct file * filp, char * buf, size_t count, loff_t *ppos)

  2. ssize_t generic_file_write(struct file *file,const char *buf,size_t count, loff_t *ppos)


其参数的含义如下:

filp:和这个设备文件相对应的文件对象的地址。

Buf:用户态地址空间中的缓冲区的地址。generic_file_read()把从块设备中读出的数据写入这个缓冲区;反之,generic_file_write()从这个缓冲区中读取要写入块设备的数据。

Count:要传送的字节数。

ppos:设备文件中的偏移变量的地址;通常,这个参数指向filp->f_pos,也就是说,指向设备文件的文件指针。

只要进程对设备文件发出读写操作,高级设备驱动程序就调用这两个函数。例如,superformat程序通过把块写入/dev/fd0设备文件来格式化磁盘,相应文件对象的write方法就调用generic_file_write()函数。这两个函数所做的就是对缓冲区进行读写,如果缓冲区不能满足操作要求则返回负值,否则返回实际读写的字节数。每个块设备在需要读写时都调用这两个函数。

下面介绍几个低层被频繁调用的函数。

1. bread( )breada( ) 函数

bread( )函数检查缓冲区中是否已经包含了一个特定的块;如果还没有,该函数就从块设备中读取这个块。文件系统广泛使用bread( )从磁盘位图、索引节点以及其他基于块的数据结构中读取数据。(注意当进程要读块设备文件时是使用函数generic_file_read()函数,而不是使用bread( )函数。)该函数接收设备标志符、块号和块大小作为参数,其代码在fs/buffer.c中:

点击(此处)折叠或打开

  1. /**

  2. * bread() - reads a specified block and returns the bh

  3. * @block: number of block

  4. * @size: size (in bytes) to read

  5. *

  6. * Reads a specified block, and returns buffer head that

  7. * contains it. It returns NULL if the block was unreadable.

  8. */

  9. struct buffer_head * bread(kdev_t dev, int block, int size)

  10. {

  11. struct buffer_head * bh;


  12. bh = getblk(dev, block, size);

  13. touch_buffer(bh);

  14. if (buffer_uptodate(bh))

  15. return bh;

  16. ll_rw_block(READ, 1, &bh);

  17. wait_on_buffer(bh);

  18. if (buffer_uptodate(bh))

  19. return bh;

  20. brelse(bh);

  21. return NULL;

  22. }


对该函数解释如下:

· 调用getblk( )函数来查找缓冲区中的一个块;如果这个块不在缓冲区中,那么getblk( )就为它分配一个新的缓冲区。

· 调用buffer_uptodate()宏来判断这个缓冲区是否已经包含最新数据,如果是,则getblk( )结束。

· 如果缓冲区中没有包含最新数据,就调用ll_rw_block( )函数启动读操作。

· 等待,直到数据传送完成为止。这是通过调用一个名为wait_on_buffer( )的函数来实现的,该函数把当前进程插入b_wait等待队列中,并挂起当前进程直到这个缓冲区被开锁为止。

breada( )bread( )十分类似,但是它除了读取所请求的块之外,还要另外预读一些其他块。注意不存在把块直接写入磁盘的函数。写操作永远都不会成为系统性能的瓶颈,因为写操作通常都会延时。

2. ll_rw_block( )函数

ll_rw_block( )函数产生块设备请求;内核和设备驱动程序的很多地方都会调用这个函数。该函数接原型如下:

点击(此处)折叠或打开

  1. void ll_rw_block(int rw, int nr, struct buffer_head * bhs[])

其参数的含义为:

· 操作类型rw,其值可以是READWRITEREADA或者WRITEA。最后两种操作类型和前两种操作类型之间的区别在于,当没有可用的请求描述符时后两个函数不会阻塞。

· 要传送的块数nr

· 一个bhs数组,有nr指针,指向说明块的缓冲区首部(这些块的大小必须相同,而且必须处于同一个块设备)。

该函数的代码在block/ll_rw_blk.c中:


点击(此处)折叠或打开

  1. void ll_rw_block(int rw, int nr, struct buffer_head * bhs[])

  2. {

  3. unsigned int major;

  4. int correct_size;

  5. int i;


  6. if (!nr)

  7. return;


  8. major = MAJOR(bhs[0]->b_dev);


  9. /* Determine correct block size for this device. */

  10. correct_size = get_hardsect_size(bhs[0]->b_dev);


  11. /* Verify requested block sizes. */

  12. for (i = 0; i < nr; i++) {

  13. struct buffer_head *bh = bhs[i];

  14. if (bh->b_size % correct_size) {

  15. printk(KERN_NOTICE "ll_rw_block: device %s: "

  16. "only %d-char blocks implemented (%u)\n",

  17. kdevname(bhs[0]->b_dev),

  18. correct_size, bh->b_size);

  19. goto sorry;

  20. }

  21. }


  22. if ((rw & WRITE) && is_read_only(bhs[0]->b_dev)) {

  23. printk(KERN_NOTICE "Can't write to read-only device %s\n",

  24. kdevname(bhs[0]->b_dev));

  25. goto sorry;

  26. }


  27. for (i = 0; i < nr; i++) {

  28. struct buffer_head *bh = bhs[i];


  29. /* Only one thread can actually submit the I/O. */

  30. if (test_and_set_bit(BH_Lock, &bh->b_state))

  31. continue;


  32. /* We have the buffer lock */

  33. atomic_inc(&bh->b_count);

  34. bh->b_end_io = end_buffer_io_sync;


  35. switch(rw) {

  36. case WRITE:

  37. if (!atomic_set_buffer_clean(bh))

  38. /* Nothing to write */

  39. goto end_io;

  40. __mark_buffer_clean(bh);

  41. break;


  42. case READA:

  43. case READ:

  44. if (buffer_uptodate(bh))

  45. /* Already have it */

  46. goto end_io;

  47. break;

  48. default:

  49. BUG();

  50. end_io:

  51. bh->b_end_io(bh, test_bit(BH_Uptodate, &bh->b_state));

  52. continue;

  53. }


  54. submit_bh(rw, bh);

  55. }

  56. return;


  57. sorry:

  58. /* Make sure we don't get infinite dirty retries.. */

  59. for (i = 0; i < nr; i++)

  60. mark_buffer_clean(bhs[i]);

  61. }


下面对该函数给予解释:

进入ll_rw_block()以后,先对块大小作一些检查;如果是写访问,则还要检查目标设备是否可写。内核中有个二维数组ro_bits,定义于drivers/block/ll_rw_blk.c中:

static long ro_bits[MAX_BLKDEV][8];

每个设备在这个数组中都有个标志,通过系统调用ioctl()可以将一个标志位设置10,表示相应设备为只读或可写,而is_read_only()就是检查这个数组中的标志位是否为1

接下来,就通过第二个for循环依次处理对各个缓冲区的读写请求了。对于要读写的每个块,首先将其缓冲区加上锁,还要将其buffer_head结构中的函数指针b_end_io设置成指向end_buffer_io_sync,当完成对给定块的读写时,就调用该函数。此外,对于待写的缓冲区,其BH_Dirty标志位应该为1,否在就不需要写了,而既然写了,就要把它清0,并通过__mark_buffer_clean(bh)将缓冲区转移到干净页面的LRU队列中。反之,对于待读的缓冲区,其buffer_uptodate()标志位为0,否在就不需要读了。每个具体的设备就好像是个服务器,所以最后具体的读写是通过submit_bh()将读写请求提交各“服务器”完成的,每次读写一个块,该函数的代码也在同一文件中,读者可以自己去读。

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