Linux--文件C接口--系统接口--fd--输出重定向--多进程打开同一文件--1010--1014

本文详细介绍了Linux系统中文件的C语言接口,包括打开、创建、清空、关闭文件以及读写操作。深入探讨了文件描述符(fd)的本质和分配规则,讲解了输入输出重定向,特别是bash的重定向方法和系统调用`dup2`的使用。同时,文章阐述了Linux一切皆文件的概念,以及如何管理文件,特别是多进程打开同一文件的情况和可能出现的问题。
摘要由CSDN通过智能技术生成

1. 文件

狭义上的文件:磁盘上的文件。

广义上的文件:显示器,网卡,键盘...。

站在系统的角度,只要能够被输入读取,或者能被输入写出的设备都叫做文件。

Linux认为一切皆文件。访问文件,本质上是进程在访问文件。

2. 文件相关的C语言接口

2.1打开文件

fopen

FILE*fopen(const char*path,const char*mode)

FILE*fp=fopen("makefile,c","r");

path为文件名的路径  mode是访问文件的模式

模式描述文件是否存在
"r"打开文件读取存在
"r+"打开文件读取并写入存在
"w"创建文件并写入已存在就先清空
"w+"创建文件读取并写入已存在就先清空
"a"创建文件并附加写入已存在不清空
"a+"打开文件读取附加写入已存在不清空

2.2 创建文件的路径

当打开文件操作需要创建文件时,该文件会在当前路径中生成。

Linux当前路径:当进程运行起来的时候,每个进程都会记录自己当前所处的工作路径。

查看方法:拿到进程的pid后

ls/proc/14613-l

可以看到exe和cwd cwd即当前路径

2.3 清空文件

FILE*fp=fopen("makefile.c","w");

fclose(fp);

 或者命名行中

>makefile.c

2.4 文件关闭

int fclose(FILE * stream)

fclose(fp);

2.5 文件写入

fwrite

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

const char*s1="hello fwrite";

fwrite(s1,strlen(s1),1,fp);

参数说明 :

const void *ptr : 指针指向要写出数据的内存首地址 ;

size_t size : 要写出数据的 基本单元 的字节大小 , 写出单位的大小 ;

size_t nmemb : 要写出数据的 基本单元 的个数 ;

FILE *stream : 打开的文件指针。

:C语言中字符串以'\0'结尾,这里传入strlen(s1)却不用+1。因为文件不需要遵守C语言的规定,文件仅保存有效数据。

fprintf

int fprintf( FILE *stream, const char *format, ... );


const char*s2="hello fprintf";
fprintf(fp,"%s",s2);//s2以%s的形式输入fp

char name[20] = "Mary";
fprintf( fp, "Hello %s\n", name );//Hello Mary

fputs

int fputs(const char *str, FILE *stream)

const char*s3="hello fputs";

fputs(s3,fp);

2.6 文件读取

fgets

char *fgets(char *s, int size, FILE*stream);

char line[64];//存放的地方

fgets(line,sizeof(line),fp);

从 stream 流中读取 size 个字符存储到字符指针变量 s 所指向的内存空间。

3. 文件操作的系统接口

C语言中使用文件操作,默认打开三个标准输入输出流。stdin,stdout,stderr.但这些流的类型是FILE*

C语言库fopenfwritefreadfclose
系统接口openwritereadcols

3.1 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)

打开成功返回非负数,失败返回-1

umask(0);//将该进程的umask值置0

int fd=open("makefile",O_WRONLY|O_CREAT|O_TRUNC,0666);//rw-rw-rw-

参数说明

flags :一些宏定义。相当于传参,形式是  宏A|宏B|宏C

mode:用八进制代表的权限。 注:这里最后的文件权限会被os的umask值影响。

相关宏:

O_WRONLYO_CREATO_TRUNCO_APPENDO_RDONLYO_RDWR
功能只写没有该文件创建打开就清空追加内容只读可读可写

3.2 close

作用:关闭文件  头文件 <unistd.h>  声明 int close(int fd)

close(fd);

 注意:调用close(fd)就会直接关闭文件,而不会先让缓冲区内的数据刷新出来。

3.3 write

作用:写入  头文件 <sys/types.h> <sys/stat.h> <fcntl.h>

const char*s="hello write";

