Linux开发(三):文件I/O

目录

一、系统调用

1、open()/openat()函数

2、read()函数

3、write()函数

4、close()函数

5、lseek()函数

6、sync()/fsync()/fdatasync()

7、stat()/fstat()/lstat()

8、mmap()函数

9、munmap()函数

10、msync()函数

11、ftruncate()/truncate()函数

12、flock()函数

 13、fcntl()函数

14、fileno()函数

15、pread()/pwrite()函数

16、dup()/dup2()函数

17、dup2()函数

18、umask()函数

19、readv()/writev()函数

19、readn()/writen()函数

二、C库文件操作

1、fopen()函数

2、fwrite()函数

3、fread()函数

4、fseek()函数

5、fcloseAll()函数

6、fflush()函数

7、feof()函数

8、ferror()函数

9、clearerr()函数

10、rewind()函数

11、ftell()函数

12、临时文件

13、rename()函数

14、remove()函数

15、fdopen()函数

三、POSIX异步I/O

四、目录操作

1、opendir()函数

2、closedir()函数

3、readdir()函数

4、telldir()/seekdir()/rewinddir函数

 5、getcwd()/chdir()函数

6、目录操作示例


一、系统调用

系统调用函数是操作系统为内核代码提供给用户或上层使用的一些函数接口,调用在用户态,执行在内核态。

对于内核而言,每个打开的文件都通过文件描述符引用,文件描述符是一个非负整数,当打开一个文件时,内核将向进程返回一个文件描述符,用于操作该文件,按照惯例,Linux系统的shell已经占用了前3个文件描述符:

文件描述符 宏常量 作用
0 STDIN_FILENO 标准输入
1 STDOUT_FILENO 标准输出
2 STDERR_FILENO 标准出错

1、open()/openat()函数

在指定路径下打开或新建一个文件

#include 
int open(const char *pathname,int flag);
int open(const char *pathname,int flag, mode_t mode);
int openat(int  fd , const char * pathname , int  flags );
int openat(int  fd , const char * pathname , int  flags , mode_t  mode );

函数参数:

pathname: 要打开或创建文件的路径+名字

fd:fd参数将的含义将根据pathname的值有不同的含义:

  • path为绝对路径名:fd将被忽略,openat函数就相当于open函数
  • path为相对路径名:fd参数指出了相对路径名在文件系统中的开始地址,即fd为这个相对路径所在目录的文件描述符
  • path为相对路径名,且fd为特殊值AT_FDCWD,此时路径名为当前目录

flag:用下列的一个或多个进行或运算构成:

O_RDONLY 以只读方式打开
O_WRONLY 以只写方式打开
O_RDWR 以可读可写方式打开
O_EXEC 以可执行方式打开
O_SEARCH 以只搜索方式打开(目录)

 以上5个常量必须指定一个且只能指定一个,下面的常量可以任意选:

O_APPEND 每次写时,内容追加到末尾
O_CLOEXEC
O_CREAT 若文件不存在则创建它,若指定该标志位,必须也要启用mode参数
O_DIRECTORY 若path引用的不是目录,则报错
O_EXCL 如果已经指定了O_CREAT,但文件不存在,则创建,否则将会出错,这是一个原子操作,但如果打开的文件为符号连接,即使文件存在,依然会打开失败。
用此可以测试一个文件是否存在。
O_NOCTTY 如果path引用的是终端设备,则不将该设备作为此进程的控制终端
O_NOFOLLOW 如果path引用的是一个符号链接,则出错
O_NONBLOCK 如果path引用的是一个FIFO或一个块文件或一个字符文件,则选项将会为文件的本次打开及其后续I/O操作设置为非阻塞方式
O_SYNC 使每次write等待物理I/O操作完成,包括由write操作引起的文件属性的变更
O_TRUNC 如果此文件存在,而且为O_WRONLY或O_RDWR方式打开,则将其长度截断为0
O_DSYNC 使每次write要等待物理I/O完成,但是如果该写操作并不影响读取刚写入的数据,则不需要等待文件属性被更新
D_RSYNC 使每一个以文件描述符作为参数进行read操作等待,直至所有对文件同一部分挂起的写操作都完成

mode:创建问件时,指定文件的权限:4-可读 ,2-可写 ,1-可执行

