【Linux】基础文件的I/O操作(1)

1、先来回顾一下C语言中的文件操作接口

(1)文件的写入和读出操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;

int main()
{
	//向文件中,写入数据
	FILE* fp = fopen("myfile", "w");
	if (!fp)
	{
		printf("fpoen error!\n");
	}
	const char *msg = "hello world!\n";
	int count = 5;
	while (count--)
	{
		fwrite(msg, strlen(msg), 1, fp);
	}
	fclose(fp);

	//从文件中向外读取数据
	fp = fopen("myfile", "r");
	if (!fp)
	{
		printf("fopen error!\n");
	}
	char buf[1024];
	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);
	system("pause");
	return 0;
}

从上面的代码中我们可以看出来关于文件操作对的相关函数:

FILE * fopen(const char *文件名,const char * mode);//打开文件
size_t fwrite(const void * ptr,size_t size,size_t count,FILE * stream);//写入文件
size_t fread(void * ptr,size_t size,size_t count,FILE * stream);//读出文件
int fclose(FILE * stream);//关闭文件
int fseek ( FILE * stream, long int offset, int origin );//文件指针偏移

(2)文件的显示

int main()
{
	const char *msg = "hello world!\n";
	fwrite(msg, strlen(msg), 1, stdout);//直接将输出的字符串写入到输出流文件
	printf("hello world!\n");//将格式指向的C字符串写入标准输出(stdout)
	fprintf(stdout, "hello world!\n");//写入数据到输出流
	system("pause");
	return 0;
}

上述运用到的函数:

int fprintf ( FILE * stream, const char * format, ... );//将格式化数据写入流
int sprintf(char * str,const char * format,...;//将格式化的数据写入字符串

结论:c会默认打开三个输入输出流,是stdin、stdout、stderr;对应的在c++中,是std::in、std::out、std::err;三个对应分别是:输入,输出,将错误输出;而且这三个流的类型都是FILE*

2、系统提供的操作文件的I/O接口

从上面的文件操作相关复习代码中我们可以复习到一些高级语言中所提供给我们的一些封装好的的文件操作函数,但是这些都是程序员在系统接口上封装好了的函数调用接口来帮助用户实现这些功能,其实底层还是调用系统所提供的的接口,下来我们去深入了解一下,系统提供的I/O接口;
(1)认识接口

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
	//写入文件
	umask(0);//暂时性的修改权限掩码
	int fd = open("myfile",O_WRONLY | O_CREAT,0644);
	if(fd < 0)
	{
		perror("open:");
		return 1;
	}
	int count = 5;
	const char* msg = "hello world!\n";
	int len = strlen(msg);

	while(count--)
	{
		write(fd,msg,len);
	}	
	close(fd);

	//读出文件
	fd = open("myfile",O_RDONLY);
	if(fd < 0)
	{
		perror("open:");
		return 1;
	}
	char buf[1024];
	while(1)
	{
		size_t s = read(fd,buf,len);
		if(s > 0)
		{
			printf("%s",buf);
		}
		else
		{
			break;
		}
	}
	close(fd);
	return 0;
}

系统接口介绍:
OPEN

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

int open(const char *pathname,int flages);
int open(const char *pathname,int flages,mode_t mode);

1、open接口是一个不定参数函数,open函数用来打开和创建一个文件
2、参数pathname:通常就是一个文件的路径名
3、参数flages:打开方式包括:O_RDONLY(只读)O_WRONLY(只写)O_RDWR(读写)还可以搭配使用(按位与)O_CREAT(若文件不存在则创建)O_EXCL(通常与O_CREAT搭配使用,文件若不在则生成,若在则open报错)O_APPEND(文件已追加模式打开,文件读写指针被置于文件末尾)O_TRUNC(打开文件后消除原有内容)
4、参数mode:用于指定文件权限,但是不是必须要使用的参数,当使用O_CREAT(若文件不存在则创建)时,才必须使用指定文件的创建权限,配合mode_t umask(mode_t mode):修改权限掩码使用
5.、返回值:打开或创建文件的操作句柄-----**文件描述符**

WRITE

#include<unistd.h>

ssize_t write(int fd,const void *buf,size_t count);

1、参数fd:文件描述符----指向一个文件的操作句柄
2、参数buf:要读出的缓冲区首地址
3、参数count:count字节数
4、返回值:成功时,返回所写的字节数(0,表示满意写入数据);错误时,返回-1;并置errno为相应值;

READ

#include<unistd.h>

ssize_t read(int fd,void *buf,size_t count);

1、参数fd:文件描述符----指向一个文件的操作句柄
2、参数buf:要写入的缓冲区首地址
3、参数count:count个字节数
4、返回值:成功时,返回所读到的字节数(0表示读到文件描述符);失败时,返回-1;并置errno为相应值;

LSEEK

#include<sys/types.h>
#include<unistd.h>

off_t lseek(int fd,off_t offset,int whence);

1、参数fd:文件描述符----指向一个文件的操作句柄
2、参数offset:偏移量
3、参数whence:偏移位参考置;包括:SEEK_SET(文件开始位置)SEEK_CUR(文件指针当前位置)SEEK_END(文件结尾位置)
4、返回值:成功时,返回起始位置到当前位置的偏移量;失败时,返回-1;

CLOSE

#include<unistd.h>

int close(int fd);//关闭一个文件描述符

