文件IO

不带缓冲的io(unix系统在内核中设有缓冲区,这个不带缓冲意思是用户不自己缓冲),每个read和write都调用内核中的一个系统调用。

文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。

Unix系统shell把文件描述符0与进程的标准输入关联,文件描述符1与标准输出关联,文件描述符2与标准错误关联。在符合POSIX.1的程序中,幻数0,1,2已被标准化为符号常量STDIN_FILENO, STDOUT_FILENO和STDERR_FILENO。这些常量定义在<unistd.h>文件中。文件描述符的变化范围是0~OPEN_MAX-1

文件IO函数

1
2
3
4
5
6
7
#include<fcntl.h>
//open函数成功返回文件描述符,出错返回-1
int open(const char *path, int oflag,... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
int creat(const char *path, mode_t mode);
#include<unistd.h>
int close(int fd);

oflag参数可以使用下列一个或多个常量进行“或”运算构成oflag参数,此参数用来说明此函数的多个选项。

  • O_RDONLY 只读打开 0

  • O_WRONLY 只写打开 1

  • O_RDWR 读写打开 2

  • O_EXEC 只执行打开

  • O_SEARCH 只搜索打开(–) 上述5个常量中必须指定一个且只能指定一个。

  • O_APPEND 写时追加,每次写时追加到文件末尾

  • O_CLOEXECFD_CLOEXEC常量设置为文件描述符的标志

  • O_CREAT 若此文件不存在则创建它

  • O_DIRECTORY 如果path引用的不是目录,则出错

  • O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错,可以用此测试文件是否存在

  • O_NOCTTY 如果path引用的时终端设备,则不将该设备分配作为此进程的控制终端

  • O_NOFOLLOW 如果path引用的时一个符号链接,则出错

  • O_NONBLOCK 如果path引用的是一个FIFO,块特殊文件或字符文件,则设置本次打开及后续操作为非阻塞i/o操作

  • O_SYNC 每次write都等待物理i/o操作完成

  • O_TRUNC 如果文件存在,而且为只写或读-写成功打开,则将其长度截断为0

  • O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios的参数值,使其符合Single Unix Specification

  • O_DSYNC 使每次write都要等待物理i/o操作完成

  • O_RSYNC 使每一个以文件描述符作为参数进行的read操作等待,直至所有对文件同一部分挂起的写操作完成

用open和openat函数返回的文件描述符一定是最小的未用描述符数值。

fd参数把open和openat参数区分开,共有三种可能性

  • path时绝对路径名,这种情况忽略fd参数,openat相当于openat
  • path参数指定的时相对路径名,fd参数指出相对路劲名的开始地址,fd参数是通过打开相对路径名所在的目录来获取的
  • path参数制定了相对路径名,fd参数具有特殊值AT_FDCWD,这种情况下,路径名在当前工作目录中获取,openat函数在操作上与open函数一致

openat()是在新增的一类函数之一希望解决两个问题:

  • 让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录
  • 可以避免time-of-check-to-time-of-use(TOCTTOU)错误

creat(const char *path, mode_t mode)函数等效于open(path, O_WRONLY|O_CREAT|O_TRUNC, mode)creat函数以只写的方式打开创建的文件。

关闭一个文件时还会释放该进程加在该文件上的所有记录锁。当一个进程终止时,内核会自动关闭它所有的打开文件。

  • 每个打开文件都有一个与其相关的当前文件偏移量(current file offset),用以度量从文件开始处计算的字节数。
  • 通常读,写操作都是从当前文件偏移量开始,并使偏移量增加所读写的字节数
  • 系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则偏移量设为0

可以使用lseek函数为打开的文件设置偏移量。成功返回新的文件偏移量,出错返回-1。该方法也可以用来判断涉及的文件是否可以设置偏移量。

1
2
#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);

参数offset与参数whence的值有关:

  • 若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节。
  • 若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加上offset,offset可为正或负。
  • 若whence是SEEK_END,则将该文件的偏移位置为文件长度加offset,offset可为正或负。

lseek仅当当前文件偏移量记录都在内核中,并不引起I/O操作,然后偏移量被用于下一个读写操作

文件偏移量可以大于文件长度,对该文件的下一次写将加长该文件并形成一个空洞,位于文件中但是没有写过的字节都被读为0

文件空洞不要求在磁盘上占用存储区,具体处理方式和文件系统有关,因为lseek使用的偏移量是用offset表示的,所以允许具体实现根据各自特定平台自行选择大小合适的数据类型。

od -c命令可以观察文件中的实际内容,-c标志以字符方式打印文件内容。

1
2
3
4
5
#include<unistd.h>
//返回读到的文件字节数,若已到文件尾,返回0,出错返回-1
ssize_t read(int fd, void *buf, size_t nbytes);
//返回已写入的字节数,若出错,则返回-1
ssize_t write(int fd, const void *buf, size_t nbytes);

有多种情况可以使读到的字节数少于要求读的字节数:

  • 读普通文件时,读到文件要求的字节数前已经到达文件尾端
  • 当从终端设备读时,通常一次最多读一行
  • 当从网络读时,网络中的缓冲区机制可能造成返回值小于要求读的字节数
  • 当从管道和FIFO读时,如果管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数
  • 当一信号中断,而已经读了的部分数据量时

读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加时机读到的字节数。

返回值通常和nbytes的值相同,否则意味着出错,一次成功写,offset增加实际写的字节数。

write出错的常见原因是磁盘已经满了,或者超过了一个给定进程的文件长度限制。

对于普通文件,写操作从文件的当前偏移量开始,如果在打开该文件时指定了O_APPEND选项,则每次都将文件偏移量移到末尾。

大多数文件系统为了改善性能采取了预读技术,预读就是当检测到正在顺序读的时候,系统就试图读取比应用所要求的更多的数据。

如果重复度量程序性能,可能后续得到的结果比第一次好,因为后续各次运行一般从系统高速缓存访问,不需要在从文件系统进入高速缓存。

文件共享操作

unix系统支持在不同进程间共享打开的文件。内核用于I/O的数据结构。

内核使用3种数据结构表示打开文件:

  • 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,每个文件描述符表中项都包含:
    • 文件描述符标志。
    • 指向一个文件表项的指针。
  • 内核为所有打开文件维持一张文件表项,每个文件表包含:
    • 文件状态标志(读,写,添加,同步和非阻塞等)。
    • 当前文件偏移量。
    • 指向该文件v结点 表项的指针。
  • 每个打开文件(或设备)都有一个v结点结构(linux下的i-node)。v结点包含了文件类型和对此文件进行各种操作的函数的指针

打开文件的内核数据结构

两个独立进程各自打开同一个文件,每个进程有自己的文件表项,即拥有自己的当前偏移量。lseek函数只修改文件表项中的当前文件偏移量,不进行任何IO操作。

两个独立进程各自打开同一个文件

原子操作

一般而言原子操作(atomic operation)指的是由多步组成的一个操作。如果该操作原子的执行,则要么执行完所有的步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

1
2
3
4
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset) ;
//成功返回读到的字节数出错返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset) ;
//成功返回已写的字节数,出错返回-1

