目录
1.打开和关闭文件
进程是通过调用 open 函数来打开一个已经存在的文件 或 创建一个新的文件:
//若成功则返回新文件描述符,失败返回-1
int open(const char *pathname, int flags);
pathname 是文件名,函数会将 pathname 转换为一个文件描述符,并且返回描述符数字;
flags 指明进程如何访问这个文件:
O_RDONLY: 只读
O_WRONLY: 只写
O_RDWR: 可读可写
如:
fd = open("foo.txt",O_RDONLY)
flags 也可以是一个或多个掩码的或:
O_CREAT : 如果文件不存在,就创建一个文件
O_TRUNC : 如果这个文件中本来是有内容的,则原来的内容会被丢弃
O_APPEND :如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面
如:
fd = open("foo.txt",O_WRONLY | O_APPEND)
最后通过 close 函数关闭一个打开的文件:
//成功返回 0 ,失败返回 -1
int close(int fd);
程序举例如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fd;
if(fd = open("foo.txt",O_RDWR) < 0)
{
printf("open error\n");
exit(1);
}
printf("open sucess fd = %d\n",fd);
if(close(fd) < 0)
{
printf("close error\n");
exit(1);
}
printf("close sucess\n");
exit(0);
}
打开同目录下的 foo.txt 文件,输出:
open sucess fd = 0
close sucess
2.读和写文件
read 函数:从描述符为 fd 的当前文件位置,复制最多 n 个字节,到内存位置 buf;
返回值: -1 表示读错误;0 表示EOF(实际文件字节数量小于 n );
否则,返回值表示的是实际传送的字节数量
ssize_t read(int fd, void *buf, size_t count);
write 函数:从内存位置buf,复制最多 n 个字节,到描述符 fd 的当前文件位置;
ssize_t write(int fd, const void *buf, size_t count);
下面程序展示了一个程序使用 read 和 write 调用一次一个字节地从标准输入复制到标准输出:
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
char c;
while(read(STDIN_FILENO, &c, 1) != 0) //STDIN_FILENO:接收键盘的输入
write(STDOUT_FILENO, &c, 1); //STDOUT_FILENO:向屏幕输出
exit(0);
}
3.元数据
调用 stat 和 fstat 函数,检索到关于文件的信息(元数据)
int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
stat 函数以一个文件名作为输入,并填写如下所示的 stat 数据结构中的各个成员,这里我们只讨论st_mode 和 st_size
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
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 ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
};
st_size 成员包含了文件的字节数大小;
st_mode 成员则编码了文件访问许可位和文件类型
在Linux中的 sys/stat.h 中定义了宏谓词来确定 st_mode 成员的文件类型:
S_ISREG(m) 这是一个普通文件吗?
S_ISDIR(m) 这是一个目录文件吗?
S_ISSCOK(m) 这是一个网络嵌套字吗?
下面程序使用这些宏和 stat 函数来读取和解释一个文件的 st_mode 位:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
int main (int argc, char **argv)
{
struct stat Stat;
char *type, *readok;
stat(argv[1], &Stat);
if (S_ISREG(Stat.st_mode)) // 确定文件类型
type = "regular";
else if (S_ISDIR(Stat.st_mode))
type = "directory";
else
type = "other";
if ((Stat.st_mode & S_IRUSR)) // 检查读权限
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\n", type, readok);
exit(0);
}
输出
bash> ./a.out foo.txt
type: regular, read: yes
4. I/O重定向
int dup2(int oldfd, int newfd);
dup2 函数复制描述符表表项 oldfd 到描述符表表项 newfd,覆盖描述符表表项 newfd以前的内容;
假设调用 dup(4,1) 之前,我们的状态是下图:
之后会变成:
我们只要改变文件描述符指向的文件,也就完成了重定向的过程,下图中我们把原来指向终端的文件描述符指向了磁盘文件,也就把终端上的输出保存在了文件中;
5.标准I/O
C 标准库中包含一系列高层的标准 IO 函数,比如
打开和关闭文件: fopen
, fclose
读取和写入字节: fread
, fwrite
读取和写入行: fgets
, fputs
格式化读取和写入: fscanf
, fprintf
标准 IO 会用流的形式打开文件,所谓流(stream)实际上是文件描述符和缓冲区(buffer)在内存中的抽象。C 程序一般以三个流开始,如下所示:
#include <stdio.h>
extern FILE *stdin; // 标准输入 descriptor 0
extern FILE *stdout; // 标准输出 descriptor 1
extern FILE *stderr; // 标准错误 descriptor 2
int main()
{
fprintf(stdout, "Hello, Da Wang\n");
}
缓冲区目的:使开销较高的 Linux I/O 系统调用的数量尽可能小。
程序经常会一次读入或者写入一个字符,比如 getc
, putc
, ungetc
,同时也会一次读入或者写入一行,比如 gets
, fgets
。如果用 Unix I/O 的方式来进行调用,是非常昂贵的,比如read
和 write
因为需要内核调用,需要大于 10000 个时钟周期。
解决的办法就是利用 read
函数一次读取一块数据,然后再由高层的接口,一次从缓冲区读取一个字符(当缓冲区用完的时候需要重新填充)