返回值

成功则返回对应文件描述符(最小的未被使用的描述符数值),失败则返回-1

2、read()函数

从打开的文件中读取数据

#include 
ssize_t read(int fd,void *buf,size_t size);

函数参数:

fd:读取文件的文件描述符
buf:读取的数据存储的起始位置
size:一次最多读取的字节个数

返回值:

成功则返回实际读取到的字节数,若已读到文件结尾则返回0,若读取失败返回-1 

read 函数实际读到的字节数少于要求读的字节数时:

  1. 读普通文件,在读到要求字节数之前就到达文件尾;
  2. 当从终端设备读,通常一次最多读一行;
  3. 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数;
  4. 某些面向记录的设备,如磁带,一次最多返回一个记录;

3、write()函数

向打开的文件中写数据,注意磁盘已写满或者文件长度超过限制,都可能导致文件写入失败!

#include 
ssize_t write(int fd,void *buf,size_t size);

函数参数:

fd:将数据写入的文件的文件描述符
buf:写入的数据的起始位置
size:写入数据的字节数

返回值:

若成功返回已经写入的数据的字节数,若失败返回-1

4、close()函数

关闭一个已经打开的文件。

close函数会让文件数据写回硬盘,并释放该文件所占资源。

关闭一个文件时,还会释放该进程加在该文件上的记录锁。

进程结束后,内核也会自动关闭该进程打开的所有文件。

#include 
int close(int fd);

函数参数:

fd:将要关闭的文件描述符

返回值:

成功返回0,失败返回-1

5、lseek()函数

每一个打开的文件都有一个与之关联的“当前文件偏移量”。除非指定O_APPEND选项,否则新打开的文件都文件偏移量都是0,通常读写操作都是从当前文件偏移量开始,并且每次读写都会使当前文件偏移量增加读写到的字节数。

#include 
int lseek(int fd,int offset,int whence);

函数参数:

fd:操作的文件的的文件描述符
offset:设置的文件偏移量
whence:操作标志,有以下几种:

whence 含义
SEEK_SET 将该文件的偏移量设置为距文件开始处off个字节
SEEK_CUR 将该文件的偏移量设置为距当前位置off个字节,off可以为正也可以为负
SEEK_END 将该文件的偏移量设置为距文件结尾处off个字节,off可以为正也可以为负

返回值:

成功则返回当前读写位置,即目前的文件指针距文件开头有多少个字节;失败则返回-1

对lseek函数有以下几点需要特别注意:

  • 1、lseek仅仅是设置文件偏移量,并不会触发文件I/O操作。
  • 2、lseek函数仅仅能用来设置普通文件的文件偏移量,如果fd为套接字、FIFO、管道,则函数返回-1,并且将errno置为ESPIPE。
  • 3、lseek函数对于某些设备,返回值可以为负值,所以判断函数成功与否,不能仅仅根据返回值是否小于0进行判断,而是要根据是否为-1进行判断。
  • 4、如果将文件的偏移量设置为其总大小之后,则会造成文件空洞,空洞内容读取为0,文件空洞并不占有磁盘空间,即使它ls显示的大小很大

6、sync()/fsync()/fdatasync()

将所有已写入文件描述符fd的数据真正的写到磁盘或者其他下层设备上。

sync()函数执行始终成功,但只是将修改过的块的缓存排入写队列,并不等待实际I/O操作结束就立即返回。系统守护进程会周期性的(一般30s)会调用一次sync()函数,从而保证系统定期刷新内核缓存。
fsync()函数则等待实际I/O结束才返回,从而确保修改过的块立即写到硬盘上,成功返回0,否则返回-1。
fdatasync()函数只是更新硬盘文件内容,如果没有必要,并不更新元数据,即文件的属性(长度、上次修改时间等)

#include 
void sync(void);
int fsync(int fd);
int fdatasync(int fd);

函数参数:

fd:操作的文件的的文件描述符

返回值:

成功返回0,否则返回-1

7、stat()/fstat()/lstat()

获取指定文件的属性。

// from /usr/include/sys/stat.h
// 普通文件(传入绝对路径或相对路径)
int stat(const char* filename,struct stat* buf); 
// 普通文件(传入已打开的fd)
int fstat(int fd,struct stat* buf);     
// 符号链接文件          
int lstat(const char* filename,struct stat* buf);  

