C++/IO

C/C++程序默认打开标准输入、输出和错误流,它们分别对应键盘、显示器。文件在操作系统中被视为设备,可以通过系统调用接口进行读写。文章介绍了fopen、fwrite、fprintf等文件操作函数,并讨论了文件描述符、打开模式(如w和a的区别)以及重定向。同时,文章提到了Linux的文件系统抽象——虚拟文件系统(VFS),强调了Linux一切皆文件的设计哲学。
摘要由CSDN通过智能技术生成

C/C++程序默认会打开三个文件流

标准输入 --->>>>键盘

标准错误 ------->>>显示器

标准输出 ------->>>显示器

先窥见一下!!!

linux下一切皆文件,那么键盘,显示器可以被看做文件吗??

当然可以!!

我从来没有打开过键盘和显示器啊但是依旧能够进行scanf, fgets,printf, cout ...。这是为啥呢????

因为我语言层面就已经默认打开了三个标准输出流,所以我们不用打开,我们就能直接操作!!

为什么要学习系统调用接口

其实我们应该明确的一点是:文件在磁盘和硬盘上放着,我们通过书写代码访问文件。----->>先编译形成二进制可执行程序(.exe)然后运行访问文件:本质是进程在访问文件!!!

要像硬件写入,只有操作系统有权利写入!!!

如若普通用户也想写入内容???我们可以通过OS提供接口(文件类的系统调用接口)来实现的!!!

其次文件类的系统调用接口比较难,语言上对这些接口做一下封装,可以让接口更好的使用。导致了不同的语言,有不同的语言级别的文件访问接口(都不一样),但是,都封装的是系统接口。

那为什么要学习OS层面的文件接口?

这样的接口,只有一套!!0S只有一个

如果语言不提供对文件的系统接口的封装,所有的访问文件的操作,都必须直接使用OS的接口。而用语言的客户,要想访问文件时,一旦使用系统接口,编写所谓的文件代码,无法在其他平台中直接运行了不具备跨平台性!

系统比如是Liunx,Windows.............

跨平台:把所有的平台的代码,都实现一遍。条件编译,动态裁剪

显示器是硬件吗? printf向显示器打印,为什么不觉得奇怪呢?因为它是直接显示出来的,没有延迟。

和磁盘写入到文件,没有本质区别!


文件而言:

曾经理解的文件:read、write ------->output

显示器: printf/cout->一种write ------->output

键盘:scanf/cin..->一种read ------->input

站在写的程序的角度--->>>站在内存的角度--->加载到内存

input output

普通文件->fopen/fread -〉你的进程的内部(内存)->fwrite -〉文件中

什么叫做文件呢?

站在系统的角度,能够被input读取,或者能够output写出的设备就叫做文件!

狭义文件:普通的磁盘文件

广义上的文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有的外设,都可以称之为文件

当前路径:进程有一个属性,会保存自己的工作路径。cwd,这个就是我们通常的当前路径。(具体可以跳转到进程那块)

IO类函数

这是C语言的文件函数fopen的打开方式

我们先来学习一下fopen函数

fopen

NAME fopen, fdopen, freopen - stream open functions SYNOPSIS #include <stdio.h> FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode);

第一个参数是文件路径,第二个是打开方式

fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); RETURN VALUE On success, fread() and fwrite() return the number of items read or written. This number equals the number of bytes transferred only when size is 1. If an error occurs, or the end of the file is reached, the return value is a short item count (or zero).

第一个是输入的字符串的其实地址,第二个是输入单位的大小,第三个是输入单位的个数,

fprintf

SYNOPSIS #include <stdio.h> int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...);

fputs

int fputs(const char *s, FILE *stream);

练习运用

#include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 8 int main() 9 { 10 FILE* fp = fopen("log.txt","w"); 11 if(fp == NULL) 12 { 13 perror("fopen"); 14 return 1; 15 } 16 17 18 const char *s1 = "hello hang\n"; 19 fwrite(s1, strlen(s1), 1, fp); 20 21 const char *s2 = "hello fprintf\n"; 22 fprintf(fp, "%s", s2); 23 24 const char *s3 = "hello fputs\n"; 25 fputs(s3, fp); 26 27 fclose(fp); 28 return 0; }

值得注意的是:

const char *s1 = "hello 105\n"; //不要+1, \0结尾是C语言的规定,文件用遵守吗?文件要保存的是有效数据! fwrite(s1, strlen(s1), 1, fp);