复制文件描述符操作:

1
2
3
4
5
6
#include<unistd.h>
int dup(int fd);    //返回新文件描述符时当前可用文件描述符中最小值
int dup2(int fd, int fd2);  //fd2可以指定新描述符的值,不一定最小值。
//等效于
close(fd2);
fcntl(fd, F_DUPFD, fd2);

dup2是一个原子操作,而close和fcntl包含两个函数调用。

传统的UNIX系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候写入磁盘。这种方式被称为延迟写(delayed write)。

update系统守护进程周期性的调用sync函数,定期(大约30s)flush缓冲区。

1
2
3
4
5
6
7
#include <unistd.h>
/* 只对由文件描述符fd指定的一个文件起作用,等待写磁盘结束后返回,一般用于数据库 */
int fsync(int fd) ;
/* 类似于fsync,只影响文件的数据部分,fsync还会同步更新文件属性 */
int fdatasync(int fd) ;
/* 将所有修改过的块缓冲区排入写队列,然后返回,实际上并不等待写磁盘结束*/
void sync(void) ;

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

1
2
#include<fcntl.h>
int fcntl(int fd, int cmd, .../* int arg */);

fcntl函数有一下5种功能:

  1. 复制一个已有的描述符.(cmd=F_DUPFD/F_DUPFD_CLOEXEC)
  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_DUPFD 复制文件描述符fd
  • F_DUPFD_CLOEXEC 复制文件描述符,设置与新文件描述符关联的FD_CLOEXEC文件描述符值,返回新文件描述符
  • F_GETFD 对于fd的文件描述符标志作为函数值返回
  • F_SETFD 对于fd设置文件描述符标志
  • F_GETFD 对应于fd的文件状态标志作为函数值返回。
  • F_SETFL 将文件状态标志设置为第三个参数的值
  • F_GETOWN 获取当前SIGIO和SIGURG信号的进程ID或进程组id。