1、参数fd:文件描述符----指向一个文件的操作句柄
2、返回值:成功返回0;失败返回-2;

(2)库函数和系统接口
看一张图:
在这里插入图片描述
结论:
1、系统调用是系统调用实际上是指底层的一个调用,就是内核提供的、功能十分强大的一系列的函数。这些系统调用是在内核中实现的。是操作系统为用户态运行的进程和硬件设备(如CPU、磁盘、打印机等)进行交互提供的一组接口,即就是设置在应用程序和硬件设备之间的一个接口层。可以说是操作系统留给用户程序的一个接口。
2、库函数顾名思义是把函数放到库里,是把一些常用到的函数编完放到一个文件里,供别人用。别人用的时候把所在的文件名用#include<>加到里面就可以了,一般放到lib文件里。库函数一般分为两类:一种是C语言标准规定的库函数,一类是编译器特定的库函数。libc就是一个C标准库,里面放着一些基本的函数,这些函数都被标准化了。
3、所以可以简单地认为**f#**的函数都是对系统调用的封装,方便二次开发

在这里插入图片描述

转载文章
系统调用和库函数详解

(2)文件描述符
注意:看到这里我们发现,在C语言中我们调用库函数接口操作文件都是通过文件指针 (FILE*) 进行操作,但是在系统调用接口当中操作文件都是通过文件描述符进行操作的,那么文件指针(FILE*)我们知道,但是文件描述符是什么呢?

  • 通过对系统调用接口open的学习,可以了解到文件描述符就是一个小整数

0 & 1 & 2

  • llinux下进程默认情况下有三个缺省打开文件描述符,分别是标椎输入0,标椎输出1,标椎错误2
  • 0,1,2对用的物理设备一般是:键盘、显示器、显示器所以还可以采用如下的方式:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>

int main()
{
	char buf[1024];
	ssize_t s = read(0,buf,sizeof(buf));//向标输入中读入
	if(s > 0)
	{
		buf[s] = 0;
		write(1,buf,strlen(buf));//向标椎输出中写入
		write(2,buf,strlen(buf));//向标椎错误中写入
	}
	return 0;
}
//最后程序结束,冲涮缓冲区输出三份数据

在这里插入图片描述
文件描述符:文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
系统通过文件描述符进行文件操作: 进程通过文件描述符在PCB指向的结构体(struct files_struct)中(struct filearry[])数组中找到对应的结构体(struct_file)中的文件信息找到对应的文件进而将要操作的文件和进程链接起来操作文件。
(3)文件描述符分配规则

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

int main()
{
	int fd = open("mytext",O_RDONLY | O_CREAT,0644);//创建一个文件
	if(fd < 0)
	{
		perror("open:");
		return 1;
	}
	printf("fd:%d\n",fd);//此时fd输出3
	close(fd);
	
	close(0);
	fd = open("mytext",O_RDONLY);
	if(fd < 0)
	{
		perror("open:");
		return 1;
	}
	printf("fd:%d\n",fd);//此时fd输出0
	close(fd);
	return 0;
}

结论:先前我们已经文件描述(file)的地址信息都在(files_struct)中的file *fd_array[]中存放着,除去默认打开的默认打开的标椎输入0,标椎输出1,标椎错误2,开始应该是从数组下标3开始的fd=3,但是我们关闭了标椎输入0 在打开文件是fd=0;说明了什么? 在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

重定向

(1)重定向
这里的重定向其实就是对: 对文件描述符的重定向
一般的对文件操作其实就是通过文件描述符操作的,当文件描述符对应内核中的文件描述信息放生了改变的时候,那么操作的还是该文件吗?答案是:不是;当文件描述符对应在内核中的文件描述信息改变为另外一个文件的描述信息,就相当于文件描述符没有改变,但是其描述的具体文件改变了。对应的操作文件描述符操作的文件也就发生了改变;

#include<unistd.h>

int dup2(int oldfd,int newfd);

1、参数oldfd:目标文件描述符
2、参数newfd:将要改变其描述内容的文件描述符
3、让newfd 指向 oldfd ,并且释放newfd中的原来描述信息

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

//写入读出
int main()
{
	int fd = open("./log",O_CREAT | O_RDWR,0644);
	if(fd < 0)
	{
		perror("open:");
		return 1;
	}
	close(1);
	dup2(fd,1);//将1号标识符重定向到fd
	for(;;)
	{
		char buf[1024]={0};
		ssize_t s = read(0,buf,1023);
		if(s < 0)
		{
			perror("read:");
			break;
		}
	printf("%s\n",buf);
	fflush(stdout);
	}

	return 0;
}
/*从标椎输入和获取到数据,并且将数据刷入到标准输出,单数由于标准输出重定向到了文件描述符fd中,所以所有的数据刷入文件描述符fd所指向的文件*/

(2)FILE
文件描述符:系统调用接口的操作句柄 : int fd
文件流指针:库函数接口的操作句柄 : FILE*p

注意:系统调用中并没有缓冲区,而库函数内部封装了一块自己维护缓冲区------用户态缓冲区使用的是用户空间地址
在这里插入图片描述
注意:程序运行与哪个态,取决于接口是否是自己写的(接口是否是系统调用接口而不是封装的库函数)

结论: 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd文件描述符来访问的。 所以FILE结构体内部,同样封装了fd文件描述符printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

如有见解,请不吝赐教

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值