西部菱斑响尾蛇教你基础IO

news2024/9/23 15:31:11

快学,再不学普洱就要超过你们了

 在C阶段进行的文件操作有哪些呢?

#include<stdio.h>
#include<string.h>

int main()
{
	FILE* fp = fopen("myfile", "w");
	if (!fp)
	{
		printf("fopen error!\n");
	}
	const char* msg = "hello world!\n";
	int count = 5;
	while (count--)
	{
		fwrite(msg, strlen(msg), 1, fp);
	}
	fclose(fp);
	return 0;
}

进行文件操作本质是我们的程序跑起来了,文件打开和关闭是CPU在执行我们的代码

读文件:

#include <stdio.h>
#include <string.h>
int main()
{
	FILE* fp = fopen("myfile", "r");
	if (!fp)
	{
		printf("fopen error!\n");
	}
	char buf[1024];
	const char* msg = "hello world!\n";
	while (1)
	{
		size_t s = fread(buf, 1, strlen(msg), fp);
		if (s > 0)
		{
			buf[s] = 0;
			printf("%s", buf);
		}
		if (feof(fp))
		{
			break;
		}
	}
	fclose(fp);
	return 0;
}

fread是将文件的数据读到缓冲区里

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

 buffer是自己设定的缓冲区,size是要读取数据的大小,char就是1,int就是4,count是要读取的个数,stream是文件指针,返回值是实际从文件中读取的基本单元个数

feof是用于在文件读取已经结束的时候,判断是读取失败导致的结束还是遇到文件尾正常的结束 

将内容输出到显示器上有很多种方法:

#include<stdio.h>
#include<string.h>
int main()
{
	const char* msg = "hello fwrite\n";
	fwrite(msg, strlen(msg), 1, stdout);

	printf("hello printf\n");
	fprintf(stdout, "hello fprintf\n");
	return 0;
}

 均可:

C默认会打开三个输入输出流:stdin, stdout, stderr

这三个流的类型都是FILE*, fopen返回值类型:文件指针  

文件没有被打开的时候在哪呢?

很显然在磁盘

进程能打开很多文件吗?

可以啊

系统中还支持多进程

在很多的情况下,OS内部存在大量的被打开的文件

那操作系统要不要对打开的文件进行管理呢?

肯定可以啊,,,

 应该是存在某种描述文件属性的数据结构,最后转换成指针之间的映射

文件=属性+内容

以w打开文件的话,文件如果不存在,就在当前路径下新建指定文件,且默认打开文件的时候会把目标文件先清空

输出重定向伴随着文件操作

理解文件

操作文件的本质是进程在操作文件

文件在开始的时候在磁盘上,磁盘是外设,本质是硬件

但是用户没有权利直接写入,OS是硬件的管理者,需要通过操作系统进行写入

操作系统为我们提供系统调用的接口,用C/C++是对系统调用接口的封装

访问文件也可以系统调用

C++进行文件操作是这样的:

#include<iostream>
#include<string>
#include<fstream>

#define FILENAME "log.txt"

int main()
{
	std::ofstream out(FILENAME, std::ios::binary);
	if (!out.is_open())
	{
		return 1;
	}
	
	std::string message = "hello C++\n";

	out.write(message.c_str(), message.size());
	out.close();
	return 0;
}

 

 看看系统调用接口:

使用一下系统调用接口: 

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	int fd = open("log.txt", O_WRONLY | O_CREAT);
	if (fd < 0)
	{
		perror("open fail");
		return 1;
	}
	return 0;
}

 在Linux中创建文件的时候要告诉它起始权限,否则将会是乱码 

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if (fd < 0)
	{
		perror("open fail");
		return 1;
	}
	return 0;
}

关于最终的权限还是由umask掩码和起始权限共同决定 

 umask掩码也可以人为设定:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	umask(0);
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if (fd < 0)
	{
		perror("open fail");
		return 1;
	}
	return 0;
}

在传参的时候传递标记位一般是传int,但是有的时候我们只需要知道有无,int有32个bit位

一般用比特位进行标志位的传递,在OS设计系统调用接口的时候比较常用

这个:O_WRONLY | O_CREAT是宏

我们可以自己设计一个传递位图标记位的函数:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)

void Print(int flag)
{
	if (flag & ONE)
	{
		printf("one\n");
	}
	if (flag & TWO)
	{
		printf("two\n");
	}
	if (flag & THREE)
	{
		printf("three\n");
	}
	if (flag & FOUR)
	{
		printf("four\n");
	}
}
int main()
{
	Print(ONE);
	printf("\n");
	Print(ONE | TWO);
	printf("\n");
	Print(ONE | TWO | THREE);
	printf("\n");
	Print(ONE | TWO | THREE | FOUR);
	printf("\n");
	return 0;
}

可以用标记位组合的方式向一个函数中传递多个标记位

在系统中提供的关闭文件的接口是: 

在Linux中提供的向文件中写入的接口是write 

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
	umask(0);
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if (fd < 0)
	{
		perror("open fail");
		return 1;
	}

	const char* message = "Hello Linux file\n";
	write(fd, message, strlen(message));
	close(fd);
	return 0;
}

 

