深入理解计算机系统 --- 系统级I/O

目录

1.打开和关闭文件

2.读和写文件

3.元数据

4. I/O重定向

 5.标准I/O


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 函数,比如

        打开和关闭文件: fopenfclose

        读取和写入字节: freadfwrite

        读取和写入行:     fgetsfputs

        格式化读取和写入: fscanffprintf

标准 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 系统调用的数量尽可能小。

程序经常会一次读入或者写入一个字符,比如 getcputcungetc,同时也会一次读入或者写入一行,比如 getsfgets。如果用 Unix I/O 的方式来进行调用,是非常昂贵的,比如read 和 write 因为需要内核调用,需要大于 10000 个时钟周期。

解决的办法就是利用 read 函数一次读取一块数据,然后再由高层的接口,一次从缓冲区读取一个字符(当缓冲区用完的时候需要重新填充)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值