fcntl的返回值与命令有关,如果出错都返回-1,成功返回某个其他值。

在修改文件描述符标志或文件状态标志时必须谨慎,先要获得现有的标志值,然后按照期望修改它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "apue.h"
#include <fcntl.h>

void set_fl(int fd, int flags) /* flags are file status flags to turn on */
{
    int     val;

    if ((val = fcntl(fd, F_GETFL, 0)) < 0)
        err_sys("fcntl F_GETFL error");

    val |= flags;       /* turn on flags */

    if (fcntl(fd, F_SETFL, val) < 0)
        err_sys("fcntl F_SETFL error");
}
1
2
3
4
#include <unistd.h>        /* System V */
#include <sys/ioctl.h>     /* BSD and Linux */

int ioctl(int filedes, int request, ...);

较新的系统都提供名为/dev/fd的目录,其目录项为0,1,2,等的链接文件,打开文件/devfd/n等效于复制描述符n。

文件与目录

stat系列函数

1
2
3
4
5
6
#include <sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf);
int fstatat(int fd, const char *restrict pathname, struct 
stat *restrict buf, int flag);

这四个函数返回文件相关的结构信息,成功返回0,失败返回-1;

stat函数返回与此命名文件(pathname)有关的信息结构。

fstat函数获得已在描述符fd上打开文件的有关信息。

当文件是一个符号链接时,lastat返回该符号链接有关的信息。

fstatat函数为一个相对于当前打开目录(由fd参数指向)的路径返回文件统计信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct stat {
    mode_t    st_mode;      /* file type & mode (permissions) */
    ino_t     st_ino;       /* i-node number (serial number) */
    dev_t     st_dev;       /* device number (file system) */
    dev_t     st_rdev;      /* device number for special files */
    nlink_t   st_nlink;     /* number of links */
    uid_t     st_uid;       /* user ID of owner */
    gid_t     st_gid;       /* group ID of owner */
    off_t     st_size;      /* size in bytes, for regular files */
    time_t    st_atime;     /* time of last access */
    time_t    st_mtime;     /* time of last modification */
    time_t    st_ctime;     /* time of last file status change */
    blksize_t st_blksize;   /* best I/O block size */
    blkcnt_t  st_blocks;    /* number of disk blocks allocated */
};

文件类型

  • 普通文件
  • 目录文件(只有内核能写目录)
  • 块特殊文件(提供对设备带缓冲的访问,每次访问以固定长度为单位进行)
  • 字符特殊文件(对设备提供不带缓冲的访问,每次访问长度可变。)
  • FIFO,命名管道
  • 套接字,socket
  • 符号链接,这种类型的文件指向另一个文件。

文件类型信息包含在stat结构的st_mode成员中。这些宏的参数都是stat结构中的st_mode成员。