这种写入是在上次基础上进行写入 

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
        umask(0);
        int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
        {
                perror("open fail");
                return 1;
        }
        const char* message = "Hello Linux\n";
        //const char* message = "Hello Linux file\n";
        write(fd, message, strlen(message));
        close(fd);
        return 0;
}

 

打开文件的时候想要清空怎么办捏?

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
	umask(0);
	int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	if (fd < 0)
	{
		perror("open fail");
		return 1;
	}

	const char* message = "Hello Linux file\n";
	write(fd, message, strlen(message));
	close(fd);
	return 0;
}

这是写方式打开,存在就创建,不存在就清空

想要进行追加写入该怎么办呢?

就是APPEND啦~

看这个代码: 

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
	int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fda:%d\n", fda);
	int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fdb:%d\n", fdb);
	int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fdc:%d\n", fdc);
	int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
	printf("fdd:%d\n", fdd);
	return 0;
}

 看输出结果:

 

 为何打印出是此种结果呢?

012去哪了?

0:标准输入     键盘

1:标准输出     显示器

2:标准错误     显示器

 在C中默认打开的文件流:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	const char* msg = "hello pineapple\n";
	write(1,msg,strlen(msg));
	return 0;
}

 就输出啦

刚输出的3456是文件描述符fd

fd的本质是什么?为什么可以通过这个数字向显示器文件中写入?

操作系统对被打开的文件要创建内核数据结构struct_file,对这些内核数据结构采用双链表的形式进行管理

对文件的管理变为对链表的增删查改

它指向文件内核级的缓存

进程:文件 = 1:n

在进程的PCB中存在struct files_struct *files,它指向struct files_struct,struct files_struct里面有一个指针数组struct file* fd_array[N],数组就有下标,到时只需要把描述文件的结构体变量地址存放在这个指针数组中即可

所以文件描述符fd的本质是内核的进程和文件映射关系的数组的下标

无论读和写,都要在合适的时候让OS把文件内容读到文件缓冲区中

那open是在做什么呢?

1.创建file

2.开辟文件缓冲区的空间,加载文件数据

3.查进程的文件描述符表

4.file地址,填入对应的表下标中

5.返回下标

还是看看这个open:

 open函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则使用两个参数的open

012是硬件,要理解一切皆文件

这些设备都是IO设备(外设)

他们间的映射是通过指针完成的

比如键盘:

//函数指针
void (*read)(...)
void (*write)(...)
//函数
void k_read();
void k_write();

显示器鼠标同理:

void screen_read();
void screen_write();

void mouse_read();
void mouse_write();

 想要访问显示器,就找到对应的struct file让函数指针找到对应函数就可以了

类有属性和方法,struct file就是类

在OS内,系统在访问文件的时候,只认文件描述符fd

那么C是如何用FILE* 访问文件呢?

FILE是C提供的一个结构体类型

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	FILE* fp = fopen("log.txt", "w");
	if (fp == NULL)
	{
		return 1;
	}
	printf("fd:%d\n", fp->_fileno);
	fwrite("hello", 5, 1, fp);
	fclose(fp);
	return 0;
}

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main()
{
	printf("stdin->fd:%d\n", stdin->_fileno);
	printf("stdout->fd:%d\n", stdout->_fileno);
	printf("stderr->fd:%d\n", stderr->_fileno);

	FILE* fp = fopen("log.txt", "w");
	if (fp == NULL)
	{
		return 1;
	}
	printf("fd:%d\n", fp->_fileno);
	
	FILE* fp1 = fopen("log1.txt", "w");
	if (fp1 == NULL)
	{
		return 1;
	}
	printf("fd:%d\n", fp1->_fileno);
	
	FILE* fp2 = fopen("log2.txt", "w");
	if (fp2 == NULL)
	{
		return 1;
	}
	printf("fd:%d\n", fp2->_fileno);

	fclose(fp);
	return 0;
}

 封装了系统的细节

可以使用系统调用(系统不同提供的调用接口,代码不具跨平台性),也可以用语言提供的方法

C语言本身有源代码---标准库

C标准库对于不同系统会生成不同版本的C标准库

所有的语言都具有跨平台性

C语言为什么要这样做呢?

因为所有语言都具有跨平台性,所以它们要对不同平台的系统调用进行封装,不同语言进行封装的时候,文件接口就会有差别

cout,cin,cerr都被叫做类

内部也包含了相应的文件描述符

文件描述符就是从0开始的小整数,当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体:表示一个已经打开的文件对象。

进程执行open系统调用,所以必须让进程和文件关联起来

每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分包含一个指针数组,每个元素都是一个指向打开文件的指针,本质上,文件描述符就是该数组的下标

只要拿着文件描述符,就可以找到对应的文件

重定向

读文件操作:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

const char* filename = "log.txt";

int main()
{
	struct stat st;
	int n = stat(filename, &st);
	if (n < 0)
	{
		return 1;
	}
	printf("file size:%lu\n", st.st_size);
	int fd = open(filename, O_CREAT | O_RDONLY);
	if (fd < 0)
	{
		perror("open fail");
		return 1;
	}

	close(fd);
	return 0;
}

 

 康康:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