在这上面的代码中,我们发现 w 这个模式,是会先清空再写入。清空是在打开这个文件的时候就完成的。

我们先使用重定向的方式往文本里写入东西,然后再用重定向的方式清空文件

[hang@VM-4-12-centos day9]$ cat log.txt hello hang hello fprintf hello fputs [hang@VM-4-12-centos day9]$ echo aa>log.txt [hang@VM-4-12-centos day9]$ cat log.txt aa [hang@VM-4-12-centos day9]$ >log.txt [hang@VM-4-12-centos day9]$ cat log.txt [hang@VM-4-12-centos day9]$

fgets

char *fgets(char *s, int size, FILE *stream);

s为缓存区,size为缓存区的大小

#include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 8 9 int main() 10 { 11 12 FILE* fp = fopen("log.txt","r"); 13 14 if(fp == NULL) 15 { 16 perror("fopen"); 17 } 18 char line[64]; 19 //mems 20 //memset(line,size) 21 // fgets -> C -> s(string) -> 会自动在字符结尾添加\0 22 while(fgets(line, sizeof(line), fp) != NULL) 23 { 24 //printf("%s", line); 25 fprintf(stdout, "%s", line); //fprintf->stdout? 26 27 } 29 fclose(fp); 30 return 0; 31 }

[hang@VM-4-12-centos day9]$ ls log.txt Makefile myfile myfile.c [hang@VM-4-12-centos day9]$ cat log.txt log.txt Makefile myfile myfile.c [hang@VM-4-12-centos day9]$ ./myfile log.txt Makefile myfile myfile.c [hang@VM-4-12-centos day9]$

模拟cat

#include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 8 9 int main(int argc,char* argv[]) 10 { 11 if(argc != 2) 12 { 13 printf("argv error\n"); 14 return 1; 15 } 16 17 FILE* fp = fopen(argv[1],"r"); 18 19 if(fp == NULL) 20 { 21 perror("fopen"); 22 return 2; 23 } 24 25 char line[64]; 29 while(fgets(line, sizeof(line), fp) != NULL) 30 { 31 //printf("%s", line); 32 fprintf(stdout, "%s", line); //fprintf->stdout? 33 34 } 35 36 fclose(fp); 37 return 0; 38 }

[hang@VM-4-12-centos day9]$ ./myfile log.txt log.txt Makefile myfile myfile.c [hang@VM-4-12-centos day9]$ ./myfile Makefile myfile:myfile.c gcc -o $@ $^ .PHONY:clean clean: rm -f myfile [hang@VM-4-12-centos day9]$

fopen以w方式打开文件」默认先清空文件(注意:在fwrite之前>fopen

以a方式打开文件,追加,不断的向文件中新增内容


系统调用

我们在应用层看到一个很简单的动作,在系统接口层面甚至OS层面,可能要做非常多的动作!

标识位

函数标识位,利用了位图。

#define ONE 0x1 //0000 0001 #define TWO 0x2 //0000 0010 #define THREE 0x4 //0000 0100 void show(int flags) //0000 0101 { if(flags & ONE) printf("hello one\n"); // 0000 0011 & 0000 0001 if(flags & TWO) printf("hello two\n"); if(flags & THREE) printf("hello three\n"); } int main() { show(ONE); printf("-----------------------------------------\n"); show(TWO); printf("-----------------------------------------\n"); show(ONE | TWO); //000 0001 | 0000 0010 = 0000 0011 printf("-----------------------------------------\n"); show(ONE | TWO | THREE); printf("-----------------------------------------\n"); show(ONE | THREE); printf("-----------------------------------------\n"); return 0; }

open

NAME open, creat - open and possibly create a file or device SYNOPSIS #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int creat(const char *pathname, mode_t mode);

flags的选项

O_APPEND The file is opened in append mode. Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). O_APPEND may lead to corrupted files on NFS file systems if more than one process appends data to a file at once. This is because NFS does not support appending to a file, so the client kernel has to simulate it, which can't be done without a race condition.

O_CREAT If the file does not exist it will be created. The owner (user ID) of the file is set to the effective user ID of the process. The group ownership (group ID) is set either to the effective group ID of the process or to the group ID of the parent directory (depending on file system type and mount options, and the mode of the parent directory, see the mount options bsdgroups and sysvgroups described in mount(8)).

