linux_I/O基础

输入/输出(I/O) : 是指主存和外部设备(如磁盘,终端,网络)之间拷贝数据过程

在LINUX下,一切皆文件

  • 一个Unix 文件就是一个m个字节的序列:

  • 所有I/O设备都被模型化为文件。

  • 而所有的输入和输出都被当做相应文件的读和写

  • 设备优雅地映射成文件,允许Unix内核引出一个简单,低级的应用接口。叫做Unix I/O,使得所有的输入输出都能以一种统一且一致的方式来执行

打开和关闭文件

在c程序中,任何进程启动时,分别会启动三个输出流:stdin,stdout,stderro,
stdin: 标准输入,对应设备:键盘
stdout:标准输出,对应设备显示器
stderro:标准错误,对应设备显示器
也就是说,如 print 或 scanf 函数都默认打开了这三个输出流,而在linux下也是一样。

打开文件

  1. 应用程序要求内核打开文件
  2. 内核返回一个小的非负整数,叫做文件描述符
  3. 等于内核分配一个文件名,来标示当前的文件。
  4. 内核记录有关这个打开文件的所有信息。应用程序只需要记住标示符。

文件描述符
什么是文件描述符?
文件描述符的本质其实是数组下标。如下图:
在这里插入图片描述
当我们打开一个目标文件时,操作系统要为目标文件创建相应的数据结构来描述这个文件,于是就有了File结构体,来表示已打开的文件。每一个进程都有一个File指针来指向File_struct结构体,这个结构体中包含一个指针数组,每一个元素都存放一个指向打开的文件的指针,下标为0,1,2的元素分别指向stdin,stdout,stderro标准输入,标准输出,标准错误三个文件,只要知道文件描述符就能知道对应的文件。
文件描述符分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

open函数

进程是通过open函数来打开或创建一个文件的

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);
//打开不存在文件同时创建该文件,mode_t mode表示创造所需要的权限

open函数将filename转换为一个文件描述符,并且返回描述符数字。
返回的描述符总是在进程当前没有打开的最小描述符。
flags参数指明了进程打算如何访问这个文件:

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:返回-1;

例子代码

//已只读模式打开一个文件
fd = Open("foo.txt",O_RDONLY,0);
//打开一个已存在的文件,并在后面面添加一个数据
fd = Open("foo.txt",O_WRONLY|O_APPEND,0);

  • 每个进程都有umask
  • 权限掩码,或 权限屏蔽字
  • 所有被设置的权限都要减去这个权限掩码才是实际权限。
  • 777-022=755 或者是 777&~022。
  • 通过umask()函数设置
#define DEF_MODE S_IRUSR|S_IWUSER|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
//所有人都能读和写
#define DEF_UMASK S_IWGRP|S_IWOTH //屏蔽了用户组的写和其他人的写

umask(DEF_UMASK);
fd=oepn("foo.txt",O_CREAT|O_TRUNC|O_WRONLY,DEF_MODE);
//创建了一个新文件,文件的拥有者有读写权利,其他人只有读权限。(屏蔽了用户组的写和其他人的写)

关闭文件

close函数关闭一个打开的文件

#include <unistd.h>
int close(int fd);
//返回: 若成功则为0,若出错则为-1
重定向

linux外壳提供了I/O重定向功能,允许用户将磁盘文件和标准输入输出联系起来。
例如:输出重定向,什么是输出重定向?
就是把原本应该打印到显示器的内容打印到相应的文件中,其中fd=1,常见的重定向有:>> ,>,<
在这里插入图片描述
代码如下:

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

int main()
{
	close(1);
	int fd=open("myfile",O_WRONLY|O_CREAT,00644);
	if(fd<0)
	{
		printf("open erro");
		return 1;
	}
	printf("fd:%d\n",fd);
	fflush(stdout);
	exit(0);
}

I/O重定向如何工作?
使用dup2函数

#include<unistd.h>

int dup2(int oldfd,int newfd);
//返回:若成功则为非负的描述符,若出错则为-1

dup2函数拷贝描述符表表项 oldfd 到描述符表表项 newfd ,覆盖newfd。
如果newfd已经打开,dup2会在拷贝oldfd之前关闭newfd.

原理:struct_File* fd array[ ] 是一个指针数组,array[]中存的都是地址,比如把输出显示器中内容输入到新创建的文件中,只要把array[ 3]中内容,也就是这个新文件的地址拷贝一份,赋给下标为1的元素,就可以了。
追加重定向
追加重定向就是在使用open函数时,采取追加的方式打开这个文件

int fd = open("log.txt",O_WRONLY|O_APPEND);
#include <stdio.h>
#include <string.h>
int main()
{
 const char *msg0="hello printf\n";
 const char *msg1="hello fwrite\n";
 const char *msg2="hello write\n";
 printf("%s", msg0);
 fwrite(msg1, strlen(msg0), 1, stdout);
 write(1, msg2, strlen(msg2));
 fork();
 return 0;
 }

这一段代码的输出结果为:

hello printf
hello fwrite
hello write

但如果对进程实现输出重定向呢? ./hello > file ,得到的结果却是这样的:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

这里write只打印了一次,而printf和write却打印了两次,我们知道这个结果肯定和fork()有关联

一般c库函数写入文件时是全缓冲的,而写入显示器是行缓冲的,printf和fwrite是自带缓冲区的,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲,而我们放入缓冲区的内容会不断被刷新,甚至fork()之后,但是进程退出之后会统一刷新,写入文件中,但是fork()之后父子进程会发生写时拷贝,所以当父进程刷新数据时,子进程会有一份同样的数据,随机产生两份数据,而write没有变化说明没有所谓的缓冲,

printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
在这里插入图片描述

发布了34 篇原创文章 · 获赞 4 · 访问量 1742

猜你喜欢

转载自blog.csdn.net/qq_41181857/article/details/104776559