上犹电脑信息网我们一直在努力
您的位置:上犹电脑信息网 > 文件问题 > APUE3阅读笔记(二)文件I/O与写冲突避免-TD文件

APUE3阅读笔记(二)文件I/O与写冲突避免-TD文件

作者:上犹日期:

返回目录:文件问题

文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是非负整数。当打开或创建一个文件时,内核向进程返回一个文件描述符,这个文件描述符将作为参数传给read或write函数,用于标识具体哪个文件。

按照惯例,文件描述符0对应进程的标准输入;1对应进程的标准输出,2与标准错误关联。他们对应的宏名称为STDIN_FILENO,STDOUT_FILENO和STERR_FILENO。这些宏定义在所在linux系统的这个路径/usr/include/unistd.h头文件中,注意不是在kernel中。如果是交叉编译,这个头文件应在在toolchain中可以找到。

文件描述符的值域为[0,OPEN_MAX-1],早期unix系统OPEN_MAX为20,后来增加到64,实际实现的系统基本都对此不作限制,最终受限于物理资源,例如整型字长,存储器容量,系统管理员配置的限制等等。

文件读写相关的系统调用

open 和openat函数

#include <fcntl.h>

int open(const char *path, int oflag,... /* mode_t mode */);

int openat(int fd, const char *path, int oflag, ... /* mode_t mode */ );

两函数的返回值:若成功,返回文件描述符;若出错,返回−1

如果pathname是绝对路径,则openat函数中的fd参数没用。如果pathname是相对路径,并且fd的值不是AT_FDCWD,则pathname的参照物是相对于fd指向的目录,而不是进程的当前工作目录;反之,如果fd的值是AT_FDCWD,pathname则是相对于进程当前工作目录的相对路径,此时等同于open。其他含at的函数与对应的不含at的函数也都是这种差别。

函数入参path没啥好说的,就是文件路径。参数oflag需要好好解释一下,以下flag的常量定义在fcntl.h文件中,具体数值可通过文件查看。(要么网上搜索一下,要么在自己的linux系统的/usr/include里找。)

APUE3阅读笔记(二)文件I/O与写冲突避免

注意,不同系统中对SYNC的实现不尽相同。linux中定义了O_DSYNC,但行为与O_SYNC相同。

由于open函数已经覆盖了creat函数的功能,不做过多介绍。

close函数的入参为文件描述符。用于关闭一个文件,同时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核自动关闭所有它打开过的文件,很多程序因此不显式的关闭文件。

lseek函数用于给一个打开的文件设置偏移量,原型为:

off_t lseek(int fd, off_t offset, int whence);

offset是相对于whence(基准)的偏移量(可正可负)

whence 可以是SEEK_SET(文件指针开始),SEEK_CUR(文件指针当前位置) ,SEEK_END为文件指针尾

函数返回为新的文件偏移量。

如果文件描述符指向一个管道或者FIFO或者网络套接字,则lseek返回-1,并且将errno设置为ESPIPE.

read和write函数

#include <unistd>

ssize_t read(int filedes, void *buf, size_t nbytes);

1)读普通文件时,在读到要求字节数之前就已经达到了文件末端。例如,若在到达文件末端之前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件末端)。

  2)当从终端设备读时,通常一次最多读一行。

  3)当从网络读时,网络中的缓存机构可能造成返回值小于所要求读的字节数。

  4)当从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数。

  5)当从某些面向记录的设备(例如磁带)读时,一次最多返回一个记录。

  6)当某一个信号造成中断,而已经读取了部分数据,返回实际读取到的字节数。

#include <unistd>

ssize_t write(int filedes, void *buf, size_t nbytes);

我们在实际写代码时,可以直接使用以上函数,也可以使用C语言库函数,File相关的读写操作,那些操作也是对以上系统调用的封装。

文件共享

APUE3阅读笔记(二)文件I/O与写冲突避免

linux没有使用v节点,而是全部使用i节点结构。v节点和i节点虽然实现不同,但概念相同,最终都是指向i节点结构。

上图显示了一个进程对应的3张表的关系。它有两个不同的打开文件:一个文件从标准输入打开(文件描述符为0);另一个从标准输入打开(文件描述符为1).

如果两个进程打开了同一个文件,结果是咋样的呢?

APUE3阅读笔记(二)文件I/O与写冲突避免

这里可以看出,同一个文件在不同进程中的文件描述符可以不同,而且它在两个进程中有独立的文件表项,因为不同的进程对文件的读写时的偏移量等信息是不同的。这两个文件表项指向同一个v节点表项。

对于wirte操作,写入相应的字节数后,如果当前文件偏移量超出了文件长度,则将i节点中的文件长度加长。当我们用O_APPEND标志打开一个文件时,文件偏移量直接赋值成i节点中的文件长度。注意这两个操作都涉及i节点中的文件长度,很多文件读写的冲突由此而起。

假如两个进程都先用lseek定位到文件末尾,然后写入内容。那么就有这种情况发生:两个进程都通过lseek定位到文件末尾了,A进程写入数据退出,B进程也写入数据,就把A进程的数据覆盖了。

