共享文件、
内核用三个相关的数据结构来表示打开的文件:
- 描述符表(descriptor table)。
- 每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。
- 每个打开的描述符表项指向文件表中的一个表项。
- 打开文件表(open file table)。
- 打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。
- 表项包括:文件位置、 引用计数( reference count)(即当前指向该表项的描述符表项数),以及一个指向 v-node 表中对应表项的指针。
- v-node 表(v-node table)。
- 所有的进程共享这张 v-node 表。
- 每个表项包含 stat 结构中的大多数信息,包括 st_mode 和 st_size 成员。
父子进程共享文件:
- 1、父进程和子进程可以共享打开的文件描述符。
- 2、父子进程共享文件描述符的条件:在fork之前打开文件。
- 3、对于两个完全不相关的进程,文件描述符不能共享。
- 4、父子进程文件描述符是共享的,但是关闭的时候可以分别关闭,也可以同时在公有代码中关闭。
fork函数创建子进程后,子进程将复制父进程的数据段、BSS段、代码段、堆空间、栈空间和文件描述符,而对文件描述符关联的内核文件表项(struct file结构),则采用共享的方式。
#include<iostream>
using namespace std;
#include<mutex>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<cstring>
#include<dirent.h>
//安全的输出函数
mutex print_mutex{};
template<class...Args>
void print(const Args&...args)
{
lock_guard<mutex> m(print_mutex);
((cout << args),...);
}
//输出错误函数
void unix_error(const char* msg)
{
print(msg);
exit(0);
}
//安全考虑的open函数,第一个参数是文件路径,第二个参数是打开的模式
int Open(const char* path,int flag)
{
int fd = 0;
//打开文件失败会返回-1
if((fd = open(path,flag)) < 0)
{
unix_error("open file error\n");
}
return fd;
}
//安全考虑的close函数,参数是文件描述符
void Close(int fd)
{
int res = 0;
if((res = close(fd)) < 0)
{
unix_error("close file error\n");
}
}
//安全考虑的read函数
void Read(int fd,char*buf,size_t len)
{
size_t left = len;
size_t reads = 0;
char* tmp = buf;
while(left > 0)
{
//error
if((reads = read(fd,buf,left)) < 0)
{
unix_error("read file error\n");
}
//EOF
else if(reads == 0)
{
break;
}
left -= reads;
tmp += reads;
}
}
//安全考虑的write函数
void Write(int fd,char*buf,size_t len)
{
int n = 0;
if((n = write(fd,buf,len)) < 0)
{
unix_error("write file error\n");
}
}
//打印一个目录下所有文件
void print_dir(const char* path)
{
DIR* dir = opendir(path);
if(dir == nullptr)
{
return;
}
struct dirent*status = nullptr;
if(chdir(path) < 0)
{
unix_error("change dir error\n");
}
while(status = readdir(dir))
{
if(strcmp(status->d_name,".") != 0 && strcmp(status->d_name,"..") != 0)
{
switch(status->d_type)
{
case DT_DIR:
print("dir : ",status->d_name,"\n");
print_dir(status->d_name);
if(chdir(path) < 0)
{
unix_error("change dir error\n");
}
break;
case DT_REG:
print("file : ",status->d_name,"\n");
break;
default:
break;
}
}
}
if(closedir(dir) < 0)
{
unix_error("close dir error\n");
}
}
//读取文件元数据
void read_file_data(const char* path)
{
struct stat status;
if(stat(path,&status) < 0)
{
unix_error("read file data error\n");
}
print("filename : ",path,"\n");
print("type : ",(S_ISDIR(status.st_mode) == 1 ? "dir" : "regular file"),"\n");
print("size : ",status.st_size,"\n");
print("user : ",status.st_uid,"\n");
}
//子进程共享父进程打开的文件描述符
void shared_file(const char* path)
{
int fd = Open(path,2);
const size_t size = 2056;
char* tmp = new char[size];
Read(fd,tmp,size);
if(fork() == 0)
{
print(tmp,"\n");
Close(fd);
delete[]tmp;
exit(0);
}
}
int main(int args,char* argv[],char* envp[])
{
if(argv[1])
{
shared_file(argv[1]);
}
return 0;
}
I/O重定向、
Linux shell 提供了 I/O 重定向操作符( <,>,<<,>>),允许用户将磁盘文件和标准输人输出联系起来。单个 > 表示覆盖,两个 >> 表示追加。
C语言中的I/O 重定向:
dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品,换句话说,两个文件描述符现在都指向同一个文件,并且是函数第一个参数指向的文件,失败返回-1。dup2是一个强大但危险的机制!!!
int dup2(int oldfd, int newfd); //关闭newfd并打开oldfd
int dup(int oldfd);
void IO_Redirection()
{
int fd1 = Open("./test1", 2);
int fd2 = Open("./test2", 2);
//fd1是test1的描述符,fd2是test2的描述符
//更改fd2的描述符为fd1,现在fd1,fd2都是同一个文件的描述符,文件是fd1的文件
//文件fd2被关闭,相当于打开了两次fd1的文件
if (dup2(fd1, fd2) < 0)
{
unix_error("IO_Redirection error\n");
}
char tmp[2056];
Read(fd1, tmp, sizeof(tmp));
print(tmp, "\n");
//两次输出的内容相同,都是文件fd1的内容
Read(fd2, tmp, sizeof(tmp));
print(tmp, "\n");
fflush(stdout);
Close(fd1);
Close(fd2);
}
标准IO、
C 语言定义了一组高级输人输出函数,称为标准 I/O 库,为程序员提供了 Unix I/O的较高级别的替代。标准库I/O是带有缓冲的I/O 。
这个库(libc)提供了:
- 打开和关闭文件的函数(fopen 和 fclose)
- 读和写字节的函数(fread 和 fwrite)
- 读和写字符串的函数(fgets 和 fputs)
- 复杂的格式化的 I/O 函数(scanf 和 printf)
标准 I/O 库将一个打开的文件模型化为一个流。对于程序员而言,一个流就是一个指向 FILE 类型的结构的指针。每个 ANSI C 程序开始时都有三个打开的流 stdin、stdout和 stderr,分别对应于标准输人、标准输出和标准错误。类型为 FILE 的流是对文件描述符和流缓冲区的抽象。
fopen()函数打开一个文件,打开成功返回有效指针,失败返回nullptr。
FILE *fopen(const char *filename, const char *mode);
mode包括下面这些:
r
以只读方式打开文件,该文件必须存在。
r+
以读/写方式打开文件,该文件必须存在。
rb+
以读/写方式打开一个二进制文件,只允许读/写数据。
rt+
以读/写方式打开一个文本文件,允许读和写。
w
打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
w+
打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
a
以附加的方式打开只写文件。若文件不存在,则会创建该文件;如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF 符保留)。
a+
以附加方式打开可读/写的文件。若文件不存在,则会创建该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(EOF符不保留)。
wb
以只写方式打开或新建一个二进制文件,只允许写数据。
wb+
以读/写方式打开或新建一个二进制文件,允许读和写。
wt+
以读/写方式打开或新建一个文本文件,允许读和写。
at+
以读/写方式打开一个文本文件,允许读或在文本末追加数据。
ab+
以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
fwrite()函数功能是向指定的文件中写入若干数据块,如成功执行则返回实际写入的数据块数目,否则返回0。该函数以二进制形式对文件进行操作,不局限于文本文件。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr-- 这是指向要被写入的元素数组的指针。
size-- 这是要被写入的每个元素的大小,以字节为单位。
nmemb-- 这是元素的个数,每个元素的大小为 size 字节。
stream-- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
fclose是一个函数名,功能是关闭一个打开的文件流。如果流成功关闭,fclose 返回 0,否则返回EOF(-1)。
int fclose(FILE *stream)
fread是一个函数。从一个文件流中读取数据到buffer中,最多读取count个元素,每个元素size字节,如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0。
size_t fread( void* buffer, size_t size, size_t count, FILE *restrict stream );
buffer
指向要读取的数组中首个对象的指针
size
每个对象的大小(单位是字节)
count
要读取的对象个数
stream
输入流
void standard_IO()
{
FILE* fptr = nullptr;
if((fptr = fopen("./test1","rt+")) == nullptr)
{
unix_error("open file error\n");
}
char tmp[512];
if(fread(tmp,sizeof(char),sizeof(tmp),fptr) > 0)
{
print(tmp,"\n");
}
if(fclose(fptr) < 0)
{
unix_error("close file error\n");
}
fflush(stdout);
}
标准库I/O,低级I/O的局限性、
1)标准库提供的较高级的I/O非常适用于对终端或文件执行I/O操作,但不太适用于在网络连接方面,因为这不是为网络连接设计的。网络连接的I/O有专门的库来使用。
2)带缓存的I/O与带缓存的不相同I/O不能很好的共存,因为它们有着各自的缓冲区,彼此之间互不了解,还会互相干扰,因此只能选择一个标准的I/O 来使用,且不能够混用。
3)低级别的I/O 不是很好用,存在出错代码,不足值等诸多缺陷,所以一般来说需要自己编写程序或者使用其他程序对它进行包装。
4)标准库I/O提供了很多实用的诸如printf,scanf等不错的函数,它是统一的,在所有的系统中,而且很标准化。所以标准I/O最常使用在日常文件的使用;低级I/O适合用于来完成一些低级操作,如编写信号处理程序,此时不应该使用标准I/O。
5)不能对没有行概念的文件进行I/O 操作,比如jpeg格式的图片等。主要是因为这些函数在处理新行符 0xa 这个特殊字符时会停止读入,或者是在windows下和linux下在处理回车换行符时也会停止读入。同样的,strlen,strcpy等函数在遇到空字节时会停止操作。所以要小心谨慎,有时候你经常使用且熟悉的函数也许完全不适用于你当下的操作,比如二进制数据,或网络通讯等。
综合:我该使用哪些IO函数?
- 函数选用标准\建议
- G1:只要有可能就使用标准 I/O。
- G2: 不要使用 scanf 或 rio_readlineb 来读二进制文件。因为这样的函数是专门用来读去文本文件的。
- G3: 对网络套接字的 I/O 使用 RIO 函数。因为标准IO在网络中的输入输出中存在问题。
- 对于标准IO来说,一般认为是全双工的,除以下两种情况:
- 限制一 :跟在输出函数之后的输入函数。
- 如果中间没有插入对 fflush(清空与流相关的缓冲区)、fseek、 fsetpos 或者 rewind (三者数使用 Unix I/O lseek 函数重置当前的文件位置)的调用,一个输入函数不能跟随在一个输出函数之后。
- 限制二:跟在输入函数之后的输出函数。
- 如果中间没有插人对 fseek、 fsetpos 或者 rewind 的调用,一个输出函数不能跟随在一个输入函数之后,除非该输入函数遇到了一个文件结束。
- 限制一 :跟在输出函数之后的输入函数。
第十章所有练习代码、
#include<iostream>
using namespace std;
#include<mutex>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<cstring>
#include<dirent.h>
#include<stdio.h>
//安全的输出函数
mutex print_mutex{};
template<class...Args>
void print(const Args&...args)
{
lock_guard<mutex> m(print_mutex);
((cout << args),...);
fflush(stdout);
}
//安全的输入函数
mutex input_mutex{};
template<class T>
void input(T& val)
{
lock_guard<mutex> m(input_mutex);
cin >> val;
fflush(stdin);
}
//错误处理函数
void unix_error(const char* msg)
{
print(msg);
exit(0);
}
//安全考虑的open函数,第一个参数是文件路径,第二个参数是打开的模式
int Open(const char* path,int flag)
{
int fd = 0;
//打开文件失败会返回-1
if((fd = open(path,flag)) < 0)
{
unix_error("open file error\n");
}
return fd;
}
//安全考虑的close函数,参数是文件描述符
void Close(int fd)
{
int res = 0;
if((res = close(fd)) < 0)
{
unix_error("close file error\n");
}
}
//安全考虑的read函数
void Read(int fd,char*buf,size_t len)
{
size_t left = len;
size_t reads = 0;
char* tmp = buf;
while(left > 0)
{
//error
if((reads = read(fd,buf,left)) < 0)
{
unix_error("read file error\n");
}
//EOF
else if(reads == 0)
{
break;
}
left -= reads;
tmp += reads;
}
}
//安全考虑的write函数
void Write(int fd,char*buf,size_t len)
{
int n = 0;
if((n = write(fd,buf,len)) < 0)
{
unix_error("write file error\n");
}
}
//打印一个目录下所有文件
void print_dir(const char* path)
{
DIR* dir = opendir(path);
if(dir == nullptr)
{
return;
}
struct dirent* status = nullptr;
if(chdir(path) < 0)
{
unix_error("change dir error\n");
}
while(status = readdir(dir))
{
if(strcmp(status->d_name,".") != 0 && strcmp(status->d_name,"..") != 0)
{
switch(status->d_type)
{
case DT_DIR:
print("dir : ",status->d_name,"\n");
print_dir(status->d_name);
if(chdir(path) < 0)
{
unix_error("change dir error\n");
}
break;
case DT_REG:
print("file : ",status->d_name,"\n");
break;
default:
break;
}
}
}
if(closedir(dir) < 0)
{
unix_error("close dir error\n");
}
}
//读取文件元数据
void read_file_data(const char* path)
{
struct stat status;
if(stat(path,&status) < 0)
{
unix_error("read file data error\n");
}
print("filename : ",path,"\n");
print("type : ",(S_ISDIR(status.st_mode) == 1 ? "dir" : "regular file"),"\n");
print("size : ",status.st_size,"\n");
print("user : ",status.st_uid,"\n");
}
//子进程共享父进程打开的文件描述符
void shared_file(const char* path)
{
int fd = Open(path,2);
const size_t size = 2056;
char* tmp = new char[size];
if(fork() == 0)
{
Read(fd,tmp,size);
print(tmp,"\n");
Close(fd);
delete[]tmp;
exit(0);
}
}
//io重定向
void IO_Redirection()
{
int fd1 = Open("./test1",2);
int fd2 = Open("./test2",2);
if(dup2(fd1,fd2) < 0)
{
unix_error("IO_Redirection error\n");
}
char tmp[2056];
Read(fd1,tmp,sizeof(tmp));
print(tmp,"\n");
Read(fd2,tmp,sizeof(tmp));
print(tmp,"\n");
Close(fd1);
Close(fd2);
}
//标准库io操作
void standard_IO(const char* file,const char* mode)
{
FILE* fptr = nullptr;
if((fptr = fopen(file,mode)) == nullptr)
{
unix_error("open file error\n");
}
char tmp[512];
if(fread(tmp,1,sizeof(tmp),fptr) > 0)
{
puts(tmp);
}
if(fclose(fptr) < 0)
{
unix_error("close file error\n");
}
}
int main(int args,char* argv[],char* envp[])
{
print("hello,world\n");
return 0;
}
Comments NOTHING