LDD3源碼分析之llseek分析

LDD3源碼分析之llseek分析



編譯環境:Ubuntu 10.10node

內核版本:2.6.32-38-generic-paeapp

LDD3源碼路徑:examples/scull/main.c函數

 

本文分析LDD3第6章的llseek函數。
 
1、用戶空間的lseek函數
要理解驅動中llseek函數的實現,必須先清楚對應的用戶空間中lseek函數的用法,lseek函數函數原型以下:
 
  1. off_t lseek(int fd, off_t offset, int whence);  
第一個參數fd是要操做的文件描述符。
第二個參數指定文件操做指針的偏移量。注意,文件的讀和寫使用的是同一個文件操做指針。
第三個參數指定移動文件操做指針的參考點。這個參數一般取值爲如下宏:
SEEK_SET:表示相對文件起始位置。
SEEK_CUR:表示相對文件操做指針當前位置。
SEEK_END:表示相對文件結束位置。
下面先來看一個用戶空間測試程序llseek_test.c的實現,這個程序用來測試scull的定位功能,其代碼以下:
 
  1.  1#include <stdio.h>  
  2.  2#include <unistd.h>  
  3.  3#include <fcntl.h>  
  4.  4#include <string.h>  
  5.  5#include <sys/types.h>  
  6.  6#include <sys/stat.h>  
  7.  7  
  8.  8#define BUF_SIZE 50  
  9.  9#define DEVICE_FILE "/dev/scull"  
  10. 10  
  11. 11int main(int argc, char *argv[])  
  12. 12{  
  13. 13    int fd;  
  14. 14    int num;  
  15. 15    char buf[BUF_SIZE];  
  16. 16  
  17. 17    fd = open(DEVICE_FILE, O_RDWR);  
  18. 18    if(fd < 0)  
  19. 19    {  
  20. 20        printf("open scull error!\n");  
  21. 21        return -1;  
  22. 22    }  
  23. 23  
  24. 24    memset(buf, 0, BUF_SIZE);  
  25. 25    num = read(fd, buf, BUF_SIZE);  
  26. 26    buf[num] = 0;  
  27. 27    printf("%s\n", buf);  
  28. 28  
  29. 29    lseek(fd, 2, SEEK_SET);  
  30. 30    write(fd, "aa", 2);  
  31. 31    num = read(fd, buf, BUF_SIZE);  
  32. 32    buf[num] = 0;  
  33. 33    printf("%s\n", buf);  
  34. 34  
  35. 35    lseek(fd, 2, SEEK_SET);  
  36. 36    num = read(fd, buf, BUF_SIZE);  
  37. 37    buf[num] = 0;  
  38. 38    printf("%s\n", buf);  
  39. 39  
  40. 40    lseek(fd, 0, SEEK_SET);  
  41. 41    lseek(fd, 2, SEEK_CUR);  
  42. 42    num = read(fd, buf, BUF_SIZE);  
  43. 43    buf[num] = 0;  
  44. 44    printf("%s\n", buf);  
  45. 45  
  46. 46    lseek(fd, 0, SEEK_SET);  
  47. 47    lseek(fd, 0, SEEK_END);  
  48. 48    memset(buf, 0, BUF_SIZE);  
  49. 49    printf("read return value is %d.\n", read(fd, buf, BUF_SIZE));  
  50. 50  
  51. 51    return 0;  
  52. 52}  
這個程序很簡單,主要關注一下lseek函數是怎樣移動文件操做指針的。
第29行,使用SEEK_SET宏,將文件操做指針移動到文件起始位置加上2個字節處。
第30行,寫入兩個字符’a’。
第41行,使用SEEK_CUR宏,將文件操做指針移動到文件操做指針當前位置加上2個字節處。
第47行,使用SEEK_END宏,將文件操做指針移動到文件結束處。
第49行,打印read的返回值,當文件操做指針在文件結束處時,read返回0。

下圖是使用llseek_test測試scull設備的定位功能的過程:源碼分析

這裏須要說明的一點是,從上面的輸出信息能夠看出,對文件的read和write操做使用的是同一個文件操做指針。
 
2、驅動程序中llseek函數的實現
用戶空間的lseek函數的定位功能在驅動程序中是由llseek函數實現的。注意,要完成對文件的定位操做,還須要read、write函數的配合,讀寫完成後必須更新文件操做指針位置。
即便驅動程序中沒有實現llseek函數,有某些狀況下,設備也是能夠完成定位操做的,內核經過修改filp->f_pos來執行定位,filp->f_pos是文件的當前讀寫位置。可是,若是定位操做須要涉及設備的物理操做,就必須實現llseek函數了。scull設備的llseek函數代碼以下:
 
  1. 523loff_t scull_llseek(struct file *filp, loff_t off, int whence)  
  2. 524{  
  3. 525    struct scull_dev *dev = filp->private_data;  
  4. 526    loff_t newpos;  
  5. 527  
  6. 528    switch(whence) {  
  7. 529      case 0: /* SEEK_SET */  
  8. 530        newpos = off;  
  9. 531        break;  
  10. 532  
  11. 533      case 1: /* SEEK_CUR */  
  12. 534        newpos = filp->f_pos + off;  
  13. 535        break;  
  14. 536  
  15. 537      case 2: /* SEEK_END */  
  16. 538        newpos = dev->size + off;  
  17. 539        break;  
  18. 540  
  19. 541      default/* can't happen */  
  20. 542        return -EINVAL;  
  21. 543    }  
  22. 544    if (newpos < 0) return -EINVAL;  
  23. 545    filp->f_pos = newpos;  
  24. 546    return newpos;  
  25. 547}  
這裏惟一與設備相關的操做就是第538行,取得設備文件的大小。同時,咱們在前面的文章中分析過,scull的read和write函數讀寫文件後,老是更新文件操做指針的位置,定位功能須要llseek與read、write的配合。
對於某些設備文件來講,定位功能是沒有意義的,例如鍵盤。對於這些設備,咱們不能簡單地不實現llseek函數,由於默認方法是容許經過filp->f_pos定位的。咱們應該在咱們的open函數中調用nonseekable_open,通知內核設備不支持llseek。該函數的函數原型以下:
 
  1. int nonseekable_open(struct inode *inode; struct file *filp);  
另外,爲完整起見,咱們還應該將file_operations結構中的llseek方法設置爲特殊的輔助函數no_llseek
相關文章
相關標籤/搜索