unix提供了一种原子操作,即在打开文件时设置O_APPEND标志,这样内核在每次写操作前,都将进程的当前偏移量设置到文件的尾端,于是每次写之前就不需要lseek了。(即lseek和写操作不可打断)

当打开文件时使用了O_SYNC标志,可以保证单独的read和write是原子操作了。但是如果有人想先偏移再write,这两个动作显然不是原子操作。在Linux下,pread/pwrite函数就好像是专门为上面的问题服务的,它本身就是原子性的操作,定位文件指针与读/写操作一气呵成,而且读操作并不改变文件指针。

#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t offset);

ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

dup和dup2

#include <unsitd.h>

int dup(int fd);

int dup2(int fd,int fd2);

dup和dup2函数都用来复制一个现有的文件描述符。由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。而dup2可以用fd2指定新的描述符的值,如果fd2已经存在,则先将其关闭。若fd==fd2,则直接返回fd2,而不关闭它。

一般来说,fd!=fd2,但是他们的差别仅仅是文件描述符不同而已,后面的文件表和i节点信息都是一样的。这两个函数有啥用呢?

#define TESTSTR "Hello dup2\n"

int main() {

int fd3;

fd3 = open("testdup2.dat", 0666);

if (fd < 0) {

printf("open error\n");

exit(-1);

}

if (dup2(fd3, STDOUT_FILENO) < 0) {

printf("err in dup2\n");

}

printf(TESTSTR);

return 0;

}

这段代码的结果就是你在testdup2.dat中看到"Hello dup2"。也就是说printf把“Hello dup2”发送给标准输出,而标准输出与fd3绑定了,因此就存入fd3对应的文件中去了。

网上还流传着这样一段代码,建议仔细研读一下。

#include <stdio.h>;

#include <unistd.h>;

#include <stdlib.h>;

#include <fcntl.h>;

#include <sys/types.h>;

#include <sys/stat.h>;

#include <string.h>;

#include <strings.h>;

int main()

{

int sfd = dup(STDOUT_FILENO), testfd;

printf("sfd = [%d]\n", sfd);

testfd = open("./temp",O_CREAT | O_RDWR | O_APPEND);

if (-1 == testfd)

{

printf("open file error.\n");

exit(1);

}

/* 重定向 */

if (-1 == dup2(testfd,STDOUT_FILENO) ) {

printf("can't redirect fd error\n");

exit(1);

}

/* 此时向stdout写入应该输出到文件 */

write(STDOUT_FILENO,"file\n",5);

/* 恢复stdout */

if (-1 != dup2(sfd,STDOUT_FILENO) ) {

printf("recover fd ok \n");

/* 恢复后,写入stdout应该向屏幕输出 */

write(STDOUT_FILENO,"stdout\n",7);

}

printf("gogogogogogo!\n");

close(testfd);

}

sync,fsync和fdatasync

int fdatasync(int fd);

int fsync(int fd);

int sync(void);

当我们向文件写入数据时,内核常先将数据复制到缓冲区,然后排入队列,按输入写入磁盘。这种方式称为写延迟(delayed write).那么为了保证磁盘上实际文件系统与缓冲区内容一致,unix系统提供了以上三个函数。

sync只是将所有修改过的块缓冲区写入队列,然后就返回,并不等待实际写磁盘操作结束。

fsync只对文件描述符fd指定的文件起作用,并等写磁盘操作结束才返回。

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

fcntl函数可以改变已经打开的文件的属性。

int fcntl(int fd, int cmd);

int fcntl(int fd, int cmd, long arg);

int fcntl(int fd, int cmd, struct flock *lock);

APUE3阅读笔记(二)文件I/O与写冲突避免

这个函数的作用较多,特别是在pipe和fifo等文件描述符时,我们在接收端只知道它的文件描述符,并不知道其打开方式,因此我们可以用这个函数来修改文件属性,例如设置O_SYNC标志。

ioctl函数

ioctl函数一直是I/O操作的杂物箱。

APUE3阅读笔记(二)文件I/O与写冲突避免

/dev/fd

linux中提供/dev/fd/0~3,也提供/dev/stdin,/dev/stdout和/dev/stderr这些路径。它们主要由shell使用。在cat命令中字符“-”表示标准输入。

filter file2 | cat file1 - file3 | lpr

这条命令中,cat会先读file1,然后读标准输入(来自filter file2的输出),最后都file3

当然把-替换为/dev/fd/0或者/dev/stdin也是同样的效果。

相关阅读

  • 如何刷新显卡bios-bios设置显卡

  • 上犹设置问题
  • bios设置显卡,1、首先,将下载好的NVFfash.exe和BIOS文件放置到C盘新建的NVFfash文件夹中,进入DOS界面后进入到C盘分区:输入:C:回车进入BIOS文件夹,输入:cdBIOS回车然后要刷新显卡BIOS,输
关键词不能为空
极力推荐

电脑蓝屏_电脑怎么了_win7问题_win10问题_设置问题_文件问题_上犹电脑信息网

关于我们