函数参数:

filename:操作的文件的文件路径+名称

fd:操作的文件的的文件描述符

buf: 文件属性,结构体struct stat定义如下:

struct stat {
	unsigned short   st_dev;              //设备号
	unsigned short   _pad1;              
	unsigned long    st_ino;              //文件inode值
	unsigned short   st_mode;             //文件类型及权限
	unsigned short   st_nlink;            //硬件连接数
	unsigned short   st_uid;              //用户ID
	unsigned short   st_gid;              //用户组ID
	unsigned short   st_rdev;             //设备号
	unsigned short   _pad2;
	unsigned long    st_size;             //文件大小
	unsigned long    st_biksize;          //数据块大小
	unsigned long    st_blocks;           //数据块数量
	unsigned long    st_atime;            //最后一次访问时间
	unsigned long    _unused1;
	unsigned long    st_mtime;            //最后一次修改时间
	unsigned long    _unused2;
	unsigned long    st_ctime;            //最后一次改变属性时间
	unsigned long    _unused3;
	unsigned long    _unused4;
	unsigned long    _unused5;
}

返回值:

成功返回0,否则返回-1

8、mmap()函数

mmap函数将进程的虚拟地址空间与文件fd建立映射关系,这样进程就可以像访问内存一样,访问文件了。

#include 
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

函数参数:

start: 映射内存的首地址,必须为内存页大小(PAGE_SIZE)的整数倍,当然也可以置为NULL,表示由系统分配。

length:将文件中映射到内存部分的长度

prot: 映射的内存权限,但不得与文件的打开权限冲突,该参数有以下选项的组合:

  • PORT_READ: 允许读该内存
  • PORT_WRITE: 允许写该内存
  • PORT_EXEC: 允许执行该内存段
  • PORT_NONE: 该内存段不能被访问

flags:控制程序对该内存段修改时,造成的影响范围,常用的选项如下:

  • MAP_PRIVATE: 内存是私有的,对他的修改只在局部范围内有效,其他进程不可见。
  • MAP_SHARED: 内存是共享的,某进程对该段内存空间的更新对其他进程是可见的

fd: 映射文件的文件描述符

offset:映射内容在该文件的起始偏移位置

9、munmap()函数

解除内存与文件之间的映射关系。

#include 
int munmap(void* start,size_t length); 

函数参数:

start:映射的起始地址

length:文件中映射到内存的部分的长度

返回值:

解除成功返回0,失败返回-1。

10、msync()函数

一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

#include 
int msync(const void* start,length,int flags);

函数参数:

start:映射的起始地址

length:文件中映射到内存的部分的长度

flags:标志位,有以下选项:

  • MS_ASYNC: 请内核尽快将修改写入文件
  • MS_SYNC : 在此函数返回前,将修改写入到文件
  • MS_INVALIDATE:内核自行决定是否立即写入

返回值:

解除成功返回0,失败返回-1。

11、ftruncate()/truncate()函数

ftruncate()/truncate()会将文件fd或者路径为path的文件的大小改为参数length指定的大小,如果原来的文件大小比参数length大,则超过的部分会被删除,如果原来的文件大小比参数length小,则文件将被扩展,文件的扩展部分将以\0填充。

#include 
int ftruncate(int fd, off_t length);
int truncate(const char *path,off_t length);

函数参数:

fd:操作的文件的的文件描述符

length:修改的文件大小

path:文件路径+名称

返回值:

成功返回0,失败返回-1。

注意:此函数并未实质性的向磁盘写入数据,只是分配了一定的空间供当前文件使用。当文件大小被扩展时,如果在此时使用十六进制编辑工具打开该文件,你会发现文件末尾多了很多00,此时如果发生系统复位或者装置掉电以后,该函数所产生的作用将被文件系统忽略,也就是说它所分配的空间将不能被识别,文件的大小将会是最后一次写入操作的区域大小,而非ftruncate分配的空间大小,也就是说,文件大小有可能会被改变。

解决方法:可以在执行完ftruncate之后,在新空间的末尾写入一个或以上字节的数据(不为0x00),这样新空间则不为空,文件系统会把这部分空间当成这个文件的私有空间处理,而不会出现文件大小改变的错误。

