(0)基础知识
1.文件的读写操作都是从当前文件的偏移处开始的。
2.文件偏移量保存在文件表中。
3.每个进程都有一个文件表。
4.同步IO与异步IO
在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。
在异步文件IO中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了。
5.链接: 硬链接, 符号链接
硬链接:
1.默认情况下,ln产生硬链接。
2.建立硬链接时,链接文件和被链接文件必须位于同一个文件系统中。
3.不能建立指向目录的硬链接。
符号链接:
1.ln命令加上- s选项,则建立符号链接。
2.可以链接任意一个文件或者目录
3.可以在不同的文件系统中链接
(1)打开文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
·pathname :表示要打开的文件相对路径。
·mode :只在创建文件时需要,用于指定所创建文件的权限位
·flags :用于指示打开文件的选项,常用的有 O_RDONLY 、 O_WRONLY 和 O_RDWR 。
注意: O_RDWR ! =O_RDONLY|O_WRONLY // O_RDONLY 被定义为 0 , O_WRONLY 被定义为 1 ,而 O_RDWR 却被定义为 2 。
1.open 是 glibc 的一个变参函数。
·O_APPEND :每次进行写操作时,内核都会先定位到文件尾,再执行写操作。
·O_ASYNC :使用异步 I/O 模式。
·O_CLOEXEC :在打开文件的时候,就为文件描述符设置 FD_CLOEXEC 标志。
// 这里设置为FD_CLOEXEC表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递给exec创建的新进程, 如果设置为fcntl(fd, F_SETFD, 0);
那么本fd将保持打开状态复制到exec创建的新进程中。
·O_CREAT :当文件不存在时,就创建文件。
·O_DIRECT :对该文件进行直接 I/O ,不使用 VFS Cache 。
·O_DIRECTORY :要求打开的路径必须是目录。
·O_EXCL :该标志用于确保是此次调用创建的文件,需要与 O_CREAT 同时使用;当文件已经存在时, open 函数会返回失败。
·O_LARGEFILE :表明文件为大文件。
·O_NOATIME :读取文件时,不更新文件最后的访问时间。
·O_NONBLOCK 、 O_NDELAY :将该文件描述符设置为非阻塞的(默认都是阻塞的)。
·O_SYNC :设置为 I/O 同步模式,每次进行写操作时都会将数据同步到磁盘,然后 write 才能返回。
·O_TRUNC :在打开文件的时候,将文件长度截断为 0 ,需要与 O_RDWR 或 O_WRONLY 同时使用。
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
1.如果pathname是绝对路径,则dirfd参数没用。
2.如果pathname是相对路径,并且dirfd的值不是AT_FDCWD,则pathname的参照物是相对于dirfd指向的目录,而不是进程的当前工作目录;
3.如果pathname是相对路径,并且dirfd的值是AT_FDCWD,pathname则是相对于进程当前工作目录的相对路径,此时等同于open。
(2)创建文件
int creat(const char *pathname, mode_t mode);
1.若成功返回为只写打开的文件描述符; 若出错,返回-1
2.等效于open(path, O_WRONLY | O_CREATE | O_TRUNC, mode);
(3)关闭文件描述符
int close(int fd);
1.close 用于关闭文件描述符。而文件描述符可以是普通文件,也可以是设备,还可以是 socket 。
2.遗忘 close 造成的问题
· 文件描述符始终没有被释放。
· 用于文件管理的某些内存结构没有被释放。
·对于普通进程来说,即使应用忘记了关闭文件,当进程退出时, Linux 内核也会自动关闭文件,释放内存(详细过程见后文)。
·但是对于一个常驻进程来说,问题就变得严重了。
3.lsof查看打开了,但是尚未关闭的文件
(4)文件偏移
off_t lseek(int fd, off_t offset, int whence);
0.如果fd是指向一个管道,FIFO或者网络套接字, lseek返回-1,置errno为ESPIPE
1.该函数用于将 fd 的文件偏移量设置为以 whence 为起点,偏移为 offset 的位置。
2. whence 可以为三个值: SEEK_SET 、 SEEK_CUR 和 SEEK_END ,分别表示为 “ 文件的起始位置 ” 、
“ 文件的当前位置 ” 和 “ 文件的末尾 ” ,而 offset 的取值正负均可。
3. lseek 执行成功后,会返回新的文件偏移量。出错返回-1。
4.确定打开文件的当前偏移量
off_t curpos;
curpos = lseek(fd, 0, SEEK_CUR);
5.文件偏移量可以大于文件的当前长度, 在这种情况下, 对该文件的下一次写将加长该文件。并在文件中
构建一个空洞,这一点是允许的。位于文件中没有学过的直接都被设为0
6.lseek函数只修改文件表项中的当前文件偏移量, 不进行任何IO操作
7.文件空洞不占用磁盘空间
(5)读
ssize_t read(int fd, void *buf, size_t count);
1.返回读取到的字节数。 若已经到文件尾部,再次调用read时返回0; 若出错返回-1。
2.有很多情况可使实际读到的字节数少于要求读的字节数:
1.文件中的当前偏移位置到文件结尾 < 要读取的字节数, 返回实际读取的字节数
2.文件当前偏移位置已经在文件尾部, 返回0。(不阻塞)
3.从网络读时, 网络中的缓冲机制可能造成返回值小于所要求读的字节数
4.读管道或者FIFO时。 如果包含的字节数 < 要读取的字节数, 那么read将只返回实际可用的字节数。
5.某些面向记录的设备(如磁带)一次最多返回一个记录。
6.读取过程中被信号中断时, 返回实际读取的字节数
(6)写
ssize_t write(int fd, const void *buf, size_t count);
1.write 尝试从 buf 指向的地址,写入 count 个字节到文件描述符 fd 中,并返回成功写入的字节数,同时
将文件偏移向前移动相同的字节数。 write 有可能写入比指定 count 少的字节数。
2.当多个进程同时写一个文件时,即使对 write 进行了锁保护,在进行串行写操作时,文件依然不可避免地会被写乱。根本原因就在于文件偏移量是进程级别的。
(7)文件共享
1.文件指针指向文件表项
2.v节点指针指向v节点表项
3.v_data指向i节点。
1.每个进程在进程表中都有一个打开文件描述符表
2.内核为所有打开的文件维持一种文件表
3.每个打开的文件(设备)都有一个v节点
4.unix环境编程60页图
注意:
1.可能有多个文件描述符指向同一文件表项
1.dup
2.fork后, 父进程打开的所有"文件描述符"都被复制到子进程中,父子进程每个相同的打开描述符
共享一个文件表项
(8)文件的原子读写
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);
1.pread 不会从文件表中获取当前偏移,而是直接使用用户传递的偏移量,并且在读取完毕后,不会更改当前文件的偏移量。
2.pwrite 的实现与 pread 类似
(9)复制文件描述符
int dup(int oldfd);
1.dup()系统调用使用编号最小的文件描述符创建文件描述符oldfd的副本。
2.在成功返回之后,旧的和新的文件描述符可以互换使用。
3.两个文件描述符共用同一个文件表,但是不共享文件描述符标志
例如:如果通过在其中一个描述符上使用lseek修改文件偏移量,则另一个描述符的偏移量也会更改。
关闭重复描述符的close-on-exec标志
4.调用close关闭文件描述符时, oldfd和拷贝的newfd都需要手动关闭。
int dup2(int oldfd, int newfd);
1.dup2与dup执行同样的操作,newfd是用户传入的
2.如果newfd和已经是另外一个文件打开的描述符, 那么会先将newfd所在的文件描述符关闭,然后执行拷贝工作
3.如果newfd与oldfd相同, 那么dup2函数不执行任何操作,且返回newfd
int dup3(int oldfd, int newfd, int flags);
dup3与dup2不同点在于:
1. 调用者仅仅可以通过在flags中指定O_CLOEXEC来强制为新文件描述符设置close-on-exec标志。
2. 如果oldfd等于newfd,则dup3()将失败并显示错误EINVAL。
(10)文件数据同步
void sync(void);
1.只是将所有修改过的块缓冲区排入写队列, 然后就返回, 他并不等待实际写磁盘操作结束
2.updata系统守护进程周期性的调用sync函数(30s)
int fsync(int fd);
1.fsync 只同步 fd 指定的文件,并且等待写磁盘操作结束才返回。
2.同步更新文件数据
int fdatasync(int fd);
1.类似于fsync但他只影响文件的数据部分
2.不会同步更新文件的属性
(11)获取文件的元数据
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
这三个函数都可用于得到文件的基本信息。
1.stat 得到路径 path 所指定的文件基本信息。
2.fstat得到文件描述符 fd 指定文件的基本信息。
3.如果path指向的是链接文件,lstat得到的是链接文件自己本身的基本信息而不是其指向文件的信息。
struct stat {
dev_t st_dev; // 设备ID
ino_t st_ino; // 节点号
mode_t st_mode; // 包含
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // 所有者用户ID
gid_t st_gid; // 所有者组ID
dev_t st_rdev; // 设备ID(如果是特殊的文件)
off_t st_size; // 总大小, 以字节为单位
blksize_t st_blksize; // 文件系统I/O的块大小
blkcnt_t st_blocks; // 分配了512B的块数
time_t st_atime; // 上次访问时间
time_t st_mtime; // 最后修改时间
time_t st_ctime; // 上次状态变更时间
};
(12)改变已经打开文件的属性
int fcntl(int fd, int cmd, ... /* arg */ );
提供了以下5个功能
1.复制一个文件描述符 (F_DUPFD、F_DUPFD_CLOEXEC )
2.获取设置文件描述符标志(F_GETFD、F_SETFD)
3.获取设置文件状态标志 (F_GETFL、F_SETFL)
4.获取设置异步I/O所有权 (F_GETOWN、F_SETOWN )
5.获取设置记录锁 (F_SETLK、F_SETLKW、F_GETLK)
(13)文件截断
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
1. truncate 截断的是路径 path 指定的文件, ftruncate 截断的是 fd 引用的文件。
2. length 可以大于文件本身的大小,这时文件长度将变为 length 的大小,扩充的内容均被填充为 0 。
3.尽管 ftruncate使用的是文件描述符,但是其并不会更新当前文件的偏移。
1.为什么需要文件截断?
如果某个文件已经存在有5个字节12345, 这时我打开文件(偏移量为0), 写入33, 结束后文件为33345,这个结果显然不是我们想要的
2.文件截断为0长度的方法。
1.打开文件的同时,指定 O_TRUNC标志。
2.truncate(path, 0) ftruncate(fd,0)
文件I/O(基础知识 打开文件 创建文件 关闭文件描述符 文件偏移 读 写 文件共享 文件的原子读写 复制文件描述符 文件数据同步 获取文件的元数据 改变已经打开文件的属性 文件截断)