【读写 (Input/Output, I/O)】在主存储器与外部设备之间转移数据。
【文件 (file)】在 Linux 系统中所有读写设备(网卡、硬盘、终端)被统称为文件。
0 == STDIN_FILENO
1 == STDOUT_FILENO
2 == STDERR_FILENO
lseek
\n
】即 LF (line feed),用于 Linux 及 macOS 系统\r\n
】其中 \r
即 CR (carriage return),用于 Windows 系统及网络一种特殊的文件,其数据为一数组,数组元素为指向其他文件的链接。
.
】..
】/
】pwd
】print working directorycd
】change directorymkdir
】make directoryrmdir
】remove directory见《网络编程》
open()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(char *filename, int flags, mode_t mode);
/* Returns: new file descriptor if OK,
−1 on error */
flags
O_RDONLY
O_WRONLY
O_RDWR
O_CREAT
O_TRUNC
O_APPEND
mode
只在 flags
含 O_CREAT | O_TMPFILE
时起作用。
S_IRUSR, S_IWUSR, S_IXUSR
can be read/write/execute by current userS_IRGRP, S_IWGRP, S_IXGRP
can be read/write/execute by current groupS_IROTH, S_IWOTH, S_IXOTH
can be read/write/execute by others每个进程有一个 umask
值,可由 umask()
设置。
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH/* rw-rw-rw- */
#define DEF_UMASK S_IWGRP|S_IWOTH /* ~DEF_UMASK == rwxr-xr-x */
umask(DEF_UMASK); /* umask = DEF_UMASK */
fd = Open("foo.txt", O_CREAT|O_TRUNC|O_WRONLY, DEF_MODE);
/* set access permission bits to (DEF_MODE & ~DEF_UMASK) 即 rw-r--r-- */
close()
#include <unistd.h>
int close(int fd);
read()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t n);
/* Returns: number of bytes read if OK,
0 on EOF,
−1 on error */
write()
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t n);
/* Returns: number of bytes written if OK,
−1 on error */
read()
及 write()
的返回值(实际读写的字节数),可能小于传入的 n
(请求读写的字节数)。
不会发生于读(遇到 EOF 除外)写硬盘,但可能发生于
fd
的当前位置起还有 20
字节未读,则调用 read(fd, buf, 50)
返回 20
,再调用 read(fd, buf += 20, 50)
返回 0
,这两次的返回值都属于 short count。read
每次读入一行,返回该行的字节数。read()
或 write()
只能读写部分数据,因此 robust (network) applications 需多次调用 read()
或 write()
。接口与 Unix I/O 的 read()
及 write()
相同。
适用于从网络套接字读写二进制数据。
#include "csapp.h"
ssize_t rio_readn (int fd, void *user_buf, size_t n);
ssize_t rio_writen(int fd, void *user_buf, size_t n);
/* Returns: number of bytes transferred if OK,
0 on EOF or `n == 0` (rio_readn only)
−1 on error */
rio_readn()
【思路】多次调用 Unix I/O 的 read()
,直到请求的字节数都被读入,或遇到 EOF(唯一可能出现 short count 的情形)。
ssize_t rio_readn(int fd, void *user_buf, size_t n_request) {
size_t n_left = n_request;
ssize_t n_read;
char *pos = user_buf;
while (n_left > 0) {
if ((n_read = read(fd, pos, n_left)) < 0) {
if (errno == EINTR) /* Interrupted by sig handler return */
n_read = 0; /* and call `read()` again */
else
return -1; /* `errno` set by `read()` */
}
else if (n_read == 0)
break; /* EOF */
n_left -= n_read; pos += n_read;
}
return (n_request - n_left); /* short count only on EOF */
}
rio_writen()
【思路】多次调用 Unix I/O 的 write()
,直到请求的字节数都被写出。
不会出现 short count。
ssize_t rio_writen(int fd, void *user_buf, size_t n_request) {
size_t n_left = n_request;
ssize_t n_written;
char *pos = user_buf;
while (n_left > 0) {
if ((n_written = write(fd, pos, n_left)) <= 0) {
if (errno == EINTR) /* Interrupted by sig handler return */
n_written = 0; /* and call `write()` again */
else
return -1; /* `errno` set by `write()` */
}
n_left -= n_written; pos += n_written;
}
return n_request; /* never returns a short count */
}
【需求】线程安全;支持从同一文件交替读取文本行与二进制数据。
#include "csapp.h"
/* 初始化 internal buffer */
void rio_readinitb(rio_t *rp, int fd); /* once per fd */
ssize_t rio_readlineb(rio_t *rp, void *user_buf, size_t n_request);
/* read n_request bytes, unless meet EOF or '\n' */
ssize_t rio_readnb (rio_t *rp, void *user_buf, size_t n_request);
/* read n_request bytes, unless meet EOF */
/* Both returns: number of bytes read if OK,
0 on EOF,
−1 on error */
⚠️ 后缀 b
表示带缓冲的,不要与无缓冲的 rio_readn()
混用。但对同一 rio_t
可以交替调用 rio_readlineb()
与 rio_readnb()
。
rio_readinitb()
初始化 internal buffer(每打开一个 fd
,需初始化一个 rio_t
对象):
#define RIO_BUFSIZE 8192
typedef struct {
int rio_fd; /* Descriptor for this internal buf */
int rio_cnt; /* Unread bytes in this internal buf */
char *rio_bufpos; /* Next unread byte in this internal buf */
char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;
void rio_readinitb(rio_t *rp, int fd) {
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufpos = rp->rio_buf;
}
rio_read()
与 Unix I/O 的 read()
接口相同,但先(尽量多地)读入缓冲区,再复制(指定片段)给用户。
💡 用 rio_read()
可以减少直接调用 read()
的次数,后者在用户态(而非内核态)程序中调用开销巨大。
static ssize_t rio_read(rio_t *rp, char *user_buf, size_t n) {
int cnt;
while (rp->rio_cnt <= 0) { /* Refill if buf is empty */
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, RIO_BUFSIZE);
if (rp->rio_cnt < 0) {
if (errno != EINTR/* Interrupted by sig handler return */)
return -1;
}
else if (rp->rio_cnt == 0) /* EOF */
return 0;
else
rp->rio_bufpos = rp->rio_buf; /* Reset buffer ptr */
}
/* Copy bytes from `rp->rio_bufpos` to `user_buf` */
cnt = min(n, rp->rio_cnt);
memcpy(user_buf, rp->rio_bufpos, cnt);
rp->rio_bufpos += cnt; rp->rio_cnt -= cnt;
return cnt;
}
rio_readlineb()
读取一行字符,满足以下条件之一时停止:
n_request
字节\n
ssize_t rio_readlineb(rio_t *rp, void *user_buf, size_t n_request) {
int n/* 当前字符串长度 */, rc/* 单次读取字节数 */;
char c, *pos = user_buf;
for (n = 1/* 字符串总是以 '\0' 结尾,故长度至少为 1 */; n < n_request; n++) {
if ((rc = rio_read(rp, &c, 1)) == 1) {
*(pos++) = c;
if (c == '\n') {
n++;
break;
}
}
else if (rc == 0) {
if (n == 1)
return 0; /* EOF, no data read */
else
break; /* EOF, some data was read */
}
else
return -1; /* Error */
}
*pos = 0; /* end of string */
return n - 1; /* 不计 '\0' */
}
rio_readnb()
读取若干字节,满足以下条件之一时停止:
n_request
字节ssize_t rio_readnb(rio_t *rp, void *user_buf, size_t n_request) {
size_t n_left = n_request;
ssize_t n_read;
char *pos = user_buf;
while (n_left > 0) {
if ((n_read = rio_read(rp, pos, n_left)) < 0)
return -1;
else if (n_read == 0)
break; /* EOF */
n_left -= n_read; pos += n_read;
}
return (n_request - n_left); /* within [0, n_request] */
}
stat()
#include <unistd.h>
#include <sys/stat.h>
int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
struct stat
/* Metadata returned by the stat and fstat functions */
/* included by sys/stat.h */
struct stat {
dev_t st_dev; /* Device */
ino_t st_ino; /* inode */
mode_t st_mode; /* Protection and file type */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device type (if inode device) */
off_t st_size; /* Total size, in bytes */
unsigned long st_blksize; /* Block size for filesystem I/O */
unsigned long st_blocks; /* Number of blocks allocated */
time_t st_atime; /* Time of last access */
time_t st_mtime; /* Time of last modification */
time_t st_ctime; /* Time of last change */
};
statcheck.c
#include "csapp.h"
int main (int argc, char **argv) {
struct stat stat;
char *type, *readok;
if (argc != 2) {
fprintf(stderr, "usage: %s <filename>\n", argv[0]);
exit(0);
}
Stat(argv[1], &stat);
if (S_ISREG(stat.st_mode)) /* Determine file type */
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "directory";
else
type = "other";
if ((stat.st_mode & S_IRUSR)) /* Check read access */
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\n", type, readok);
exit(0);
}
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
/* Returns: pointer to handle if OK,
NULL on error */
int closedir(DIR *dirp);
/* Returns: 0 on success,
−1 on error */
readdir()
#include <dirent.h>
struct dirent {
ino_t d_ino; /* inode number */
char d_name[256]; /* Filename */
};
struct dirent *readdir(DIR *dirp);
/* Returns: pointer to next directory entry if OK,
NULL if no more entries or error */
⚠️ 只能通过检查 errno
是否被修改,来判断是出错,还是到达列表末尾。
readdir.c
#include "csapp.h"
int main(int argc, char **argv) {
DIR *streamp;
struct dirent *dep;
if (argc != 2) {
printf("usage: %s <pathname>\n", argv[0]);
exit(1);
}
streamp = Opendir(argv[1]);
errno = 0;
while ((dep = readdir(streamp)) != NULL) {
printf("Found file: %s\n", dep->d_name);
}
if (errno != 0)
unix_error("readdir error");
Closedir(streamp);
exit(0);
}
fork()
再探子进程继承其 parent’s open file table,表中每一项的引用计数加一:
#include <unistd.h>
int dup2(int oldfd, int newfd/* close if already open */);
/* Returns: nonnegative descriptor if OK,
−1 on error */
dup2(4, 1)
duplicate fd[4]
to fd[1]
,结果如下:
C 标准库提供,将 file 及其对应的 buffer 抽象为 FILE stream。
#include <stdio.h>
extern FILE *stdin; /* Standard input (descriptor 0) */
extern FILE *stdout; /* Standard output (descriptor 1) */
extern FILE *stderr; /* Standard error (descriptor 2) */
FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);
char *fgets( char *dst, int count, FILE *stream);
int fputs(const char *src, FILE *stream);
size_t fread ( void *dst, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *src, size_t size, size_t count, FILE *stream );
int scanf( const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(char *buffer, const char *format, ...);
int printf( const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *buffer, const char *format, ...);
int fflush(FILE *output); /* undefined behavior for input */
I/O 库 | 适用场景 | 缺点 |
---|---|---|
Unix I/O | 读取文件元数据、信号处置器内部 | 难以处理 short count、系统调用开销大 |
Standard I/O | 终端、硬盘文件 | 无法获取元数据、非线程安全、不能读写网络套接字 |
Robust I/O | 网络套接字 | 不支持格式化读写(需借助 Standard I/O 中的 sscanf() 及 sprintf() 完成) |
⚠️ 不要用 fgets()
、scanf()
或 rio_readlineb()
等读二进制文件。