12、flock()函数

文件锁是一种文件读写机制,在任何特定的时间只允许一个进程访问一个文件。利用这种机制能够使读写单个文件的过程变得更安全。

进程使用flock尝试锁文件时,如果文件已经被其他进程锁住,进程会被阻塞直到锁被释放掉,或者在调用flock的时候,采用LOCK_NB参数,在尝试锁住该文件的时候,发现已经被其他服务锁住,会返回错误。
 

// from /usr/include/sys/file.h
int flock(int fd,int operation);

函数参数:

fd:操作的文件的的文件描述符

operation:操作标志位,有下列四种情况:

  • LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
  • LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
  • LOCK_UN 解除文件锁定状态。
  • LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。

注意:单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。

返回值:

成功返回0,失败返回-1。

 13、fcntl()函数

fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的的各种属性。

#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);

struct flock  {  
    short_l_type;    /*锁的类型*/  
    short_l_whence;  /*偏移量的起始位置:SEEK_SET,SEEK_CUR,SEEK_END*/  
    off_t_l_start;     /*加锁的起始偏移*/  
    off_t_l_len;    /*上锁字节*/  
    pid_t_l_pid;   /*锁的属主进程ID */  
};   

fcntl函数功能依据cmd的值的不同而不同。参数对应功能如下:

(1)F_DUPFD

与dup函数功能一样,复制由fd指向的文件描述符,调用成功则返回新的文件描述符,与旧的文件描述符共同指向同一个文件。

(2)F_GETFD

读取文件描述符close-on-exec标志

(3)F_SETFD

将文件描述符close-on-exec标志设置为第三个参数arg的最后一位

(4)F_GETFL

获取文件打开方式的标志,标志值含义与open调用一致

(5)F_SETF

设置文件打开方式为arg指定方式

(6)F_SETLK

设置或释放锁。当short_l_type为F_RDLCK为读锁,F_WDLCK为写锁,F_UNLCK为解锁。且如果锁被其他进程占用,则返回-1

(7)F_SETLKW

此时也是给文件上锁,不同于F_SETLK的是,该上锁是阻塞方式。当希望设置的锁因为其他锁而被阻止设置时,该命令会等待相冲突的锁被释放。

(8)F_GETLK

第3个参数lock指向一个希望设置的锁的属性结构,如果锁能被设置,该命令并不真的设置锁,而是只修改lock的l_type为F_UNLCK,然后返回该结构体。如果存在一个或多个锁与希望设置的锁相互冲突,则fcntl返回其中的一个锁的flock结构。

14、fileno()函数

将流文件指针转换为文件描述符

// FILE->fd
int fileno(FILE* stream);

函数参数:

stream:要转换的文件指针

返回值:

成功返回转换成功的fd,失败返回-1。

15、pread()/pwrite()函数

我们知道,若同一时间,两个进程对同一个文件进行写操作,由于lseek和read/write 调用之间,内核可能会临时挂起进程,发生意想不到的覆盖问题,所以为了规避这个问题,UNIX系统提供了原子读写函数---pread()/pwrite()。

pread()/pwrite()函数相当于顺序调用了lseek 和 read/write,并将这两个操作相当于一个捆绑的原子操作。 但是请注意,pread()/pwrite()并不会引起文件偏移量的改变

#include
ssize_t  pread (int fd,   void *buf,  size_t  nbytes,  off_t  offset );
ssize_t  pwrite (int fd,   const void *buf,  size_t  nbytes,  off_t  offset );

函数参数:

fd: 读取/写入的 文件描述符

buf:数据缓冲区

nbytes:缓冲区长度

offset:写入或读取文件位置距文件起始位置的长度

返回值:

成功:返回读到的字节数;出错:返回-1;到文件结尾:返回0

16、dup()/dup2()函数

函数dup用来复制一个描述符,即传给函数一个既有的描述符,它就会返回一个新的描述符,这个新的描述符是传给它的描述符的拷贝。另外dup返回的新文件描述符一定是当前可用文件描述符中最小数值

#include 
//等同于 fcntl(int files,F_DUPFD,0);
int dup(int fd);

函数参数:

fd: 待复制的文件描述符

返回值:

成功返回最小的尚未被使用的文件描述符,该文件描述符和fd指向相同的文件表项,失败返回-1