const char* filename = "log.txt";

int main()
{
	struct stat st;
	int n = stat(filename, &st);
	if (n < 0)
	{
		return 1;
	}

	printf("file size:%lu\n", st.st_size);
	int fd = open(filename, O_RDONLY);
	
	if (fd < 0)
	{
		perror("open fail");
		return 2;
	}
	printf("fd:%d\n", fd);

	char* file_buffer = (char*)malloc(st.st_size + 1);
	
	n = read(fd, file_buffer, st.st_size);
	if (n > 0)
	{
		file_buffer[n] = '\0';
		printf("%s\n", file_buffer);
	}
	free(file_buffer);
	close(fd);
	return 0;
}

 

#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include<sys/types.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
const char* filename = "log.txt";

int main()
{
    close(0);
    int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
    if(fd<0)
    {
      perror("open fail");
      return 1;                                                   
    }                                                            
                                                                 
    printf("fd:%d\n",fd);                                        
    close(fd);                                                   
                                                                 
    return 0;                                                    
}          

 

文件描述符有一套对应的分配规则

它会查找自己的文件描述表,分配最小的没有被分配的fd

printf和fprintf默认是向显示器中打印,但是这样可以向文件内打印:

#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include<sys/types.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>

const char* filename = "log.txt";

int main()
{
  close(1);
  int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
  if(fd<0)
  {
    perror("open fail");
    return 1;
  }

  printf("fd:%d\n",fd);
  fprintf(stdout,"fprintf,fd:%d\n",fd);

  fflush(stdout);
  close(fd);

  return 0;
}

这个过程叫做重定向

printf/fprintf默认是向stdout中打印的,stdout有对应的struct FILE,里面对应的_fileno==1

重定向的本质是在内核中改变文件描述符表特定下标的内容,和上层无关

 但是我们可以发现如果不加fflush的话是默认没有打印内容的

 struct FILE*里有语言级别的缓冲区,所以需要刷新才能打印出来

看个函数,dup2,完成对文件描述符下标所对应内容的拷贝

进行标准的输出重定向应该:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

const char* filename="log.txt";

int main()
{
  int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);
  
  dup2(fd,1);

  printf("hello world\n");
  fprintf(stdout,"hello world\n");
  return 0;
}

 所以如果改一下的话,实现像以前一样的多终端打印则可以:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

const char* filename = "log.txt";

int main()
{
	int fd = open("/dev/pts/2", O_CREAT | O_WRONLY | O_TRUNC, 0666);

	if (fd < 0)
	{
		perror("open fail\n");
		return 1;
	}

	dup2(fd, 1);

	printf("hello world\n");
	fprintf(stdout, "hello world\n");

	fflush(stdout);
	return 0;
}

缓冲区 

缓冲区即有用户级缓冲区,又有内核级缓冲区

功能主要有两种:解耦和提高效率

提高效率是指能提高使用者的效率

调用系统调用需要成本,可提高IO刷新的效率

缓冲区要给上层提供高效的IO体验,同时提高整体的效率

刷新策略

那缓冲区的刷新策略是什么呢?

1.立即刷新:从内核到外设的刷新,比如fflush(stdout),再比如fsync(int fd)...

2.行刷新:一般应用于显示器

3.全缓冲,缓冲区写满,才刷新,应对普通文件

还有特殊情况是进程退出系统自动刷新

缓冲区刷新策略和用户级内核级无关,是通用的(均码)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

const char* filename = "log.txt";

int main()
{
	printf("hello printf\n");
	fprintf(stdout, "hello fprintf\n");

	const char* msg = "hello write\n";
	write(1, msg, strlen(msg));

	fork();
	return 0;
}

在我们默认打印的时候是向显示器打印的,显示器刷新的策略为行刷新

当重定向到文件中,刷新策略会发生变化

所以log.txt会显示成这样的原因是,重定向之后,刷新策略发生变化,普通文件采用全缓冲策略进行刷新,假设没有fork,不论是否重定向,数据都直接写入操作系统内部,文件缓冲区数据已经存在,fork变得无意义

当重定向之后变为全缓冲,文件缓冲区并没有被写满,所以会把它暂时保存起来,在stdout对应的缓冲区,在fork时并没刷新,还在缓冲区(因为没写满),而在fork时父子进程都会刷新

所以语言级别的printf和fprintf被打印了两次

库函数输出两次,系统调用只输出一次:

库函数写入文件时是全缓冲,写入显示器是行缓冲

printf、 fwrite 库函数会自带缓冲区

当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲,放在缓冲区中的数据不会被立即刷新(fork之后也不会)

进程退出之后,会统一刷新,写入文件当中

fork的时候,父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。 write 没有变化,说明没有所谓的缓冲

printf fwrite 库函数会自带缓冲区,write 系统调用没有带缓冲区 (均指用户级缓冲区)

为了提升整机性能,OS也会提供相关内核级缓冲区

那这个缓冲区谁提供捏?

printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,write 没有缓冲区,printf fwrite 有,该缓冲区是二次加上的,由C标准库提供 

每一个文件都有一个自己的缓冲区

这是FILE结构体:

struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */
 char* _IO_write_end; /* End of put area. */
 char* _IO_buf_base; /* Start of reserve area. */
 char* _IO_buf_end; /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 
 struct _IO_marker *_markers;
 
 struct _IO_FILE *_chain;
 
 int _fileno; //封装的文件描述符
#if 0
 int _blksize;
#else
 int _flags2;
#endif
 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
 
#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];
 
 /* char* _save_gptr; char* _save_egptr; */
 
 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

shell加个重定向

在进行重定向的时候,进程中的程序替换不会影响进程关联的或者打开的文件

因为进程替换的是代码和数据,内核数据结构不受影响

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<ctype.h>
#include<errno.h>
#include<sys/wait.h>

#define SkipPath(p) do{ p += (strlen(p) - 1); while (*p != '/')p--;}while(0)

#define SIZE 512
#define ZERO '\0'
#define SEP " "
#define NUM 32

#define None_REDIR 0
#define In_REDIR 1
#define Out_REDIR 2
#define App_REDIR 3

#define SkipSpace(usercommand,pos) do{\
	while (1){\
		if(isspace(usercommand[pos]))\
			pos++;\
		else break;\
	}\
}while (0)

int redir_type = None_REDIR;
char* filename = NULL;

int lastcode = 1;
char cwd[SIZE * 2];
char* gArgv[NUM];

void Die()
{
	exit(1);
}

const char* Home()
{
	const char* home = getenv("HOME");
	if (home == NULL)
	{
		return "/";
	}
	return home;
}

void Cd()
{
	const char* path = gArgv[1];
	if (path == NULL)
	{
		path = Home();
	}
	chdir(path);
	char temp[SIZE * 2];
	getcwd(temp, sizeof(temp));
	snprintf(cwd, sizeof(cwd), "PWD=%s", temp);
	putenv(cwd);
}

const char* GetUserName()
{
	const char* name = getenv("USER");
	if (name == NULL)
	{
		return "None";
	}
	return name;
}

const char* GetHostName()
{
	const char* hostname = getenv("HOSTNAME");
	if (hostname == NULL)
	{
		return "None";
	}
	return hostname;
}

const char* GetCwd()
{
	const char* cwd = getenv("PWD");
	if (cwd == NULL)
	{
		return "None";
	}
	return cwd;
}

void MakeCommandLine()
{
	char line[SIZE];
	const char* username = GetUserName();
	const char* hostname = GetHostName();
	const char* cwd = GetCwd();

	SkipPath(cwd);
	snprintf(line, sizeof(line), "[%s@%s %s]#", username, hostname, strlen(cwd) == 1 ? "/" : cwd + 1);
	printf("%s", line);
	fflush(stdout);
}

int GetUserCommand(char command[], size_t n)
{
	char* s = fgets(command, n, stdin);
	if (s == NULL)
	{
		return -1;
	}
	command[strlen(command) - 1] = ZERO;
	return strlen(command);
}


void splitCommand(char command[], size_t n)
{
	gArgv[0] = strtok(command, SEP);
	int index = 1;
	while ((gArgv[index++] = strtok(NULL, SEP)));
}

void ExecuteCommand()
{
	pid_t id = fork();
	if (id < 0)
	{
		Die();
	}
	else if (id == 0)
	{
		if (filename != NULL)
		{
			if (redir_type == In_REDIR)
			{
				int fd = open(filename, O_RDONLY);
				dup2(fd, 0);
			}
			else if (redir_type == Out_REDIR)
			{
				int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
				dup2(fd, 1);
			}
			else if (redir_type == App_REDIR)
			{
				int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND);
				dup2(fd, 1);
			}
			else
			{

			}
		}
		execvp(gArgv[0], gArgv);
		exit(errno);
	}
	else
	{
		int status = 0;
		pid_t rid = waitpid(id, &status, 0);
		if (rid > 0)
		{
			lastcode = WEXITSTATUS(status);
			if (lastcode != 0)
			{
				printf("%s:%s:%d\n", gArgv[0], strerror(lastcode), lastcode);
			}
		}
	}
}

