输入/输出(I/O)是在主存和外部设备(例如磁盘驱动器、终端和网络)之间赋值数据的过程。
一个Linux文件就是一个m个字节的序列, 所有的I/O设备都被模型化为文件 ,而所有的输入和输出都被当作对相应文件的读和写来执行。Unix I/O使得所有的输入输出都以一种统一且一致的方式来执行:
Linux文件都有一个类型:
\n
结尾,其数值为0x0a
。 mkdir
创建一个目录,ls
查看目录内容,rmdir
删除目录。 进程通过调用open
函数打开一个已存在的文件或者创建一个新文件。
open
函数将filename转换为文件描述符,返回的 描述符总是进程中当前没有打开的最小描述符 。
flags参数指明进程如何访问文件:
mode参数指定新文件的访问权限位。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> // 成功时返回文件描述符,出错返回-1 int open(char *filename, int flags, mode_t mode);
进程通过调用close
函数关闭一个打开的文件。
关闭一个已关闭的描述符会出错。
#include <unistd.h> // 成功返回0,出错返回-1 // fd参数表示文件描述符 int close(int fd);
应用程序通过调用read
和write
函数执行输入输出。
read
函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf。
write
函数从内存位置buf复制至多n个字节到描述符fd的当前文件位置。
#include <unistd.h> // 成功时返回读的字节数,若EOF则为0,出错返回-1 ssize_t read(int fd, void *buf, size_t n); // 成功时返回写的字节数,出错返回-1 ssize_t write(int fd, const void *buf, size_t n);
x84-64系统中,size_t
被定义为unsigned long
,而ssize_t
被定义为long
。
RIO(Robust I/O)包:自动处理读写过程中的信号中断,反复调用read
和write
处理不足值,直到传送完所有请求数据。
程序通过调用stat
和fstat
函数,检索到关于文件的信息/元数据(metadata)。
stat
函数以文件名作为输入,并将相关文件元数据写入stat
结构体中。
fstat
函数与stat
函数功能类似,只是其以文件描述符作为参数。
stat
结构体中有两个常用的成员:
st_mode
:编码了文件访问许可位和文件类型 st_size
:文件的字节数大小 #include <unistd.h> #include <sys/stat.h> // 成功时返回0,出错返回-1 int stat(const char *filename, struct stat *buf); int fstat(int fd, struct stat *buf);
应用程序通过readdir
系列函数读取目录内容。
目录流是对条目有序列表的抽象,每次调用readdir
返回指向流中下一个目录项的指针,没有更多目录项或出错时返回NULL。每个目录项的结构为如下,d_ino
是文件位置,d_name
是文件名。
struct dirent { ino_t d_ino; // inode number char d_name[256]; // filename };
检查readdir
是否错误的方法是检查调用readdir
以来errno是否被修改过。
#include <sys/types.h> #include <dirent.h> // 成功时返回指向目录流的指针,出错返回NULL DIR *opendir(const char *name); // 成功时返回指向下一个目录项的指针,出错则返回NULL struct dirent *readdir(DIR *dirp);
函数closedir
关闭流并释放其所有资源。
#include <dirent.h> // 成功时返回0,错误返回-1 int closedir(DIR *dirp);
内核用三个相关的数据结构表示打开的文件:
stat
结构中的大多数信息,包括st_mode和st_size等。 父进程调用fork()
时,子进程有一个父进程描述符表的副本, 父子进程共享相同的打开文件表集合 ,因此共享相同的文件位置。在内核中删除相应文件表表项之前,父子进程必须都关闭了它们的描述符。
I/O重定向操作符允许用户将磁盘文件和标准输入和输出系统联系起来。
ls > foo.txt
表示把执行ls得到的标准输出重定向到磁盘文件foo.txt。
使用dup2
函数可以进行I/O重定向,dup2
函数赋值描述符表表项oldfd到描述符表表项newfd,覆盖描述符表表项newfd以前的内容。
dup2(4,1)
可以使得任何写到标准输出的数据都被重定向到文件描述符为4的文件。
#include <unistd.h> // 成功时返回非负的描述符,出错返回-1 int dup2(int oldfd, int new fd);
使用带缓冲区的读时,只要缓冲区中还有未读的字节,那么接下来再进行读时可以直接从流缓冲区中得到服务。
#define RIO_BUFSIZE 8192 typedef struct { int rio_fd; /* Descriptor for this internal buf */ int rio_cnt; /* Unread bytes in internal buf */ char *rio_bufptr; /* Next unread byte in internal buf */ char rio_buf[RIO_BUFSIZE]; /* Internal buffer */ }rio_t; // 从缓冲区复制n和rp->rio_cnt中较小值个字节到用户缓冲区,返回复制的字节数 static ssize_t rio_read(rio_t *rp, char *usrbuf, 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, sizeof(rp->rio_buf)); 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_bufptr = rp->rio_buf; /* Reset buffer ptr */ } /* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */ cnt = n; if(rp->rio_cnt < n) cnt = rp->rio_cnt; memcpy(usrbuf, rp->rio_bufptr, cnt); rp->rio_bufptr += cnt; rp->rio_cnt -= cnt; return cnt; }
对于大多数应用程序而言,标准I/O更简单,是优于Unix I/O的选择,但是Unix I/O比标准I/O更适用于网络应用程序:
fopen
,fclose
,fread
,fwrite
,fgets
,fputs
,scanf
,printf
。 scanf
或rio_readlineb
读二进制文件,这些函数是用来读取文本文件的,二进制文件可能散布很多0xa字节,而这些字节又与终止文本行无关。 a
--
123456789
更改id为3
--
test
更改id为2
--
commentor
伪造名称???
--
hhh
伪造名称???
--
yayay