17、dup2()函数

 dup2()跟dup()相似,也是用来复制一个文件描述符,但dup2函数可以用参数newfd指定新文件描述符的数值,并将newfd描述符所对应的文件表改成oldfd所对应的文件表项,之后,newfd与oldfd指向同一个文件表,这样就将newfd重定向到oldfd。若newfd已经被进程打开,系统就会将其关闭以释放该文件描述符。若newfd与oldfd相等,dup2将返回newfd,而不关闭它。

#include 
//等同于 close(filedes2)->fcntl(int files,F_DUPFD,filedes2);
int dup2(int oldfd, int newfd);

函数参数:

oldfd: 待复制的文件描述符

newfd: 指定的新文件描述符

返回值:

成功返回新的文件描述符,出错返回-1

18、umask()函数

为进程创建文件模式屏蔽字,即创建的文件默认不再有cmask权限,除非主动调用chmod()函数修改

头文件:#include
mode_t umask(mode_t cmask);

函数参数:

cmask: 待设置的屏蔽字

返回值:

成功返回之前的屏蔽字

19、readv()/writev()函数

我们经常使用的读写函数是read()和write(),但是每次读写只能读入或者写入到一个缓冲区,今天介绍另一种读写函数,可以一次调用,读写多个非连续缓冲区。

#include 
ssize_t readv(int fd,const struct iovec*iov,int iovcnt);
ssize_t writev(int fd,const struct iovec*iov,int iovcnt);

函数参数:
fd:操作的文件描述符
iov:操作的缓冲区数组首地址,struct iovec结构体内容为:

struct iovec{
    void* iov_base;//缓冲区地址
    ssize_t iov_len;//缓冲区长度
}

 iovcnt:表示iov结构体的数目。
返回值:

成功则返回已读或者已写的字节数,若出错,则返回-1 

19、readn()/writen()函数

我们知道:

  1. 一次read的返回值可能少于所要求的数据长度,即使没有到达文件尾,这不是一个错误,可能是因为信号中断,或者其他原因,此时我们应该继续访问文件。
  2. 一次write的返回值可能少于所要求的数据长度,可能因为内核缓冲区满等原因,这也不是一个错误,我们也应该继续访问文件。

而函数readn和writen将按照你所要求的数据长度不断读写,直到达到你的要求(信号中断也可能导致返回值不是要求长度)。

#include "apue.h"
ssize_t readn(int fd,void* buf,ssize_t nbytes);
ssize_t writen(int fd,void* buf,ssize_t nbytes);

函数参数:

fd:将数据写入的文件的文件描述符
buf:写入的数据的起始位置
size:写入数据的字节数

返回值:

若成功返回已经写入的数据的字节数,若失败返回-1

二、C库文件操作

1、fopen()函数

fopen函数类似于系统调用中的open函数。和open一样,它返回文件的标识符,只是这里叫做流(stream),在库函数里实现为一个指向文件的指针。

#include
FILE *fopen(const char *filename, const char *mode);

函数参数:

filename:打开文件的文件路径+名称

mode:打开的方式:

r

以只读方式打开文件,该文件必须存在。

r+

以读/写方式打开文件,该文件必须存在。

rb+

以读/写方式打开一个二进制文件,只允许读/写数据。

rt+

以读/写方式打开一个文本文件,允许读和写。

w

打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。

w+

打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。

a

以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。

a+

以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。

wb

以只写方式打开或新建一个二进制文件,只允许写数据。

wb+

以读/写方式打开或新建一个二进制文件,允许读和写。

wt+

以读/写方式打开或新建一个文本文件,允许读和写。

at+

以读/写方式打开一个文本文件,允许读或在文本末追加数据。

ab+

以读/写方式打开一个二进制文件,允许读或在文件末追加数据。

返回值:

成功是返回一个非空的FILE *指针,失败返回NULL

2、fwrite()函数

向指定文件中写入数据

#include
size_t fwirte(void *ptr, size_t size, size_t nitems, FILE *stream);

函数参数:

ptr:向文件写入数据的内存首地址

size:指定要写入的单个数据块的大小

nmemb:指定要写入数据块的个数

stream:要写入内容的文件指针

返回值:

成功返回实际写入的字节数,失败返回-1

