Linux 基础IO

作者 : admin 本文共5147个字,预计阅读时间需要13分钟 发布时间: 2024-06-16 共1人阅读

1.在系统角度理解文件

文件=内容+属性

1.1文件的所有操作:1.对内容操作  2.对属性操作;

C/C++程序,会默认打开三个文件流:标准输入、标准输出、标准错误;

Linux下一切皆文件

键盘、显示器可以被看作文件,但是我从来没有打开过键盘和显示器,依旧能够进行scanf、fgets、printf、cout………(映照了C/C++程序,会默认打开三个文件流:标准输入、标准输出、标准错误;)

标准输入(stdin  默认设备键盘)、标准输出(stdout、默认设备显示器)、标准错误(stderr、默认设备显示器);

2. 文件在磁盘(硬件)上放着,我们访问文件,先写代码->编译->exe->运行->访问文件:本质是谁在访问文件呢    答:进程在访问文件(进程是需要接口的)

2.1需要向硬件写入,只有操作系统有权利;普通用户也想写入,就必须让OS提供文件类的系统接口(系统接口比较难,所以语言上对这些接口做了封装,为了让接口更好的使用;导致了不同的语言,有了不同语言级别的文件访问接口(都不一样)但是,封装的系统接口是一样的)

2.2跨平台问题:基本上所有的语言都是跨平台的;如果语言不提供对文件的系统接口的封装,是不是所有的访问文件的操作,都必须直接使用OS的接口;答:是的

一旦使用系统接口,编写所谓的文件代码,就无法在其他平台上直接运行了,是不具备跨平台性的;语言级别上把所有平台的代码,都实现一遍,再条件编译,动态裁剪;来实现跨平台;

3.显示器是硬件吗?为什么往显示器上打印,不觉得奇怪?

其实,本质上也是一种写入,和往磁盘写入文件没有本质区别,只不过能直观看到罢了

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

5.复习C语言所学的文件操作

5.1当每一个进程运行起来的时候,每个进程都会记录自己所处的工作路径;

5.2当前路径是怎样形成的:进程会使用cwd(current work directory)在底层做一个拼接,拼接想要创建(或打开的)文件名,形成当前路径

#include
#include