Macro Type of file
S_ISREG() regular file
S_ISDIR() directory file
S_ISCHR() character special file
S_ISBLK() block special file
S_ISFIFO() pipe or FIFO
S_ISLNK() symbolic link
S_ISSOCK() socket
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "apue.h"
int main(int argc, char *argv[])
{
    int         i;
    struct stat buf;
    char        *ptr;

    for (i = 1; i < argc; i++) {
        printf("%s: ", argv[i]);
        if (lstat(argv[i], &buf) < 0) {
            err_ret("lstat error");
            continue;
         }
         if (S_ISREG(buf.st_mode))
            ptr = "regular";
         else if (S_ISDIR(buf.st_mode))
            ptr = "directory";
         else if (S_ISCHR(buf.st_mode))
            ptr = "character special";
         else if (S_ISBLK(buf.st_mode))
            ptr = "block special";
         else if (S_ISFIFO(buf.st_mode))
            ptr = "fifo";
         else if (S_ISLNK(buf.st_mode))
            ptr = "symbolic link";
         else if (S_ISSOCK(buf.st_mode))
            ptr = "socket";
         else
            ptr = "** unknown mode **";
         printf("%s\n", ptr);
  }
   exit(0);
}

用户ID与组ID

说明
实际用户ID 我们是谁,在登录时取自口令文件
实际组ID
有效用户ID 用于文件访问权限检查
有效组ID
附属组ID
保存的设置用户ID 由execl函数保存,有效用户,组ID的副本
保存的设置组ID

通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID。

当执行一个程序文件时,进程的有效用户ID通常是实际用户ID,有效组ID通常是实际组ID。但可以在文件模式字(st_mode)中设置一个特殊标记,使执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid),将进程的有效组ID设置为文件的组所有者ID(st_gid)。在文件模式字中的这两位被称为设置用户ID(set-user-ID)位和设置组ID(set-group-ID)位。

例如,若文件所有者是超级用户,且设置了该文件的设置用户ID位,那么当该程序文件由一个进程执行时,该进程具有超级用户权限。

设置用户ID位及设置组ID位都包含在文件的st_mode值中,这两位分别用常量S_ISUIDS_ISGID测试。

文件访问权限

st_mode屏蔽 含义
S_IRUSR 用户(文件所有者)读
S_IWUSR 用户写
S_IXUSR 用户执行
S_IRGRP 组读
S_IWGRP 组写
S_IXGRP 组执行
S_IROTH 其它读
S_IWOTH 其它写
S_IXOTH 其它执行

如果path环境变量指定了一个我们不具有执行权限的目录,那么shell绝不会在该目录下找到可执行文件。

新文件的用户id设置为进程的有效用户id,组id可以是进程有效组id,也可以是所在目录的组id。

open函数打开一个文件时,内核以进程的有效用户ID和有效组ID为基础执行其访问权限测试。accessfaccessat函数是按实际用户ID和实际组ID进行访问权限测试。

1
2
3
4
5
6
#include <unistd.h>
//两函数成功返回0,出错返回-1
//mode 可为是否存在读写执行的按位或
//F_OK,R_OK,W_OK,X_OK
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag );

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值。cmask参数是(S_IRUSR,S_IWUSR等)9个常量中的若干位按“或”构成。在文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭。

1
2
#include <sys/stat.h>
mode_t umask(mode_t cmask);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include "apue.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
    if (argc != 2)
        err_quit("usage: a.out <pathname>");
    if (access(argv[1], R_OK) < 0)
        err_ret("access error for %s", argv[1]);
    else
        printf("read access OK\n");
    if (open(argv[1], O_RDONLY) < 0)
        err_ret("open error for %s", argv[1]);
    else
        printf("open for reading OK\n");
    exit(0);
}
1
umask -S //打印符号格式

chmod,fchmod与fchmodat这三个函数用于更改现有文件的访问权限。为了更改一个文件的访问权限位,进程的有效用户ID必须等于文件的所有者ID,或者该进程必须具有超级用户权限。

