回顾

  • 文件操作
  • open
  • read
  • write
  • close
  • lseek

今天

  • 文件控制fcntl
  • 文件描述符的复制dup(内存中)
  • 文件锁
  • 文件的周边函数
  • 目录操作(待定)

1. dup和dup2

dup和dup2

  • ​可以复制文件描述符,但不复制文件表。​

dup 返回当前可用描述符的最小值。
dup2 可以 指定文件描述符的值,如果这个值正在使用,关闭正在使用的之后再完成复制。
dup3

注解:
dup: 系统指定最小可用的描述值
dup2: 可自己指定文件描述符,如它被占用,强制关闭再使用

int fd = open("a.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
int fd2 = dup(fd); // 不会复制文件表
write(fd,"1",1);
write(fd2,"2",1);//如果覆盖 复制了 文件表

int fd3 = dup2(fd,100);
write(fd3,"3",1);
// 文件中显示:123
// 说明了它们在内存中共用一个文件偏移指针,也就是说它们只复制了文件描述符并不复制文件表

2. 理解

  • close(fd)
  • 相当于 剪断了 文件描述符与文件表之间的联系,同时将文件描述符移出文件描述符总表
  • 文件描述符
  • 相当于索引,本质是一个非负整数,自身不能操作文件,和文件没有直接关系。
  • 文件表
  • 在内存中存储,记录了文件的相关信息。文件表需要通过文件描述符找到。
  • 文件
  • 在 硬盘中,可以 用文件表进行 操作。
  • 程序
  • 使用的是 文件描述符, 通过在 描述符总表中查找对应的文件表去操作文件。如果 在描述符总表 中查不到 文件表, 文件描述符 没有意义。

3. 函数fcntl

  • int fcntl(int fd,int cmd, …)
    + cmd常见用法:
    - F_DUPFD 复制文件描述符
    - F_GETFL/F_SETFL 设置/取得 文件表的状态(不是全部)
    - F_SETLK/F_GETLK/F_SETLKW 文件锁的操作
// 复制文件描述符
/*
和dup2方法类似,区别在于它不会强制关闭已被占用的文件描述符,而是给予其大于指定的值
*/
int fd2 = fcntl(fd,F_DUPFD,5); // 复制
printf("fd2=%d\n",fd2); // 不会强制关闭已经打开的
int fd3 = fcntl(fd,F_DUPFD,5); // 如果5已经被占用
printf("fd3=%d\n",fd3); // 使用大于等于参数的描述符

-------------------
// 获取文件状态标识
flags = fcntl(fd,F_GETFL);
if(flags&O_RDWR)
printf("RDWR\n");

// 修改,只有O_APPEND可以被修改,权限和创建标示都不能修改
/*
F_SETFL (int)
Set the file status flags to the value specified by arg. File access
mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags (i.e.,
O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored. On Linux this
command can change only the O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and
O_NONBLOCK flags. It is not possible to change the O_DSYNC and O_SYNC
flags; see BUGS, below.
*/
fcntl(fd,F_SETFL,O_RDWR|O_APPEND);

-------------------
获取文件状态

O_LARGEFILE 01000000

读写权限宏定义

#define O_RDONLY  00
#define O_WRONLY 01
#define O_RDWR 02
取某些位上数据的方法
flags & 0xFF 取后8位
flags & 0x11 取后2位(相当于flags & 3)

错误:由于没有加上括号,导致 先判断3==1为0,然后flags&0,结果为0,永远无法进入判断分支

if (flags & 3 == 1) {
}

正确用法: 自身要加上括号

if ((flags & 3) == 1) {
}
文件锁

由于 多个进程/线程 同时写文件时,可能出现 互相覆盖的问题,导致 数据错误。

  • 解决方案
  • 可以使用文件锁。
  • 文件锁
  • 针对 文件内存 锁定,可以 锁定 文件的某部分。
  • 是一个结构体,记录了 文件锁的相关信息。
struct flock{
short l_type; // 锁的类型,分为 读锁 F_RDLCK、写锁F_WRLCK 和 释放锁 F_UNLCK
short l_whence; // 锁的偏移量 出发点,一般 SEEK_SET
int l_start; // 锁的偏移量
int l_len; // 锁定的长度
pid_t l_pid; // 加锁的进程ID,为F_GETLK服务,一般-1
};
  • 读锁(F_RDLCK)
  • 是 共享锁,所有读进程 都可以加 读锁,但不能加 写锁。(锁 写 不锁 读)
  • 写锁(F_WRLCK)
  • 是 独占锁,其它进程不能 再加 任何的锁。
  • 如果 当前进程 加了读锁,其它 进程 可以加读锁,不能加写锁;
  • 如果 当前进程 加了写锁,其它进程 不可以加锁。
    - 用 F_SETLK 方式加锁,如果加不上,返回-1(非阻塞)。
    - 用 F_SETLKW 方式加锁,如果加不上,等待解锁后继续执行。(阻塞)

l_whence和l_start 联合决定 锁定的 起始位置,l_len 决定了锁定的 长度。
文件锁 只是在内存中的设定,不会真正影响文件。即使程序加了文件锁,用vi之类的编辑器可以修改文件。

经验:

开发时,读写文件 应该 先加 对应的锁,然后 在read或者write。(多进程或多线程时)

F_GETLK

  • 不是取锁,而是 测试一个锁能不能加上,
  • 如果能加,把 传入的锁的类型 改成 F_UNLCK,其它数据不变。
  • 如果不能加, 把 当前 正在加的锁的信息返回(包括进程id)。
  • 传入的flock指针 既是 传入参数(被测试的锁),又是传出参数(改 l_type为F_UNLCK或完全被新锁 替换)。

延迟写缓冲区,立即更新

sync 将所有修改过的缓冲区排入写队列,并不等于实际写磁盘
fsync 只对一个文件,并且等实际写磁盘完成才返回
fdatasync 只更新数据,不更新文件属性

获取文件属性

stat: 只使用文件路径,不需要fd
fstat:需要使用文件描述符,所以使用它之前还要open获取fd
lstat

Unix/Linux-05_C

access

#include <unistd.h>

int access(const char *pathname, int mode);
0 success
-1 error errno

6. 推荐书籍及翻译软件

星际译王 - Linux专用翻译软件
Unix环境高级编程