int CheckBuildin()
{
	int yes = 0;
	const char* enter_cmd = gArgv[0];
	if (strcmp(enter_cmd, "cd") == 0)
	{
		yes = 1;
		Cd();
	}
	else if (strcmp(enter_cmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
	{
		yes = 1;
		printf("%d\n", lastcode);
		lastcode = 0;
	}
	return yes;
}

void CheckRedir(char usercommand[])
{
	int pos = 0;
	int end = strlen(usercommand);
	while (pos<end)
	{
		if (usercommand[pos] == '>')
		{
			if (usercommand[pos + 1] == '>')
			{
				usercommand[pos++] = 0;
				pos++;
				redir_type = App_REDIR;
				SkipSpace(usercommand, pos);
				filename = usercommand + pos;
			}
			else
			{
				usercommand[pos++] = 0;
				pos++;
				redir_type = In_REDIR;
				SkipSpace(usercommand, pos);
				filename = usercommand + pos;
			}
		}
		else if (usercommand[pos] == '<')
		{
			usercommand[pos++] = 0;
			redir_type = In_REDIR;
			SkipSpace(usercommand,pos);
			filename = usercommand + pos;
		}
		else
		{
			pos++;
		}
	}
}

int main()
{
	int quit = 0;
	while (!quit)
	{
		redir_type = None_REDIR;
		filename = NULL;
		MakeCommandLine();

		char usercommand[SIZE];
		int n = GetUserCommand(usercommand, sizeof(usercommand));
		if (n < 0)
		{
			return 1;
		}

		CheckRedir(usercommand);

		splitCommand(usercommand, sizeof(usercommand));

		n = CheckBuildin();
		if (n)
		{
			continue;
		}

		ExecuteCommand();
	}
	return 0;
}

  

封装库 

 带不带\n可以决定刷新的效率:

mystdio.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>

#define LINE_SIZE 1024
#define FLUSH_NOW 1
#define FLUSH_LINE 2
#define FLUSH_FULL 4

struct _myFILE
{
	unsigned int flags;
	int fileno;

	char cache[LINE_SIZE];
	int cap;
	int pos;            //下次写入的位置
};

typedef struct _myFILE myFILE;

ssize_t my_fwrite(myFILE* fp, const char* data, int len);
void my_fflush(myFILE* fp);
myFILE* my_fopen(const char* path, const char* flag);
void my_fclose(myFILE* fp);

mystdio.c 

#include"mystdio.h"

ssize_t my_fwrite(myFILE* fp, const char* data, int len)
{
	memcpy(fp->cache + fp->pos, data, len);
	fp->pos += len;
	if ((fp->flags & FLUSH_LINE) && fp->cache[fp->pos - 1] == '\n')
	{
		my_fflush(fp);
	}
	return len;
}

void my_fflush(myFILE* fp)
{
	write(fp->fileno, fp->cache, fp->pos);
	fp->pos = 0;
}

myFILE* my_fopen(const char* path, const char* flag)
{
	int flag1 = 0;
	int iscreate = 0;
	mode_t mode = 0666;
	if (strcmp(flag, "r") == 0)
	{
		flag1 = (O_RDONLY);
	}
	else if (strcmp(flag, "w") == 0)
	{
		flag1 = (O_WRONLY | O_CREAT | O_TRUNC);
		iscreate = 1;
	}
	else if (strcmp(flag, "a") == 0)
	{
		flag1 = (O_WRONLY | O_CREAT | O_APPEND);
		iscreate = 1;
	}
	else
	{

	}
	int fd = 0;
	if (iscreate)
	{
		fd = open(path, flag1, mode);
	}
	else
	{
		fd = open(path, flag1);
	}
	if (fd < 0)
	{
		return NULL;
	}
	myFILE* fp = (myFILE*)malloc(sizeof(myFILE));
	if (!fp)
	{
		return NULL;
	}
	fp->fileno = fd;
	fp->flags = FLUSH_LINE;

	fp->cap = LINE_SIZE;
	fp->pos = 0;

	return fp;
}

void my_fclose(myFILE* fp)
{
	my_fflush(fp);
	close(fp->fileno);
	free(fp);
}

test.c

#include"mystdio.h"
#include<stdio.h>
#include<unistd.h>
#include<string.h>

#define FILE_NAME "log.txt"

int main()
{
	myFILE* fp = my_fopen(FILE_NAME,"w");
	if (fp == NULL)
	{
		return 1;
	}

	const char* str = "hello world";

	int cnt = 10;
	char buffer[128];
	while (cnt)
	{
		sprintf(buffer, "%s - %d\n", str, cnt);
		my_fwrite(fp, buffer, strlen(buffer));
		cnt--;

    sleep(1);
  }

	my_fclose(fp);
	return 0;
}

 说实话真挺恶心的

stderr为何存在

’>标准输出重定向,只会更改1号fd里面的内容

stderr存在主要是因为我们输出的信息有两类:正确or错误

错误信息单独往stderr中打印,这样只需要一次就可以从文件层面把正确信息和错误信息分开了

 将正确信息和错误信息分开只需:

./a.out 1>ok.txt 2>err.txt

 正确错误一起:

./a.out 1>all.txt 2>&1

有个接口perror

它的本质是向2打印

#include<iostream>
int main()
{
	std::cout << "hello cout" << std::endl;
	std::cerr << "hello cerr" << std::endl;
	return 0;
}

系统中存在非常多的文件,而被打开的文件只是少量的

没有被打开的文件到底在哪存放呢?

都在磁盘耶,就叫磁盘文件

打开需找到需定位(要在磁盘中找到  --  文件路径+文件名) 

本质是研究文件如何存取

磁盘存储结构

磁盘长这样:

 01是规定出的结果,可能在物理上会有不同的表现

盘片可读可写可擦除,一片两面都可写数据

磁盘本质是机械设备,优点是稳定和便宜

磁盘分为桌面级磁盘(民用)和企业级磁盘

就真的以为自己是华山峰的人了~

磁盘是一个机械设备,外设,比较慢,性价比高

康康磁盘的存储结构

 磁盘清理:烧掉/全都写成0或1

每个圈都是磁道

读写的基本单位是扇区:512字节,4kb

1片  =  n磁道

1磁道  =  m扇区

怎样找到指定位置的扇区呢?

先找到对应的磁头(Header),再找到指定的磁道(柱面)(Cylinder),找到指定的扇区

(Sector),这是CHS定址法

盘片旋转:定位扇区

磁头左右摆动:定位磁道

文件  =  内容  +  属性

OS对磁盘这样的设备进行管理及抽象

为什么要这样进行抽象呢?

OS直接用CHS耦合度太高,而且抽象后能方便内核实现磁盘管理

每个扇区都对应成数组下标,变成:

struct disk_array[N];

 每一个磁道都有一百个扇区

 这样转化:

index / 1000 = H

index % 1000 = temp; [0, 999]

temp / 100 = C

temp % 100 = S

只要到时候把编号交给磁盘就OK

文件 = 很多个sector数组的下标

一般而言,OS未来和磁盘交互的时候,基本单位是4kb(规定如此)

要有8个sector,8个连续的扇区(块大小)

文件由很多块构成

对于OS而言,读取数据可以以块为单位

只要知道一个起始和磁盘的总大小,有多少个块,每个块的块号,如何转换到对应的多个CHS地址就全知道了

LBA:逻辑区块地址(Logical  Block  Address,LBA)

LBA block[N];

电脑的磁盘只有一块,所谓C盘D盘是不同的分区 

磁盘分区管理好一组能管理好每一个组,分治思想

文件系统

文件的磁盘存储本质是文件内容+文件属性数据

在ll的时候能看到这些:

文件名+文件元数据

每行的七列:

 模式,硬链接数,文件所有者,组,大小,最后修改时间,文件名

ll是读取存储在磁盘上的文件信息并显示出来:

stat能看到更多的信息:

先看看整体的架构:

Linux ext2文件系统,磁盘文件系统图(内核内存映像不同),磁盘是块设备,硬盘分区被划分为一个个的block

一个block的大小是由格式化的时候确定的,并且不可以更改

启动块(Boot Block)的大小是确定的

Block Group:ext2文件系统会根据分区的大小划分为数个Block Group,每个Block Group都有着相同的结构组成

data blocks是数据区,存放文件内容 

block bitmap是块位图,记录着data block中的哪个数据块已经被占用,哪个数据块没有被占用,比特位的位置表示块号,比特位的内容表示该块是否被占用 

innode里面的也都是块,innode位图,innode Bitmap里每个bit表示一个innode是否空闲可用

Linux中文件的属性是一个大小固定的集合体

里面有文件的属性

struct innode
{
    int size;
    mode_t mode;
    int creater;
    int time;
    ...
    int innode_number;
    ... 
}

 一个文件一个innode

innode内部不包含文件名

内核层面每一个文件都要有innode number,通过innode号标识一个文件

GDT:块组描述符,描述块组属性信息

超级块(Super Block):存放文件系统本身的结构信息,记录的信息:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的 时间,最近一次检验磁盘的时间等其他文件系统的相关信息

Super Block的信息被破坏  ==  整个文件系统结构被破坏,并不是每个系统都有,某几个有,具有健壮性,让文件系统变得更稳定

在每一个分区内部分组,写入文件系统的管理数据(格式化),在磁盘中写入文件系统

本质上只能通过innode找文件,找文件需要先知道innode号

innode编号是以分区为单位的

innode的范围属于块组,data block也是这样的

i节点表:存放文件属性:文件大小,所有者,最近修改时间等

数据区:存放文件内容

一般是这样的:

int datablocks[15];

其中data block的直接映射的下标是[0,11],12,13是指向其他数据块,为了进行扩容

14指向的是索引,索引指向其他索引

通过间接映射建立更多的文件

但是不建议跨区,磁盘可能会重新选址

将属性和数据分开存放,具体是这样工作的:

 

创建新文件的四个操作:

1.存储属性

内核先找到一个空闲的i节点把文件信息记录到其中 

2. 存储数据

该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800

将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推

3. 记录分配情况

文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表

4. 添加文件名到目录

新的文件名abc

那linux如何在当前的目录中记录这个文件捏?

内核将入口(929279,abc)添加到目录文件

文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来 

这是新分区的结果: 

怎样拿到innode呢?

一直用的是文件名

目录是不是文件捏?

是哎

目录=文件属性+文件内容

文件内容:文件名和innode的映射关系

一个目录下不能建立同名文件:因为文件名在一个目录必须唯一,查找文件顺序:找到innode编号

目录的r本质是是否允许我们读取目录的内容,文件名指innode的映射关系

目录的w是新建文件,最后要向当前所处的目录内容中写入文件名和innode的映射关系

删除文件只需要找到组里的位图,删除文件并不删除属性和内容,只是删除对应的位图(删除快是因为删除只需要把文件属性和内容设置为无效即可)

那么如果文件被误删可以恢复么?

可以a,但是如果文件被误删很容易被覆盖内容,如果新建文件进行复用可能真的找不到

Linux的回收站本质是目录!

 目录是图形化显示

从回收站恢复数据本质是把数据mv回来

哦对还有个事,我的电子榨菜呜呜

没东西听了 

要找到指定的文件则必须找到该文件所在的目录,打开需要根据文件名:innode

目录是文件,要找到目录名和innode

找到根目录才能依次找到各级目录,需要进行逆向的路径解析

这是由OS自己做的

在Linux中定位一个文件,在任何时候都要有路径的原因

Linux目录结构是树状的,而Linux会缓存路径结构

要确定文件在哪个分区里,拿到文件的innode更前提的是知道在哪个分区

innode只在分区内有效,那怎么知道innode在哪个分区有效呢?

 进入分区本质是进入指定的目录

运维少年最关注的事,,,

罪恶根源:

配置服务器的盘一般就一块(配置比较低啦)

在根目录下进行的操作一般都在/dev/vda下进行

在Linux中也可以构建大文件,/dev/zero是设备提供的,这是自己形成了10M的文件

dd if=/dev/zero of=disk.img bs=1M count=10

 

 这样格式化:

mkfs.ext4 disk.img

 挂载是这样的:

可以看到它挂载到这个目录:

访问分区有前置内容: 分区-->写入文件系统(格式化)-->挂载到指定的目录下-->进入该目录-->在指定的分区中进行文件操作

任何文件都要有目录,而只要有目录,挂载到相应的路径访问到字符串的前缀,在哪个目录里,就在哪个分区下

目录通常是谁提供的捏?

肯定是你或者你的进程提供的呀

由内核文件系统提前写入并组织好,然后我们提供的

路径解析不麻烦吗,麻烦啊!

所以Linux内核在被使用的时候,存在大量的解析完毕的路径,要对访问的路径做管理

那要怎么管理捏?

内核有个结构包含路径解析,一个文件一个dentry

struct dentry
{
    struct dentry *next;
    listnode list;
}

 形成一棵树帮助我们找到对应的文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1982145.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型系列:大模型tokenizer分词编码算法BPE理论简述和实践

关键词&#xff1a;大语言模型&#xff0c;分词&#xff0c;BPE&#xff0c;BBPE 前言 token是大模型处理和生成语言文本的基本单位&#xff0c;在之前介绍的Bert和GPT-2中&#xff0c;都是简单地将中文文本切分为单个汉字字符作为token&#xff0c;而目前LLaMA&#xff0c;Cha…

云原生高级必备基础

一.文件管理 相对路径和绝对路径 touch 创建文件 mkdir 创建目录 -p多级创建 rm 删除 -i 删除前逐一询问确认。 -f 即使原档案属性设为唯读&#xff0c;亦直接删除&#xff0c;无需逐一确认。 -r 将目录及以下之档案亦逐一删除。 cp 复制 -p -r mv 移动 cp和mv的区别 …

8月4号分析:CSGO市场行情如何,给几个操作建议

很多粉丝让我聊聊对近期CSGO饰品市场的看法&#xff0c;那今天就简单聊聊&#xff01; 最近的CSGO市场&#xff0c;从在线人数就可以看出来&#xff0c;这段时间是实打实的流失了很多玩家&#xff0c;就目前这个情况&#xff0c;120万的在线人数里面&#xff0c;至少还有10多万…

sql注入之无列名注入

目录 一、基础知识 二、平替information_schema库 三、无列名注入 3.1 正常无列名查询&#xff1a; 3.2 子查询&#xff1a; 3.3 实战 一、基础知识 我们在注入的过程中很多时候利用的就是information_schema这个库获取 table_schema table_name, column_name这些数据库内…

一键转换语言,五款强大文件翻译软件推荐!

在当今的职场环境中&#xff0c;跨语言沟通已成为常态。无论是与国际客户洽谈业务&#xff0c;还是处理海外项目报告&#xff0c;精准高效的文件翻译能力都是每位职场人士的必备技能。今天&#xff0c;我们就来盘点几款职场人士必备的文件翻译工具。 福昕在线翻译&#xff1a;…

PXE批量安装——————rhel7

实验前准备 什么是PXE&#xff1f; PXE是一种基于网络的启动技术&#xff0c;它集成了在计算机的BIOS或UEFI中&#xff0c;允许计算机从网络服务器下载并启动操作系统或其他软件。 应用场景 无盘工作站&#xff1a;在教育和科研机构中&#xff0c;无盘工作站通过PXE启动操作…

字符串切割split

let obj {} let str "aa占比:17.48%,aa计费占比:0.00%" let arr str.split(,) // [aa占比:17.48%,aa计费占比:0.00%] arr.forEach(item > { let [key,value] item.split(:) obj[key] value }) console.log(obj) //{aa占比: 17.48%, aa计费占比: 0.00%} con…

Markdown文本编辑器:Typora for Mac/win 中文版

Markdown 是一种轻量级的标记语言&#xff0c;它允许用户使用易读易写的纯文本格式编写文档。Typora 支持且仅支持 Markdown 语法的文本编辑&#xff0c;生成的文档后缀名为 .md。 这款软件的特点包括&#xff1a; 实时预览&#xff1a;Typora 的一个显著特点是实时预览&#x…

lombok安装成功但是找不到方法

2024.1.1版本的IDE的插件安装了默认的lombok&#xff08;如图1&#xff09;&#xff0c;pom文件中也引入了lombok的依赖&#xff0c;在实体类写了Data的注解&#xff0c;当调用实体类的get和set方法运行时&#xff0c;报错找不到相应的方法&#xff0c;但是在调用get、set方法的…

Java实现全局异常统一处理

Java实现全局异常统一处理 【一】介绍【二】为什么要使用全局异常处理【三】案例实现【1】GlobalException【2】GlobalExceptionHandler【3】使用 【一】介绍 全局异常处理器是一种在应用程序中几种处理异常的机制&#xff0c;它允许在应用程序的任何地方抛出异常时&#xff0…

2024年6月scratch图形化编程等级考试一级真题

202406 scratch编程等级考试一级真题 选择题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 1、音乐Video Game1的时长将近8秒&#xff0c;点击一次角色&#xff0c;下列哪个程序不能完整地播放音乐两次 A、 B、 C、 D、 答案&#xff1a;D 考点分…

CasaOS系统小主机Docker部署memos结合内网穿透打造私有云笔记

文章目录 前言1. 使用Docker部署memos2. 注册账号与简单操作演示3. 安装cpolar内网穿透4. 创建公网地址5. 创建固定公网地址 前言 本文主要介绍如何在CasaOS轻NAS系统设备中使用Docker本地部署开源云笔记服务memos&#xff0c;并结合cpolar内网穿透工具配置公网地址&#xff0…

STL-queue容器适配器

目录 一、queue 1.1 使用 1.2 模拟实现 二、priority_queue 2.1 使用 2.2 仿函数 2.2.1 概念 2.2.2 使用 2.3 模拟实现 一、queue 1.1 使用 具体解释详见官方文档&#xff1a;queue - C Reference (cplusplus.com) queue就是数据结构中的队列&#xff1a;数据结构之…

体系结构论文导读(三十四):Design of Reliable DNN Accelerator with Un-reliable ReRAM

文章核心 这篇文章主要讨论了一种在不可靠的ReRAM&#xff08;阻变存储器&#xff09;设备上设计可靠的深度神经网络&#xff08;DNN&#xff09;加速器的方法。文章提出了两种关键技术来解决ReRAM固有的不可靠性问题&#xff1a;动态定点&#xff08;DFP&#xff09;数据表示…

日撸Java三百行(day14:栈)

目录 一、栈的基本知识 1.栈的概念 2.栈的功能 3.栈的实现 二、栈的代码实现 1.栈的基本属性与方法 2.栈的遍历 3.入栈实现 4.出栈实现 5.数据测试 6.完整的程序代码 总结 一、栈的基本知识 1.栈的概念 根据百度百科&#xff0c;我们知道“栈”是存储货物或供旅客…

小怡分享之Java图书管理系统

前言&#xff1a; &#x1f308;✨前面小怡给大家分享了抽象类和接口&#xff0c;今天小怡给大家分享用Java实现图书管理系统。 1.功能 不同的用户看到的菜单是不一样的&#xff0c;我们分为两个用户身份&#xff0c;管理员和普通用户。 2.知识点 数据类型、变量、数组、方法…

跳妹儿学编程之ScratchJr(12):综合篇-五只小猴子床上跳

博主资深软件架构师&#xff0c;拥有13年大型软件与互联网系统开发、设计和架构经验&#xff0c;曾就职于华为&#xff0c;现任职于国内知名互联网公司。平时在家教咱家“跳妹儿”编程&#xff0c;并将心得和过程记录下来。希望可以帮助更多对编程感兴趣的家庭。 引言 在前面的…

略读ArrayList源码

ArrayList是Java集合框架中的一部分&#xff0c;底层是通过数组实现的&#xff0c;可以动态增长和缩减。 一、首先看成员变量 序列化ID定义。在Java中&#xff0c;如果一个类实现了Serializable接口&#xff0c;那么它的serialVersionUID就非常重要了。serialVersionUID用于确…

Cesium初探-CallbackProperty

在Cesium中&#xff0c;CallbackProperty 是一种非常有用的特性&#xff0c;可以用来动态更新实体的属性&#xff0c;如位置、方向、高度等。CallbackProperty 允许你在指定的时间点计算属性值&#xff0c;这样就可以实时地改变实体的状态而不需要频繁地重新设置整个属性。 下…

PCIe学习笔记(16)

层次结构&#xff08;Hierarchy&#xff09;ID Message &#xff08;PCIe I/O 互连的树形拓扑结构称为 PCIe 的 Hierarchy&#xff0c;或称层级、层次&#xff08;不是事务层、数据链路层的“层”&#xff09;。层次区域是指与 RC 某一 RP 相关联的所有设备和链路组成的线路结…