1
2
3
4
5
#include <sys/stat.h>
//3个函数成功返回0,出错返回-1
int chmod(const char *pathname, mode_t mode);
int fchmod(int filedes, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);

如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件。

  • 拥有该文件
  • 拥有此目录
  • 是超级用户

函数chown,fchown,fchownat和lchown用于更改文件的用户ID和组ID。

1
2
3
4
5
6
7
#include <unistd.h>
//成功返回0,出错返回-1
int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int filedes, uid_t owner, gid_t group);
int lchown(const char *pathname, uid_t owner,gid_t group);
int truncate(const char *pathname, off_t length);
int ftruncate(int filedes, off_t length);

stat结构成员st_size表示以字节为单位的文件的长度,对于符号链接,文件长度就是在文件名中的实际字节数。

文件空洞是由偏移量超过文件尾部后写入数据造成。

剪裁文件长度:

1
2
3
4
#include <unistd.h>
//成功返回0,出错返回-1
int truncate(const char *pathname, off_t length);
int ftruncate(int filedes, off_t length);

文件系统

我们可以把一个磁盘分成一个或多个分区。每个分区可以包含一个文件系统。i节点是固定长度的记录项,它包含有关文件的大部分信息。

每个i节点都有一个链接计数标记,表示指向该i节点的文件/目录的数量,只有当链接计数标记减少到0时,才会释放该文件所占用的数据块,可以从图中看到有两个目录项指向同一个i节点。

i节点包含了文件的所有信息,包含文件的权限位信息,文件类型文件长度等(文件名保存在目录项中)。

在不更换文件系统的情况下为一个文件重命名,该文件的实际位置并没有更改,只需在目录块中构造一个新的目录项指向原先的i节点,并将原来的目录项删除即可。

创建一个新目录项newpath,它引用现有文件existingpath,如果newpath已存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。

1
2
3
4
5
6
#include <unistd.h>
int link(const char *existingpath, const char *newpath);
int unlink(const char *pathname); //删除目录项,且pathname引用文件链接计数减1。
#include <stdio.h>
int remove(const char *pathname);
int rename(const char *oldname, const char *newname);

符号链接是对一个文件的间接指针,与硬链接有所不同,硬链接直接指向文件的i节点。硬链接通常要求链接和文件位于同一个文件系统中。只有超级用户才能创建指向目录的硬链接(底层文件支持的情况下)。

1
2
3
4
5
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
//打开符号链接本身,读取该链接中的名字
ssize_t readlink(const char* restrict pathname,
 char *restrict buf, size_t bufsize);

文件的时间

字段 说明 例子 ls(l)选项
st_atim 文件数据的最后访问时间 read -u
st_mtim 文件数据的最后修改时间 write 默认
st_ctim i节点状态的最后更改时间 chmod,chowm -c

修改时间(st_mtim)是文件内容最后一次被修改的时间。状态更改时间(st_ctim)是该文件的i节点最后一次被修改的时间。影响i节点的操作很多,比如更改文件的访问权限,更改用户ID,更改链接数等。

系统不维护对一个i节点的最后一次访问时间,所有access和stat函数并不更改这3个时间中的任一个。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include<sys/stat.h>
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, \
    const struct timespec times[2], int flag);

#include <utime.h>
struct utimbuf {
    time_t actime;    /* access time */
    time_t modtime;   /* modification time */
}

int utime(const char *pathname, const struct
 utimbuf *times);
1
2
3
4
#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
#include <unistd.h>
int rmdir(const char *pathname);

目录操作

只有内核才能写目录,一个目录的写权限位和执行权限位决定了在该目录中能否创建新文件以及删除文件,并不表示能否写目录本身。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);

struct dirent *readdir(DIR *dp);

void rewinddir(DIR *dp);
int closedir(DIR *dp);

long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);
//更改当前工作目录。
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int filedes);

char *getcwd(char *buf, size_t size);