3、fread()函数

从文件读取数据,即将硬盘上的数据读取到内存中来

#include
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);

函数参数:

ptr:指定保存读取数据的内存首地址

size:指定要读取的单个数据块大小

nitems:指定要读取数据块的个数

stream:文件指针

返回值:

成功返回实际读取的字节数,失败返回-1

4、fseek()函数

设置流fp的文件位置为给定的偏移 

#include
int fseek(FILE *fp,long offset,int fromwhere);

函数参数:

fp:文件指针

offset: 相对whence的偏移量,以字节为单位

whence:操作标志,有以下几种:

  • SEEK_SET:将该文件的偏移量设置为距文件起始处offset个字节;
  • SEEK_CUR:将该文件的偏移量设置为距当前位置offset个字节;
  • SEEK_END:将该文件的偏移量设置为距文件末尾处offset个字节;

返回值:

成功返回0,否则返回非零值

5、fcloseAll()函数

关闭当前线程打开的所有流文件对象

#include 
int fcloseAll(void);

返回值:

成功返回0,否则返回非零值

6、fflush()函数

通过I/O系统调用,将缓冲区内容刷新到磁盘中

#include 
int fflush(FILE* fp);

函数参数:

fp:文件指针

返回值:

成功返回0,否则返回非零值

7、feof()函数

判断二进制文件是否读到文件结尾

#include 
int feof(FILE* fp);

函数参数:

fp:文件指针

返回值:

如果读到文件结尾,返回1,否则返回0

8、ferror()函数

判断指定的流是否出现了错误

#include 
int ferror(FILE* fp);

函数参数:

fp:文件指针

返回值:

文件流出现错误则返回0,否则返回非零值

9、clearerr()函数

清除指定文件流的错误标识位

#include 
void clearerr(FILE* fp);

函数参数:

fp:文件指针

10、rewind()函数

使文件流读写指针移动到文件开头

#include 
int rewind(FILE* fp);

函数参数:

fp:文件指针

返回值:

成功则返回0,失败返回非零值

11、ftell()函数

返回给定流 fp 的当前文件位置 

#include 
long int ftell(FILE *fp);

函数参数:

fp:文件指针

返回值:

成功则返回当前读写指针偏移,失败返回-1

12、临时文件

// 以二进制更新模式(wb+)在 /tmp 文件夹中创建一个临时文件。被创建的临时文件会在流关闭的时候或者在程序终止的时候自动删除
FILE *tmpfile(void);
    
// 生成并返回一个有效的临时文件名,该文件名之前是不存在的。如果s为空,则只会返回临时文件名 
char *tmpnam(char *s);
    
// 按照指定的目录dir及前缀pfx生成一个临时文件,并将文件绝对路径名返回
char *tempnam(const char *dir, const char *pfx);
    
// 按照template传入的字符创建临时文件,注意template 必须以 XXXXXX 结尾,随后创建的临时文件名保证其前缀不变,用随机字符替换这6个X
char* mktemp(char *template);
    
// 意义基本同mktemp,但是该函数不仅创建临时文件,还会以当前用户可读可写的方式打开文件,并返回其文件描述符
int mkstemp(char *template);
     

13、rename()函数

把 old_filename 所指向的文件名改为 new_filename

#include 
int rename(const char *old_filename, const char *new_filename);

函数参数:

old_filename:要被重命名/移动的文件路径+文件名称

new_filename:包含了文件的新文件路径+新文件名称

返回值:

如果成功,则返回零。如果错误,则返回 -1
注意若newname文件已存在已存在,需要分情况处理:

  • (1) 如果oldname为一个文件,那么为该文件更名。在这种情况下,如果newname作为一个目录已存在,则它不能重命名一个目录。如果newname已存在,而且不是一个目录,则先将其删除(rename会负责删除操作)然后将oldname更名为newname。对oldname所在目录以及newname所在的目录,调用进程必须具有写许可权,因为将更改这两个目录。
  • (2) 如若oldname为一个目录,那么为该目录更名。如果newname已存在,则它必须是一个目录,而且该目录应当是空目录(空目录指的是该目录中只有. 和.. 项)。如果newname存在(而且是一个空目录),则先将其删除,然后将oldname更名为newname。另外,当为一个目录更名时,newname不能包含oldname作为其路径前缀。例如,不能将/usr更名为/usr/foo/testdir,因为老名字( /usr/foo)是新名字的路径前缀,因而不能将其删除。
  • (3) 作为一个特例,如果oldname和newname引用同一文件,则函数不做任何更改而成功返回。 