int main(int argc,char* argv[])  //如果此时把这个文件改成mycat,就变成了一个命令行参数,打印其中的内容;
{
	if (argc != 2)
	{
		printf("argv error!
");
		return 1;
	}
	//FILE* fp = fopen("log.txt", "w"); //"w"  先把文件清空,再写入;
	第一个参数可以带路径,不带路径就是当前路径  //第二个参数为要进行的方式“r”"w" "a"等
	//if (fp == NULL)
	//{
	//	perror("fopen");//当返回失败时,打印函数的错误信息;
	//	return 2;
	//}
	进行操作;
	//const char* s1 = "hello fwrite
";//要不要把
#include
#include
int main(int argc,char* argv[])  //如果此时把这个文件改成mycat,就变成了一个命令行参数,打印其中的内容;
{
if (argc != 2)
{
printf("argv error!
");
return 1;
}
//FILE* fp = fopen("log.txt", "w"); //"w"  先把文件清空,再写入;
第一个参数可以带路径,不带路径就是当前路径  //第二个参数为要进行的方式“r”"w" "a"等
//if (fp == NULL)
//{
//	perror("fopen");//当返回失败时,打印函数的错误信息;
//	return 2;
//}
进行操作;
//const char* s1 = "hello fwrite
";//要不要把\0写入(要不要+1)  答:不加1,\0结尾是c语言的规定,文件是不需要遵守的
//								//文件要保存的是有效数据 /0只是标定结尾的标识符,不算有效数据;不是字符串有效的内容
//fwrite(s1,strlen(s1),1, fp);
//const char* s2 = "hello fprintf
";
//fprintf(fp,"%s", s2);
//const char* s3 = "hello fputs
";
//fputs(s3, fp);
//fclose(fp);
//FILE* fp = fopen("log.txt", "a"); //append  追加;不断往里面追加内容;
//if (fp == NULL)
//{
//	perror("fopen");
//	return 1;
//}
进行操作;
//const char* s1 = "hello fwrite
";
//fwrite(s1, strlen(s1), 1, fp);
//const char* s2 = "hello fprintf
";
//fprintf(fp, "%s", s2);
//const char* s3 = "hello fputs
";
//fputs(s3, fp);
//fclose(fp);
FILE* fp = fopen("argv[1]", "r"); //读
if (fp == NULL)
{
perror("fopen");
return 1;
}
//按行读取;
char line[64];
//fgets 是C语言提供的接口,s是string的缩写,会自动在字符结尾添加\0
while (fgets(line, sizeof line - 1, fp) != NULL)
{
//printf("%s
", line);
fprintf(stdout, "%s", line);  //stdout是啥;
}
fclose(fp);
return 0;
}
写入(要不要+1) 答:不加1,
#include
#include
int main(int argc,char* argv[])  //如果此时把这个文件改成mycat,就变成了一个命令行参数,打印其中的内容;
{
if (argc != 2)
{
printf("argv error!
");
return 1;
}
//FILE* fp = fopen("log.txt", "w"); //"w"  先把文件清空,再写入;
第一个参数可以带路径,不带路径就是当前路径  //第二个参数为要进行的方式“r”"w" "a"等
//if (fp == NULL)
//{
//	perror("fopen");//当返回失败时,打印函数的错误信息;
//	return 2;
//}
进行操作;
//const char* s1 = "hello fwrite
";//要不要把\0写入(要不要+1)  答:不加1,\0结尾是c语言的规定,文件是不需要遵守的
//								//文件要保存的是有效数据 /0只是标定结尾的标识符,不算有效数据;不是字符串有效的内容
//fwrite(s1,strlen(s1),1, fp);
//const char* s2 = "hello fprintf
";
//fprintf(fp,"%s", s2);
//const char* s3 = "hello fputs
";
//fputs(s3, fp);
//fclose(fp);
//FILE* fp = fopen("log.txt", "a"); //append  追加;不断往里面追加内容;
//if (fp == NULL)
//{
//	perror("fopen");
//	return 1;
//}
进行操作;
//const char* s1 = "hello fwrite
";
//fwrite(s1, strlen(s1), 1, fp);
//const char* s2 = "hello fprintf
";
//fprintf(fp, "%s", s2);
//const char* s3 = "hello fputs
";
//fputs(s3, fp);
//fclose(fp);
FILE* fp = fopen("argv[1]", "r"); //读
if (fp == NULL)
{
perror("fopen");
return 1;
}
//按行读取;
char line[64];
//fgets 是C语言提供的接口,s是string的缩写,会自动在字符结尾添加\0
while (fgets(line, sizeof line - 1, fp) != NULL)
{
//printf("%s
", line);
fprintf(stdout, "%s", line);  //stdout是啥;
}
fclose(fp);
return 0;
}
结尾是c语言的规定,文件是不需要遵守的 // //文件要保存的是有效数据 /0只是标定结尾的标识符,不算有效数据;不是字符串有效的内容 //fwrite(s1,strlen(s1),1, fp); //const char* s2 = "hello fprintf "; //fprintf(fp,"%s", s2); //const char* s3 = "hello fputs "; //fputs(s3, fp); //fclose(fp); //FILE* fp = fopen("log.txt", "a"); //append 追加;不断往里面追加内容; //if (fp == NULL) //{ // perror("fopen"); // return 1; //} 进行操作; //const char* s1 = "hello fwrite "; //fwrite(s1, strlen(s1), 1, fp); //const char* s2 = "hello fprintf "; //fprintf(fp, "%s", s2); //const char* s3 = "hello fputs "; //fputs(s3, fp); //fclose(fp); FILE* fp = fopen("argv[1]", "r"); //读 if (fp == NULL) { perror("fopen"); return 1; } //按行读取; char line[64]; //fgets 是C语言提供的接口,s是string的缩写,会自动在字符结尾添加
#include
#include
int main(int argc,char* argv[])  //如果此时把这个文件改成mycat,就变成了一个命令行参数,打印其中的内容;
{
if (argc != 2)
{
printf("argv error!
");
return 1;
}
//FILE* fp = fopen("log.txt", "w"); //"w"  先把文件清空,再写入;
第一个参数可以带路径,不带路径就是当前路径  //第二个参数为要进行的方式“r”"w" "a"等
//if (fp == NULL)
//{
//	perror("fopen");//当返回失败时,打印函数的错误信息;
//	return 2;
//}
进行操作;
//const char* s1 = "hello fwrite
";//要不要把\0写入(要不要+1)  答:不加1,\0结尾是c语言的规定,文件是不需要遵守的
//								//文件要保存的是有效数据 /0只是标定结尾的标识符,不算有效数据;不是字符串有效的内容
//fwrite(s1,strlen(s1),1, fp);
//const char* s2 = "hello fprintf
";
//fprintf(fp,"%s", s2);
//const char* s3 = "hello fputs
";
//fputs(s3, fp);
//fclose(fp);
//FILE* fp = fopen("log.txt", "a"); //append  追加;不断往里面追加内容;
//if (fp == NULL)
//{
//	perror("fopen");
//	return 1;
//}
进行操作;
//const char* s1 = "hello fwrite
";
//fwrite(s1, strlen(s1), 1, fp);
//const char* s2 = "hello fprintf
";
//fprintf(fp, "%s", s2);
//const char* s3 = "hello fputs
";
//fputs(s3, fp);
//fclose(fp);
FILE* fp = fopen("argv[1]", "r"); //读
if (fp == NULL)
{
perror("fopen");
return 1;
}
//按行读取;
char line[64];
//fgets 是C语言提供的接口,s是string的缩写,会自动在字符结尾添加\0
while (fgets(line, sizeof line - 1, fp) != NULL)
{
//printf("%s
", line);
fprintf(stdout, "%s", line);  //stdout是啥;
}
fclose(fp);
return 0;
}
while (fgets(line, sizeof line - 1, fp) != NULL) { //printf("%s ", line); fprintf(stdout, "%s", line); //stdout是啥; } fclose(fp); return 0; }

5.3复习C语言

fopen 以w方式打开文件,默认先清空文件(注意,在fwrite之前,就清空了)

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

演示了文件读和写的一般操作;

5.4 三个标准输入,输出流是什么? 答:标准输入对应的设备是键盘;标准输出对应的设备是显示器;标准错误对应的设备是显示器;

6.学习系统接口

6.1 open\close \read \write

6.2引子:

#include
//#include
#include

#define ONE 0x1   //0000 0001
#define TWO 0X2   //0000 0010
#define THREE 0x4 //0000 0100

void show(int flags)
{
	if(flags & ONE) printf("hello one
");

	if(flags & TWO) printf("hello two
");

	if(flags & THREE) printf("hello three
");	
}
使用类似位图的方式,来解决此问题
int main()
{
	show(ONE);
	show(TWO);
	show(ONE | TWO);
	show(ONE | TWO | THREE);
	
	return 0;
}

6.3 open函数

int open(const char *pathname, int flags);

由此flags的内部,为啥是各种宏定义就说明白了
返回值是int类型——返回的是 file descriptor;失败了返回-1;

file descriptor  ——文件描述符;

这几个常量被定义在头文件fcntl.h中

fcntl 是 “file control” 的缩写。它是由 “file”(文件)和 “control”(控制)两个单词组合而成的。

int open(const char *pathname, int flags);

在介绍这几个常量之前,要先介绍一下open函数,因为这几个常量是用在open函数里面的

其中 pathname 是要打开的文件的路径名,

flags 用于指定打开文件的方式和行为,上面这几个常量就是用在这里的

mode 是一个 mode_t 类型的参数,用于指定创建新文件时的权限

介绍这几个常量:
O_CREAT:在文件打开过程中创建新文件
O_RDONLY:以只读方式打开文件。
O_WRONLY:以只写方式打开文件。
O_RDWR:以读写方式打开文件。
O_APPEND:在文件末尾追加数据,而不是覆盖现有内容。
O_TRUNC:如果文件已经存在,将其里面内容全部删干净。
O_EXCL:与 O_CREAT 一起使用时,如果文件已经存在,则 open() 调用将失败。
O_SYNC:使文件写操作变为同步写入,即将数据立即写入磁盘。
O_NONBLOCK:以非阻塞方式打开文件,即使无法立即进行读写操作也不会被阻塞。

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

#include
#include
#include

#include
#include

int main()
{
	umask(0);  //设置系统权限;
	//int fd = open("log.txt", O_WRONLY|O_CREAT,666);  //三个参数,专门用来创建的
	//如果文件已经存在,那就只打开就行;
	int fd = open("log.txt", O_RDONLY);
	if (fd < 0)
	{
		perror("open");
		return 1;
	}
	//打开成功
	printf("open sucess,fd:%d
", fd);

	//const char* s = "hello write
";
	//write(fd, s, strlen(s));
	char buffer[64];
	memset(buffer, '
#include
#include
#include
#include
#include
int main()
{
umask(0);  //设置系统权限;
//int fd = open("log.txt", O_WRONLY|O_CREAT,666);  //三个参数,专门用来创建的
//如果文件已经存在,那就只打开就行;
int fd = open("log.txt", O_RDONLY);
if (fd < 0)
{
perror("open");
return 1;
}
//打开成功
printf("open sucess,fd:%d
", fd);
//const char* s = "hello write
";
//write(fd, s, strlen(s));
char buffer[64];
memset(buffer, '\0', sizeof(buffer));
read(fd, buffer, sizeof(buffer));
printf("%s", buffer);
close(fd);
return 0;
}
', sizeof(buffer)); read(fd, buffer, sizeof(buffer)); printf("%s", buffer); close(fd); return 0; }

7.分析系统接口的细节,引入fd(文件描述符) ——理论为主;

#include
#include
#include

#include
#include

int main()
{
	int fd1 = open("log1.txt", O_WRONLY | O_CREAT, 666);
	printf("open sucess fd: %d
", fd);
	int fd2 = open("log2.txt", O_WRONLY | O_CREAT, 666);
	printf("open sucess fd: %d
", fd);
	int fd3 = open("log3.txt", O_WRONLY | O_CREAT, 666);
	printf("open sucess fd: %d
", fd);
	int fd4 = open("log4.txt", O_WRONLY | O_CREAT, 666);
	printf("open sucess fd: %d
", fd);

	close(fd1);
	close(fd2);
	close(fd3);
	close(fd4);

    //验证三个标准流
    fprintf(stdout, "hello stdout
");  //输出
	const char*s= "hello 1
";
	write(1, s, strlen(s));

	int a = 10;
	fscanf(stdin, "%d", &a);
	printf("%d
", a);

    char input[16];
	ssize_t s = read(0, input, sizeof(input));
	if (s > 0)
	{
		input[s] = '
#include
#include
#include
#include
#include
int main()
{
int fd1 = open("log1.txt", O_WRONLY | O_CREAT, 666);
printf("open sucess fd: %d
", fd);
int fd2 = open("log2.txt", O_WRONLY | O_CREAT, 666);
printf("open sucess fd: %d
", fd);
int fd3 = open("log3.txt", O_WRONLY | O_CREAT, 666);
printf("open sucess fd: %d
", fd);
int fd4 = open("log4.txt", O_WRONLY | O_CREAT, 666);
printf("open sucess fd: %d
", fd);
close(fd1);
close(fd2);
close(fd3);
close(fd4);
//验证三个标准流
fprintf(stdout, "hello stdout
");  //输出
const char*s= "hello 1
";
write(1, s, strlen(s));
int a = 10;
fscanf(stdin, "%d", &a);
printf("%d
", a);
char input[16];
ssize_t s = read(0, input, sizeof(input));
if (s > 0)
{
input[s] = '\0';
printf("%s
", input);
}
//证明
printf("stdin:%d
", stdin->_fileno);
printf("stdout:%d
", stdout->_fileno);
printf("stderr:%d
", stderr->_fileno);
return 0;
}
'; printf("%s ", input); } //证明 printf("stdin:%d ", stdin->_fileno); printf("stdout:%d ", stdout->_fileno); printf("stderr:%d ", stderr->_fileno); return 0; }

  以上代码运行后,会产生几个问题?

7.1  0,1,2    去了哪里  答:C语言默认打开三个流  stdin(0)、stdout(1)、stderr(2)——默认是FILE*类型;—>内部绝对有fd—>怎样证明?

FILE 是一个结构体类型;C语言设计提供的结构体;结构体一般内部会有各种成员

C文件 库函数内部,一定会调用系统调用;——在系统角度认FILE?还是fd?  答:系统只认fd!所以FILE结构体里,必定封装了fd;

7.2 为什么是这样的数据      

8.1进程要访问文件,必须要打开文件;一个进程可以打开多个文件;一般而言,进程:打开的文件 = 1:n;文件要被访问,前提是加载到内存中,才能被访问!如果是多个进程都打开自己的文件,系统中就会存在大量的被打开的文件!所以OS必须会把如此之多的文件管理起来(先描述,在组织;在内核中,如何看待打开的文件?OS内部要为了管理每一个被打开的文件,会构建 struct file结构体对象,充当一个被打开的文件;结构体中包含了一个被打开文件的几乎所有内容(不仅仅包含属性);)如果有很多,再用双链表组织起来;

8.2进程和文件的对应关系  答:PCB中有一个struct file* array[32] 指针数组,他指向了结构体struct file结构体对象;利用了哈希索引的原理,进程找文件变成了 PCB找数组,数组找结构体struct file对象,再进行操作;(Linux中PCB(task_struct进程控制块里存在一个结构体指针    struct files_struct* files 里面的变量有一个指针数组,指向了struct file结构体对象(那个文件)))

8.3 fd在内核层面上,本质上就是一个数组下标;可以看源代码来证明;

9.未来,如果是多平台跑,肯定是要使用语言级别的文件调用;但是学习系统级别的调用,可以帮助我们更好的理解这个语言级别的调用接口,更多的是为网络调用打好基础;因为网络调用时需要系统级别的文件调用;C语言/C++是没有网络接口的;

10文件:磁盘文件(没有被打开)内存文件(被进程在内存中打开)

本站无任何商业行为
个人在线分享 » Linux 基础IO
E-->