读书笔记_第三章

来源:互联网 发布:软件实施服务合同范本 编辑:程序博客网 时间:2024/05/13 16:10

3.2文件描述符

  • 对于内核而言,所有打开的文件都是通过文件描述符引用。
  • 文件描述符是一个非负整数
  • 常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO分别代表标准输入、标准输出、标准出错输出,定义在头文件<unistd.h>中。

3.3open函数(P48)

  • #include <fcntl.h>int open(const char *pathname, int oflag, .../* mode_t mode */);
    对于open函数而言,仅当创建新文件是才使用第三个参数。其他情况第三个参数直接省去,相当于只有两个参数。
  • open函数返回的文件描述符一定是最小的未用描述符数值。

3.4creat函数(P49)

  • #include <fcntl.h>int creat(const char *pathname, mode_t mode);
  • creat的一个不足之处是它以只写方式打开所创建的文件。

3.5close函数(P50)

  • #include <unistd.h>int close(int filedes);
  • 关闭一个文件时还会释放该进程加在该文件上的所有记录锁。
  •  If  fd  is the last file descriptor referring to the underlying open
     file description (see open(2)), the resources  associated  with  the
     open file description are freed; if the descriptor was the last ref‐
     erence to a file which has been removed using unlink(2) the file  is
     deleted.

3.5lseek函数

  • #include <unistd.h>off_t lseek(int filedes, off_t offset, int whence);若成功则返回新的文件偏移量,若出错则返回-1
  • 如果文件描述符引用的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE
  • 某些设备可能允许负的偏移量,所以在比较lseek的返回值时应当谨慎,不要测试它是否小于0,二要测试它是否等于-1.
  • 文件空洞是由所设置的偏移量超过文件尾端,并写了某些数据后所造成的。位于文件中但没有写过的字节都被读为0.文件中的空洞并不要求在磁盘上占用存储区。

3.6read函数

  • #include <unistd.h>ssize_t read(int filedes, void *buf, size_t nbytes);若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1.

3.7write函数

  • #include <unistd.h>ssize_t write(int filedes, const void *buf, size_t nbytes);若成功返回已写的字节数,若出错则返回-1.
  • 其返回值通常与参数nbytes的值相同,否则表示出错。

3.8文件共享

  •  内核使用三种数据结构表示打开的文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响(如图3-1)
  1. 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,
  2. 内核为所有打开文件位置一张文件表。
  3. 每个打开文件(或设备)都有一个v节点(v-node)结构。v节点包含了文件类型和对比文件进行各种操作的函数的指针。对于大多数文件,v节点还包含了该文件的i节点(i-node,索引节点)。这些信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。


  • 如果两个独立进程各自打开了同一个文件按,则有图3-2中所示的安排。打开该文件的每个进程都得到一个文件表项,但对一个给定的文件只有一个v节点表项。每个进程都有自己的文件表项的一个理由是:这种安排是每个进程都有它自己的对该文件的当前偏移量。

  • 给出了这些数据结构后,现在对前面所述的操作作进一步说明。
  1. 在完成每个write后,在文件表项中的当前文件偏移量即增加所写的字节数。如果这使当前文件偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量(也就是该文件加长了)。
  2. 如果用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行操作时,在文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。这就使每次写的数据都添加到文件的当前尾端处。
  3. 若一个文件用lseek定位到文件当前尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前长度。
  4. lseek函数值修改文件表项中的当前文件偏移量,没有进行任何I/O操作。
  • 可能多个文件描述符项指向同一个文件表项。(如dup函数,fork后,此时父子进程对于每一个打开文件描述符共享同一个文件表项。
  • 文件描述符标志和文件状态标志在作用域方面的区别,前者值用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符

3.11

  • pread和pwrite允许原子行的定位搜索和执行I/O。
    #include <unistd.h>ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);
  • 原子操作指的是由多步组成的操作。如果该操作原子的执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤中的一个子集。

3.12dup和dup2函数

  • 下面两个函数用来复制一个现存的文件描述符。
    #include <unistd.h>int dup(int filedes);int dup2(int filedes, int filedes2);若成功则返回新的文件描述符,若出错则返回-1
  • 由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用filedes2参数指定新文件描述符的数值。如果filedes2已经打开,则先将其关闭。如果filedes等于filedes2,则dup2返回filedes2,而不关闭它
  • 这些函数返回的新闻键描述符与参数filedes共享同一个文件表项。图3-3显示了这种情况。

3.13sync、fsync和fdatasync函数

  • 当将数据写入文件是,内核通常先将该数据复制到其中一个缓冲区中如果该缓冲区尚未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲区排入输出队列,然后待其到达队首时,才进行实际的I/O操作,这种输出方式被成为延迟写。
  • 为了保证磁盘上的实际文件系统与缓冲区高速缓存中内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数
    #include <unistd.h>int fsync(int filedes);int fdatasync(int filedes);若成功返回0,若出错返回-1void sync(void);
    sync函数只是将所有修改过的块缓冲取区排入写队列,然后就返回,它并不等待实际写磁盘操作结束

fsync函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回

fdatasync函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的属性

3.14fcntl函数

  • fcntl函数可以改变已打开文件的性质。
    #include <fcntl.h>int fcntl(int filedes, int cmd, .../* int arg */);若成功则依赖于cmd,若出错则返回-1.

    在本节的各实例中,第三个参数总是一个整数。但是在14.3节说明记录锁时,第三个参数则是指向一个结构的指针。
  • fcntl函数有5种功能:
  1. 复制一个现有的描述符(cmd=F_DUPFD)
  2. 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)
  3. 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)
  4. 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)
  5. 获得/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW)
  • 在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现有的标志值,然后根据需要修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。
  • 将fsync和fdatasync函数与O_SYNC标志相比较,fsync和fdatasync在我们需要时更新文件内容,O_SYNC标志则在我们每次写至文件时更新文件内容。