标准IO库

ASCII字符集可以单字节表示,国际字符集可以用多个字节表示。

流的定向(stream’s orientation)决定了所读,写的字符是单字节还是多(宽)字节的。当一个流最初被创建时,没有定向。如果在未定向的流上使用一个多字节I/O函数,则将流的定向设置为宽字节。如果在未定向的流上使用一个单字节I/O函数,则将流的定向设置为字节定向的。

fwide函数可用于设置流的定向。若流是宽定向的,返回正值,字节定向的返回负值,未定向的但会0。

1
2
3
#include<stdio.h>
#include<wchar.h>
int fwide(FILE *fp, int mode);

fwide并不改变已定向的流的定向。

  • mode参数值为负,fwide试图使指定的流是字节定向的。
  • mode参数值为正,fwide试图使指定的流是宽定向的。
  • mode参数值为0,fwide不试图设置流的定向,但返回标识该流定向的值。

对一个进程预定义了这三个流,通过预定义文件指针stdin,stdout,stderr加以引用。

标准io提供三种缓冲

  • 全缓冲,填满缓冲区后才进行实际io操作,驻留磁盘文件按通常全缓冲,flush说明标准io进行缓冲区的清洗操作,可由标准io自动冲洗,调用fflush可以手动冲洗。
  • 行缓冲,在输入输出遇到换行符的受进行io操作,标准io提供的行缓冲区时固定长度,填满缓冲区时没有换行符也进行io操作,当从一个不带缓冲的流或一个行缓冲流得到输入数据,就会重复所有行缓冲流。
  • 不缓冲,不对字符进行缓冲存储,标准错误不缓冲。

对任何一个给定的流,如果不喜欢这些系统默认,那么可以更改缓冲类型。这些函数一定要在流已被打开后调用,且在对流执行任何一个其他操作之前调用。

1
2
3
4
#include<stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, \
        int mode, size_t size);

使用vsetbuf可以精确说明缓冲类型:_IOFBF(全缓冲),_IOLBF(行缓冲),IONBF(不带缓冲)。

fflush函数使该流所有未写的数据都被传送至内核。若fp是NULL,则此函数将导致所有输出流被冲洗。

1
2
#include<stdio.h>
int fflush(FILE *fp);

打开关闭流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<stdio.h>
//打开pathname指定的文件
FILE *fopen(const char *restrict pathname, \
        const char * restrict type) ;
//在一个指定的流上打开一个指定的问文件,若流已经打开,则先关闭该流
FILE *freopen(const char *restrict pathname, \
        const char * restrict type,FILE * restrict fp) ;
//取一个已有的文件描述符,并使一个标准io流于该文件描述符结合(使用fd打开流)
//此函数常用于创建管道和网络通信通道函数返回的描述符
FILE *fdopen(int fd, const char *type) ;
//成功返回文件指针,错误返回NULL
int fclose(FILE* fp);

非格式化I/O流

有三种不同类型的非格式化IO:

  • 每次一个字符的IO
  • 每次一行的IO
  • 直接IO
1
2
3
4
5
6
7
#include<stdio.h>
int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar(void);
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

函数getchar等同于getc(stdin)getc可被实现为宏,fgetc不能实现为宏。getc的参数不应当具有副作用,它可能被计算多次。put系列函数与get类似。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include<stdio.h>
//从指定的流中读,必须指定缓冲区的长度,缓冲区以null字节结尾
char *fgets(char *restrict buf, int n, FILE *restrict fp) ;
//从标准输入读,gets函数不将换行符写入gets
char * gets(char*buf) ;
//  成功返回buf,失败或到达尾端返回NULL
//这两个函数都指定了缓冲区的地址,读入的行将送入其中
//从指定的流中读,必须指定缓冲区的长度,缓冲区以null字节结尾
char *fputs(char *restrict str, int n, FILE *restrict fp) ;
//从标准输入读
char * puts(const char *buf) ;
//  成功返回非负值,失败返回EOF
//这两个函数都指定了缓冲区的地址,读入的行将送入其中
1
2
3
size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp) ;
size_t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp) ;
// 返回读写的对象数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
long ftell(FILE *fp);
int fseek(FILE *fp, long offset, int whence);
void rewind(FILE *fp);