14、remove()函数

删除给定的文件名 filename,以便它不再被访问

#include 
int remove(const char *filename);

函数参数:

filename:要被删除的文件路径+文件名称

返回值:

如果成功,则返回零。如果错误,则返回 -1

15、fdopen()函数

将文件描述符转换为流文件指针 

// fd->FILE
FILE* fdopen(int fd,char* mode);

三、POSIX异步I/O

异步 I/O 是针对同步 I/O 提出的概念,它不需要线程等待 I/O 结果,而只需要请求进行传输,然后系统会自动完成 I/O 传输,结束或者出现错误时会产生相应的 I/O 信号,用户程序只需要设置好对应的信号陷入函数,即可处理一个异步 I/O 事件。

#include 
 
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);
int aio_fsync(int op,struct aiocb *aiocbp);
int aio_error(const struct aiocb *aiocbp);
ssize_t aio_return(const struct aiocb *aiocbp);
int aio_suspend(const struct aiocb *list,int nent,const struct timespec* timeout);
int aio_cancel(int op,struct aiocb *aiocbp);

使用以上API时,需要注意以下几点:

  1. 使用aio_read()或者aio_write()将操作请求放入到等待处理的队列中。这两个函数的返回值与实际操作结果并没有任何关系
  2. aio_fsync()函数用于将aio_write()的写请求同步到硬盘,op指定异步的方式:
    1. O_DSYNC:表示只是更新硬盘文件内容,
    2. O_SYNC:表示等待实际I/O结束才返回,从而确保修改过的块立即写到硬盘上。
  3. aio_error()是为了获取到一个异步读写或同步的状态,返回值有以下几种情况:
    1. 0:异步操作完成,可以调用aio_return()函数获取操作返回值
    2. -1:aio_error函数调用失败
    3. EINPROGRESS:异步读写或同步仍未完成
  4.  如果aio_error()获取到异步操作已经完成,则可以使用函数aio_return()获取异步操作的返回值,此时必须确保异步读写或同步已经完成,函数调用失败返回-1.
  5.  如果在完成了所有事务之后,异步IO仍然没有完成,可以使用aio_suspend()阻塞进程,直到所有的操作完成,其中list是AIO控制块数组,nent表明了数组的个数。aio_suspend()的返回值有三种:
    1. 如果被信号中断,它将返回-1,并将errno设置为EINTR.
    2. 如果timeout超时,它将返回-1,并将errno设置为EAGAIN,timeout为NULL则表示不限时
    3. 如果返回值为0,则表示所有异步IO都已经完成,将不阻塞直接返回
  6. 当我们不想等待异步IO的操作时,可以使用函数aio_cancel()来尝试取消他们。fd为操作的文件描述符,aiocbp为操作的数据块,如果aiocbp为NULL,则表示取消fd上面的所有异步IO操作。返回值有以下4种情况:
    1.    AIO_ALLDONE:所有异步IO操作在尝试取消他们之前都已经完成
    2.    AIO_CANCELED:所有要求的操作已被取消
    3.    AIO_NOTCANCELED:至少一个操作请求没有被取消。
    4.    -1:函数调用失败

其中struct aiocb的结构体内容为:

#include 
struct aiocb {
    int             aio_fildes;     /* 用户用来读或者写的文件描述符 */
    off_t           aio_offset;     /* 开始读写的偏移量 */
    volatile void  *aio_buf;        /* 用于读或写的缓冲区 */
    size_t          aio_nbytes;     /* 操作的字符个数 */
    int             aio_reqprio;    /* 操作的优先级,但不一定生效 */
    struct sigevent aio_sigevent;   /* 异步IO完成后,通知进程的方式,详细见下面 */
    int             aio_lio_opcode; /* Operation to be performed;lio_listio() only */
};
 
struct sigevent{
    int             sigev_notify;    // 通知类型
    int             sigev_signo;     // 信号值
    union sigval    sigev_value;     // 回调函数的参数
    void (*sigev_notify_function)(union sigval);    // 回调函数
    pthread_attr_t *sigev_notify_attributes;       
};

