編譯環境:Ubuntu 10.10node
內核版本:2.6.32-38-generic-paeapp
LDD3源碼路徑:examples/scull/main.c函數
本文分析LDD3第6章的llseek函數。
1、用戶空間的lseek函數
要理解驅動中llseek函數的實現,必須先清楚對應的用戶空間中lseek函數的用法,lseek函數函數原型以下:
第一個參數fd是要操做的文件描述符。
- off_t lseek(int fd, off_t offset, int whence);
第二個參數指定文件操做指針的偏移量。注意,文件的讀和寫使用的是同一個文件操做指針。
第三個參數指定移動文件操做指針的參考點。這個參數一般取值爲如下宏:
SEEK_SET:表示相對文件起始位置。
SEEK_CUR:表示相對文件操做指針當前位置。
SEEK_END:表示相對文件結束位置。
下面先來看一個用戶空間測試程序llseek_test.c的實現,這個程序用來測試scull的定位功能,其代碼以下:
這個程序很簡單,主要關注一下lseek函數是怎樣移動文件操做指針的。
- 1#include <stdio.h>
- 2#include <unistd.h>
- 3#include <fcntl.h>
- 4#include <string.h>
- 5#include <sys/types.h>
- 6#include <sys/stat.h>
- 7
- 8#define BUF_SIZE 50
- 9#define DEVICE_FILE "/dev/scull"
- 10
- 11int main(int argc, char *argv[])
- 12{
- 13 int fd;
- 14 int num;
- 15 char buf[BUF_SIZE];
- 16
- 17 fd = open(DEVICE_FILE, O_RDWR);
- 18 if(fd < 0)
- 19 {
- 20 printf("open scull error!\n");
- 21 return -1;
- 22 }
- 23
- 24 memset(buf, 0, BUF_SIZE);
- 25 num = read(fd, buf, BUF_SIZE);
- 26 buf[num] = 0;
- 27 printf("%s\n", buf);
- 28
- 29 lseek(fd, 2, SEEK_SET);
- 30 write(fd, "aa", 2);
- 31 num = read(fd, buf, BUF_SIZE);
- 32 buf[num] = 0;
- 33 printf("%s\n", buf);
- 34
- 35 lseek(fd, 2, SEEK_SET);
- 36 num = read(fd, buf, BUF_SIZE);
- 37 buf[num] = 0;
- 38 printf("%s\n", buf);
- 39
- 40 lseek(fd, 0, SEEK_SET);
- 41 lseek(fd, 2, SEEK_CUR);
- 42 num = read(fd, buf, BUF_SIZE);
- 43 buf[num] = 0;
- 44 printf("%s\n", buf);
- 45
- 46 lseek(fd, 0, SEEK_SET);
- 47 lseek(fd, 0, SEEK_END);
- 48 memset(buf, 0, BUF_SIZE);
- 49 printf("read return value is %d.\n", read(fd, buf, BUF_SIZE));
- 50
- 51 return 0;
- 52}
第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函數代碼以下:
這裏惟一與設備相關的操做就是第538行,取得設備文件的大小。同時,咱們在前面的文章中分析過,scull的read和write函數讀寫文件後,老是更新文件操做指針的位置,定位功能須要llseek與read、write的配合。
- 523loff_t scull_llseek(struct file *filp, loff_t off, int whence)
- 524{
- 525 struct scull_dev *dev = filp->private_data;
- 526 loff_t newpos;
- 527
- 528 switch(whence) {
- 529 case 0: /* SEEK_SET */
- 530 newpos = off;
- 531 break;
- 532
- 533 case 1: /* SEEK_CUR */
- 534 newpos = filp->f_pos + off;
- 535 break;
- 536
- 537 case 2: /* SEEK_END */
- 538 newpos = dev->size + off;
- 539 break;
- 540
- 541 default: /* can't happen */
- 542 return -EINVAL;
- 543 }
- 544 if (newpos < 0) return -EINVAL;
- 545 filp->f_pos = newpos;
- 546 return newpos;
- 547}
對於某些設備文件來講,定位功能是沒有意義的,例如鍵盤。對於這些設備,咱們不能簡單地不實現llseek函數,由於默認方法是容許經過filp->f_pos定位的。咱們應該在咱們的open函數中調用nonseekable_open,通知內核設備不支持llseek。該函數的函數原型以下:
- int nonseekable_open(struct inode *inode; struct file *filp);
另外,爲完整起見,咱們還應該將file_operations結構中的llseek方法設置爲特殊的輔助函數no_llseek。