O_TRUNC If the file already exists and is a regular file and the open mode allows writing (i.e., is O_RDWR or O_WRONLY) it will be truncated to length 0. If the file is a FIFO or terminal device file, the O_TRUNC flag is ignored. Oth‐ erwise the effect of O_TRUNC is unspecified.

The argument flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only, or read/write, respectively.

我们发现有两个函数,他们的差别是

三个参数的是 没有文件的时候创建并且打开文件的。多的那个参数是权限。

两个参数的是 有这个文件,然后读取文件的。

如果我们以这样的方式创建文件,文件的权限相较于我们用shell创建的文件是不对的。

#include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 8 int main() 9 { 10 11 int fd = open("log.txt", O_WRONLY|O_CREAT); //rw-rw-rw- 12 if(fd < 0) 13 { 14 perror("open"); 15 return 1; 16 17 } 18 19 20 printf("open success,fd:%d\n",fd); 21 close(fd); 22 23 return 0

运行结果

[hang@VM-4-12-centos day9]$ ls -al total 20 drwxrwxr-x 2 hang hang 4096 Feb 6 23:17 . drwxrwxr-x 11 hang hang 4096 Feb 6 15:21 .. -rw-rw-r-- 1 hang hang 33 Feb 6 22:33 log.txt -rw-rw-r-- 1 hang hang 65 Feb 6 22:39 Makefile -rw-rw-r-- 1 hang hang 1874 Feb 6 23:17 myfile.c [hang@VM-4-12-centos day9]$ touch tmp.txt [hang@VM-4-12-centos day9]$ make gcc -o myfile myfile.c [hang@VM-4-12-centos day9]$ ./myfile open success,fd:3 [hang@VM-4-12-centos day9]$ ls -l total 24 -rw-rw-r-- 1 hang hang 33 Feb 6 22:33 log.txt -rw-rw-r-- 1 hang hang 65 Feb 6 22:39 Makefile -rwxrwxr-x 1 hang hang 8512 Feb 6 23:18 myfile -rw-rw-r-- 1 hang hang 1874 Feb 6 23:17 myfile.c -rw-rw-r-- 1 hang hang 0 Feb 6 23:18 tmp.txt [hang@VM-4-12-centos day9]$

正确的使用方法

[hang@VM-4-12-centos day9]$ ./myfile open success,fd:3 [hang@VM-4-12-centos day9]$ ls -l total 24 -rw-rw-r-- 1 hang hang 33 Feb 6 22:33 log.txt -rw-rw-r-- 1 hang hang 65 Feb 6 22:39 Makefile -rwxrwxr-x 1 hang hang 8512 Feb 6 23:31 myfile -rw-rw-r-- 1 hang hang 1879 Feb 6 23:31 myfile.c -rw-rw-r-- 1 hang hang 0 Feb 6 23:18 tmp.txt [hang@VM-4-12-centos day9]$

运行结果

[hang@VM-4-12-centos day9]$ ./myfile open success,fd:3 [hang@VM-4-12-centos day9]$ ls -l total 24 -rw-rw-r-- 1 hang hang 33 Feb 6 22:33 log.txt -rw-rw-r-- 1 hang hang 65 Feb 6 22:39 Makefile -rwxrwxr-x 1 hang hang 8512 Feb 6 23:31 myfile -rw-rw-r-- 1 hang hang 1879 Feb 6 23:31 myfile.c -rw-rw-r-- 1 hang hang 0 Feb 6 23:18 tmp.txt [hang@VM-4-12-centos day9]$

可是我们发现我们给的权限码是0666,理想情况是rw-rw-rw-,但实际情况是

-rw-rw-r--,这是因为umask

[hang@VM-4-12-centos day9]$ umask 0002

当然如果我们想要修改的话,可以调用这个函数

NAME umask - set file mode creation mask SYNOPSIS #include <sys/types.h> #include <sys/stat.h> mode_t umask(mode_t mask);

使用方式

umask(0);

系统会优先使用这个0,而不是系统中的那个2.

write

WRITE(2) Linux Programmer's Manual WRITE(2) NAME write - write to a file descriptor SYNOPSIS #include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);

返回值就是实际读到的符号数量。

1 #include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 8 int main() 9 { 10 11 int fd = open("log.txt", O_WRONLY|O_CREAT,0666); //rw-rw-rw- 12 if(fd < 0) 13 { 14 perror("open"); 15 return 1; 16 17 } 18 19 20 printf("open success,fd:%d\n",fd); 21 W> 22 char *s = "hello 1\n"; 23 write(fd, s, strlen(s)); 24 25 close(fd); 26 return 0; 27 }