3.15ioctl函数

  • #include <sys/ioctl.h>int ioctl(int filedes, int request, ...);若出错返回-1,若成功则返回其他值。

习题

1.当读/写磁盘文件时,本章中描述的函数是否有缓冲机制?请说明原因。

所有的磁盘I/O都要经过内核的块缓冲区(也成为内核的缓冲区高速缓存),唯一例外的是对原始磁盘设备的I/O。既然read或write的数据都要被内核缓冲,那么术语“不带缓冲的I/O“指的是在用户的进程中对这两个函数不会自动缓冲,每次read或write就要进行一次系统调用。

2.编写一个与3.12节中dup2功能相同的函数,要求不调用fcntl函数,并且要有正确的出错处理。

转自:http://blog.csdn.net/lgg201/article/details/6685241

int ud_dup2(const int ofd, const int nfd) {//新描述符等于旧描述符,不关闭直接返回if(ofd== nfd) return ofd;int pid= getpid();char *pathname= malloc(sizeof(char) * 128);sprintf(pathname, "/proc/%d/fd/%d", getpid(), nfd);//如果新描述符已经被打开,关闭它if(!access(pathname, F_OK)) close(nfd);int tmp;int max= sysconf(_SC_OPEN_MAX);int fds[max], i = 0;//如果新描述符值大于最大描述符数, 返回错误if(max < nfd) return -1;do {tmp= dup(ofd);//dup出错if(tmp < 0) break;fds[i ++]= tmp;} while(tmp < nfd);//如果拷贝出错,则i不自减,也关闭最后一次复制的描述符, 否则,最后的为新描述符, 不关闭if(tmp == nfd) i --;//关闭复制的描述符while(i-- >= 0) close(fds[i]);if(tmp != nfd) return -1;return nfd;}

3.6如果使用添加标志打开一个文件以便读、写,能否仍用lseek在任一位置开始读?能否用lseek更新文件中任一部分的数据?请编写一个程序以验证之。

这种情况下,仍然可以用lseek和read函数读文件中任意一处的内容。但是write函数在写数据之前会自动将文件偏移量设置为文件尾,所以写文件时只能从文件尾开始。

#include <stdio.h>#include <fcntl.h>#include <unistd.h>int main(){    int fd;    int n;    char buf[4];    if ((fd = open("tmp", O_RDWR | O_APPEND)) == -1)        perror("open error");    if (lseek(fd, 0, SEEK_SET) == -1)        perror("lseek error");    if ((n = read(fd, buf, 3)) == -1)        perror("read error");    printf("%c %c %c\n", buf[0], buf[1], buf[2]);    if (write(fd, buf, n) != 3)        perror("write error");    return 0;}




原创粉丝点击