#include <stdio.h>
off_t ftello(FILE *fp);
int fseeko(FILE *fp, off_t offset, int whence);

#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);

格式化IO

1
2
3
4
5
6
7
8
#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char
 *restrict format, ...);
int sprintf(char *restrict buf, const char
 *restrict format, ...);
int snprintf(char *restrict buf, size_t n,
             const char *restrict format, ...);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <stdio.h>

int scanf(const char *restrict format, ...);

int fscanf(FILE *restrict fp, const char *restrict
 format, ...);

int sscanf(const char *restrict buf, const char
 *restrict format,
           ...);
1
2
#include <stdio.h>
int fileno(FILE *fp);

系统数据文件和信息

1
2
3
4
#include <sys/utsname.h>
int uname(struct utsname *name);
#include <unistd.h>
int gethostname(char *name, int namelen);

日期

unix内核提供的基本时间服务是计算自协调时间时UTC公元1970年1月1日00:00:00这一特定时间以来经过的秒数。

1
2
3
#include <time.h>
//成功返回时间值,失败返回-1
time_t time(time_t *calptr);

clock_gettime函数可用于获取指定时钟的时间。

1
2
3
#include <sys/time.h>
int clock_gettime(clockid_t clock_id, struct timespec *tsp);
int clock_settime(clockid_t clock_id, const struct timespec *tsp);
标识符 选项 说明
CLOCK_REALTIME 实时系统时间
CLOCK_MONOTONIC _POSIX_MONOTONIC_CLOCK 不带负跳数的实时系统时间
CLOCK_PROCESS_CPUTIME_ID _POSIX_CPUTIME 调用进程的CPU时间
CLOCK_THREAD_CPUTIME_ID _POSIX_THREAD_CPUTIME 调用线程的CPU时间
1
2
3
4
5
6
7
8
9
#include <sys/time.h>

struct timeval {
    time_t tv_sec;    /* seconds */
    long   tv_usec;   /* microseconds */
};

int gettimeofday(struct timeval *restrict tp, void
 *restrict tzp);

localtimegmtime函数将日历时间转换分解为struct tm结构。区别在于localtime将日历时间转换为本地时间,gmtime将日历时间转换成协调统一时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <time.h>

struct tm {          /* a broken-down time */
    int  tm_sec;     /* seconds after the minute: [0 - 60] */
    int  tm_min;     /* minutes after the hour: [0 - 59] */
    int  tm_hour;    /* hours after midnight: [0 - 23] */
    int  tm_mday;    /* day of the month: [1 - 31] */
    int  tm_mon;     /* months since January: [0 - 11] */
    int  tm_year;    /* years since 1900 */
    int  tm_wday;    /* days since Sunday: [0 - 6] */
    int  tm_yday;    /* days since January 1: [0 - 365] */
    int  tm_isdst;   /* daylight saving time flag: <0, 0, >0 */
};

struct tm *gmtime(const time_t *calptr);
struct tm *localtime(const time_t *calptr);
1
2
3
#include <time.h>
//将本地时间转换为日历时间。
time_t mktime(struct tm *tmptr);
1
2
3
4
#include <time.h>

char *asctime(const struct tm *tmptr);
char *ctime(const time_t *calptr);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <time.h>
//strftime生成定制字符串
size_t strftime(char *restrict buf, size_t maxsize,
                const char *restrict format,
                const struct tm *restrict tmptr);

size_t strftime_l(char *restrict buf, size_t maxsize,
                const char *restrict format,
                const struct tm *restrict tmptr, locale_t locale);
//将字符串时间转换成分解时间
char *strptime(const char *restrict buf, 
                const char *restrict format,
                const tm *restrict tmptr);