对应关系

int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666); fopen("log.txt", "w");

这两个其实再某种程度上是等价的。

fopen("log.txt", "a"); int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);

read

NAME read - read from a file descriptor SYNOPSIS #include <unistd.h> ssize_t read(int fd, void *buf, size_t count);

buf为我们自己提供的缓冲区,_count为缓冲区的大小。

#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd = open("log.txt", O_RDONLY); if(fd < 0) { perror("open"); return 1; } char buffer[64]; memset(buffer, '\0', sizeof(buffer)); read(fd, buffer, sizeof(buffer)); printf("%s", buffer); return 0; }


文件描述符fd

这个struct结构体内部一般会有多种成员。

C文件库函数内部一定要调用系统调用!!!在系统角度只认fd!不认你这个FILE!!!所以FILE结构体里面,必定封装了fd! !

证明stdin,stdout,stderr

fprintf(stdout, "hello stdout\n"); const char *s = "hello 1\n"; write(1, s, strlen(s));

ssize_t s = read(0, input, sizeof(input)); if(s > 0) { input[s] = '\0'; printf("%s\n", input); } int a = 10; scanf fscanf(stdin, "%d", &a); printf("%d\n",a );

//stdin, stdout, stderr -> FILE * -> 内部有没有fd呢??-> 绝对有! -> 怎么证明? printf("stdin: %d\n", stdin->_fileno); printf("stdout: %d\n", stdout->_fileno); printf("stderr: %d\n", stderr->_fileno);

所以,fd是什么呢?

进程要访问文件,必须先打开文件!

一个进程可以打开多个文件吗?

一般而言进程:打开的文件= 1: n的

文件要被访问,前提是加载到内存中,才能被直接访间!

进程︰打开的文件= 1: n的

如果是多个进程都打开自己的文件呢?

系统中会存在大量的被打开的文件!所以,OS要不要把如此之多的文件也管理起来呢?﹖先描述,在组织!!

在内核中,如何看待打开的文件?OS内部要为了管理每一个被打开的文件,

struct file { struct file *next;struct file *prev; //包含了一个被打开的文件的几乎所有的内容(不仅仅包含属性) }

创建struct file的对象,充当一个被打开的文件。如果有很多呢﹖再用双链表组织起来!

task_struct 与 fd的关系(两张图)

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来 描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进 程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数 组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件 描述符,就可以找到对应的文件

文件描述符的分配规则

在files_struct数组当中,找到当前没有被使用的 最小的一个下标,作为新的文件描述符。

输出重定向(利用文件描述符)

close(1); int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); if(fd < 0) { perror("open"); return 1; } printf("fd: %d\n", fd); // stdout->FILE{fileno=1}->log.txt printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); fprintf(stdout, "hello fprintf\n"); const char *s = "hello fwrite\n"; fwrite(s, strlen(s), 1, stdout); fflush(stdout); close(fd); return 0;

运行结果

[hang@VM-4-12-centos day10]$ ./myfile [hang@VM-4-12-centos day10]$ cat log.txt fd: 1 fd: 1 fd: 1 fd: 1 fd: 1 fd: 1 hello fprintf hello fwrite

输出重定向(dup2)

DUP(2) Linux Programmer's Manual DUP(2) NAME dup, dup2, dup3 - duplicate a file descriptor SYNOPSIS #include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd); dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following: * If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed. * If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

if(argc != 2){ return 2; } int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); //int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND); if(fd < 0) { perror("open"); return 1; } dup2(fd, 1); fprintf(stdout, "%s\n", argv[1]); //stdout->1->显示器 close(fd);

输入重定向

close(1); int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT); int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT); if(fd<0) { perror("open"); return 1; } fprintf(stdout, "you can see me , success\n"); printf("fd: %d\n", fd); char buffer[64]; fgets(buffer, sizeof buffer, stdin); printf("%s\n", buffer);

Linux下一切皆文件

这是Linux设计哲学------->>>>体现在操作系统的软件设计层面的!

Linux C语言写的!如何用语言实现面向对象|甚至是运行时多态?通过类来实现。

类包括:1.成员属性2.成员方法(C语言的话,通过函数指针来是实现)

没有任何硬件的差别了看待所有的文件的方式,都统一了

VFS virtual file system 虚拟机文件系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值