上面的sigev_notify的取值有以下三种:

  • SIGEV_NONE:异步I/O请求完成后,不通知进程
  • SIGEV_SIGNAL:异步I/O请求完成后,产生由sigev_signo字段指定的信号。如果应用程序已选择捕捉信号,且在建立信号处理程序的时候指定了SA_SIGINFO标志,那么该信号将被入队(如果实现支持排队信号),信号处理程序会传送给一个siginfo结构,该结构的si_value字段被设置为sigev_value
  •  SIGEV_THREAD:当异步I/O请求完成后,由sigev_notify_function指定的函数被调用,sigev_value是唯一的参数。除非sigev_notify_attributes字段被设定为pthread属性结构的地址,且结构指定了另外的线程属性,否则该函数将在分离状态下的单独的线程下执行。

还有一个函数,它既可以以同步接口调用,也可以以异步接口调用:

#include 
int aio_listio(int mode,struct aiocb* list,int nent,struct sigevent* sigev);

函数参数:

mode:该参数决定该函数是异步还是同步IO:

  • LIO_WAIT:表示该函数是同步的,即在所有操作完成后,函数才返回,此时sigev将被忽略
  • LIO_NOWAIT:表示该函数是异步IO,即只是将访问请求放在队列中即返回,在所有IO结束后,按照sigev参数指定的方式被异步通知

sigev:如果不想被通知,可以将sigev设置为NULL,注意,每个控制块都有自己在完成后的通知方式,这里的sigev是另外附加的,它是是所有操作完成后,进行通知。

list:控制块的列表

nent:个数

四、目录操作

1、opendir()函数

打开参数dirname 指定的目录, 并返回DIR*形态的目录流

#include 
DIR* opendir(const char* dirname);

函数参数:

dirname:目录路径

返回值:

成功返回DIR*形态的目录流,失败返回NULL

2、closedir()函数

关闭参数dir 所指的目录流

#include 
#include 

int closedir(DIR *dir);

函数参数:

dir:打开的目录流

返回值:

关闭成功则返回0,,失败返回-1,

3、readdir()函数

读取目录内容

#include 
struct dirent* readdir(DIR* dir);

函数参数:

dir:打开的目录流。

其中struct dirent定义如下:

// from dirent.h
struct dirent
{
    long ino;                    //目录中某文件的innode值
    _kerbel_off_t d_off;         // 从目录开始到当前目录条的距离
    unsigned short d_reclen;     // 目录中某文件名的长度
    char d_name[256];            //  目录中某文件名
}

返回值:

readdir() 函数成功时,返回指向 dirent 结构的指针这个结构是静态分配的,不要试图去free它),失败返回NULL

4、telldir()/seekdir()/rewinddir函数

定位目录读取位置

// 返回目录流的当前位置
long int telldir(DIR* dirp);     
// 设置下一个readdir()位置        
void seekdir(DIR* dirp,long int loc);   
// 将目录流的位置重置到目录的开头
void rewinddir(DIR* dirp);             

 5、getcwd()/chdir()函数

// 获取当前工作路径的绝对路径到buf中,并返回buf
char* getcwd(char* buf,size_t size); 
// 修改当前进程的工作路径,成功返回0,失败返回-1
int chdir(const char* path);

6、目录操作示例

读取某路径下所有文件,并打印文件名称:

// 标准输入输出头文件
#include 
// 目录操作头文件
#include 
int main(int argc,char** argv)
{
    DIR* dp;
    struct dirent* dirp;
    // 参数个数小于1则报错
    if (argc != 2) {
        printf("please input which dir display\n");
        return -1;
    }
    // 目录打开失败则报错
    if ((dp = opendir(argv[1])) == NULL) {
        printf("dir %s not exist!",argv[1]);
        return -1;
    }
    // 读取指定目录下的文件信息,每次读取,指针自动指向下一个,直到为NULL则表示读取完毕
    while((dirp = readdir(dp)) != NULL) {
        printf("%s\n",dirp->d_name);
    }
 
    closedir(dp);
 
    return 0;
 
}

你可能感兴趣的:(Linux,linux,系统调用,文件操作,open,write)