write(fd,s,strlen(s));//这里给的是元素个数

这里是清空并从头写入还是追加,具体要看open传入的参数

 返回值:写入成功返回写入的字节数。失败返回-1。

3.4 read

作用:读文件到某地方  头文件 <unistd.h>

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

char*buffer[64];

memet(buffer,'\0');

read(fd,buffer,sizeof(buffer));//这里给的是字节大小

从fd读取字节大小为sizeof(buffer)的数据放到 buffer

返回值:读到的字节数,有可能小于count。

4. 文件描述符

fd是什么,在文件中仅打开一次文件,打印fd的值为3。

int main()
{ 
    umask(0);
    int fd1=open("make_my_file.c",O_WRONLY | O_CREAT | O_TRUNC,0666);
    printf("open success,fd:%d\n",fd1);
    return 0;
}

每多打开一次,文件描述符的值就多加1。0、1、2呢?

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    umask(0);
    int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
    printf("open success, fd: %d\n", fd1);
    int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
    printf("open success, fd: %d\n", fd2);
    int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
    printf("open success, fd: %d\n", fd3);
    int fd4 = open("log4.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
    printf("open success, fd: %d\n", fd4);
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    return 0;
}

4.1 fd的本质

数组下标!

在C语言中使用文件操作,默认打开三个标准输入输出流,stdin,stdout,stderr这三个的类型是FILE*类型,FILE是C语言中的一种自定义结构体,针对于系统层面,系统不认识FILE,所以在stdin,stdout,stderr中一定封装了0、1、2,其fd值即是对应的0,1,2。

证明:

printf("stdin:%d",stdin->fileno);//0

进程要访问文件,必须要现打开文件,文件被加载到内存中才能被访问。

进程:其打开的文件=1:n 系统为了管理庞大的文件,构建了struct file{}结构体,一个文件对应一个结构体(内容类似双链表),在用一个FILE*的数组存放这些文件的地址,以便管理。

4.2 fd的分配规则

如果在进程中先关闭了标准输入/输出/错误(0/1/2),在打开一个文件,该文件的fd为(0/1/2)

原理:优先从小到大找没有被占用的文件描述符。

5. 输入重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    umask(0);
    close(0);//把标准输入
    int fd = open("make_my_file.c", O_WRONLY|O_CREAT|O_TRUNC, 0666); //rw-rw-rw-
    if(fd<0)
    {
        perror("open");//在错误中先打印open 然后出现错误原因
        return 1;
    }
    printf("open success, fd: %d\n", fd);//这里fd为0
    char buffer[64];
    fgets(buffer,sizeof buffer,stdin);
    printf("%s\n",buffer); 
    close(fd); 
    return 0;
}

这里本应该从标准输入(键盘)中输入给buffer,这里却从make_my_file.c中输入给了buffer

6. 输出重定向

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    umask(0);
    close(1);//把标准输出(输出到显示器) 关闭了
    int fd = open("make_my_file.c", O_WRONLY|O_CREAT|O_TRUNC, 0666); //rw-rw-rw-
    if(fd<0)
    {
        perror("open");//在错误中先打印open 然后出现错误原因
        return 1;
    }
    printf("open success, fd: %d\n", fd);//这里fd为1
    printf("fd: %d\n", fd); 
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    printf("fd: %d\n", fd);
    fprintf(stdout, "hello fprintf\n");
    const char *s = "hello fwrite\n";
    fwrite(s, strlen(s), 1, stdout);
        

    //已经输出重定向了 往显示屏输入已经变成了往make_my_file文件输入
    //如果不刷新缓冲区 close后文件将没有数据
    fflush(stdout); 
    close(fd); 
    return 0;
}

这些输出原本应该都是往显示器(标准输出)打印的,但是下面的都写入(显示)到了make_my_file.c文件中,这就是输出重定向。

[chy@iZuf6aq431qs6kxxm0vveyZ sometry]$ g++ -o test test.cc
[chy@iZuf6aq431qs6kxxm0vveyZ sometry]$ ./test 
[chy@iZuf6aq431qs6kxxm0vveyZ sometry]$ ls
make_my_file.c  test  test.cc
[chy@iZuf6aq431qs6kxxm0vveyZ sometry]$ cat make_my_file.c 
open success, fd: 1
fd: 1
fd: 1
fd: 1
fd: 1
fd: 1
fd: 1
hello fprintf
hello fwrite

7. bash的输出重定向

bash中,需要将脚本demo.sh的标准输出和标准错误输出重定向至文件demo.log

第一种        bash demo.sh 2>demo.log 1>demo.log

第二种        bash demo.sh 1>demo.log 2>&1            先将标准输入重定向为目标文件

                                                                                   再将标准错误输入到已经改变的1

第三种         命令:&> file  将标准输出stdout和标准错误输出stderr重定向至指定的文件file中                    bash demo.sh &>demo.log  或者  bash demo.sh >&demo.log

 

8. 输出重定向的系统调用接口 dup2

int dup2(int oldfd,int newfd);

newfd是oldfd的一个拷贝,输出重定向的时候,我们需要更改文件描述符的指向,让1号描述符指向的位置发生改变,指向我们需要的位置,所以1号描述符是拷贝自我们需要的位置。1对应着newfd

int fd=open("make_my_file,c",O_WRONLY|O_CREAT|O_TRUNC,0666);

dup2(fd,1);

const char* s1="hello";

fprintf(stdin,"%s\n",s1);

把s1输出到make_my_file.c

  • 重定向后,oldfd和newfd都会操作newfd所操作的文件
  • 重定向前,若newfd已经有打开的文件,则会关闭

 8.1 dup2的使用

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

int main(int argc,char*argv[])
{
    if(argc!=2) 
    {
        return 2;
    }
    int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
    if(fd<0)
    {
        perror("open");
        return 1;
    }
    dup2(fd,1);
    fprintf(stdout,"%s/n",argv[1]);
    return 0;
}

9. 系统如何管理文件

9.1 如何理解Linux一切皆文件

在冯诺依曼体系中,键盘,显示器,网卡等外设均属于IO设备,也就是说在这些"文件中”一定包含了read和write,尽管对于读写的实现可能完全不相同。但我们依然可以创建对应的struct_file结构体,在结构体内使用函数指针的形式,供我们调用方法。当操作系统需要管理这些外设时,仅需要找到struct_file结构体。而如果这些结构体本来就是用双向链表连接起来的,于是我们看待所有文件的方式都统一成为了struct_file,也就是一切皆文件。

9.2文件如何管理

先描述,后管理

文件分为:磁盘文件(未打开)和内存文件(被进程在内存中打开)。每一个进程都有一个pcb结构体。

在pcb中可以找到一个类型为 struct files_struct * 的指针,(在这里以struct files_struct* files为具体对象)files指向struct files_struct结构体。

在struct files_struct结构体中又可以找到一个struct file* fd_array[ ]的数组指针,其每个成员都是struct file类型,即我们所打开的文件。

以fwrite为例

进程执行fwrite()->需要FILE*指针->FILE封装了fd->write()拿到fd,自己开始执行系统内部的wtite方法->在该进程的pcb结构体中->struct files_struct*->fd_array[fd]->进入struct file 即文件中->进行我们的操作

10. 多进程打开同一文件

原子操作(原子性):不可被中断的一个或一系列操作。

例子:银行转账,A 像 B 转账 100 元。转账这个操作其实包含两个离散的步骤:

  • 步骤 1:A 账户减去 100
  • 步骤 2:B 账户增加 100

我们要求转账这个操作是原子性的,也就是说步骤 1 和步骤 2 是顺续执行且不可被打断的,要么全部执行成功、要么执行失败。

两个进程打开同一个文件,但是各有各的文件描述信息以及读写位置,互不影响,因此多个进程同时读写有可能会造成穿插覆盖的情况,不具有原子性

仅读取时,两个不同进程其实都有自己各自的描述信息和读写位置,不受另一进程影响。

若其中一个进程删除了该文件,实际上只是删除文件的目录项,文件的数据以及inode并不会立即被删除,因此若进程已经打开文件,文件被删除时,并不会影响进程的操作,因为进程已经具备文件的描述信息。

若其中一个进程修改了文件,另一个进程可以立即感受到。内容的修改是直接反馈至磁盘文件系统中的,因此当文件内容被修改,其他进程因为也是针对磁盘数据的操作,因此可以立即感知到。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值