Linux学习笔记之文件上

1.文件

        1.1文件属性

        当我们创建文件时,文件就有了对应的属性,可以用mkdir创建目录,touch创建普通文件。用ls -al查看文件属性。

        从上图可以看出目录或者文件的所有者,所属组,其他人权限,创建时间等信息。由此我们便可以得到,一个结论,文件创建出来,即使我们没有写入任何的数据,他也是有大小的。使用stat可以查看文件的详细信息。

stat test.c

        这里大小显示为0并不是没有占内存,而是大小不足一字节,所以显示为0.

                                                文件=文件内容+属性

1.2 文件存储位置

        当文件没有被打开使用时,如果把他放他被放置在系统的磁盘中,但当文件打开时,他就会被加载到内存中。CPU只会与内存进行数据处理,使用文件前必须打开文件说的也就是把文件从磁盘加载到内存中,让CPU进行数据处理

1.3 文件与进程关系

        当我们在修改文件时,在Windows中会默认打开记事本,在Linux中使用vim或者nano。实际上我们不会之间修改文件,而是通过软件修改,其实就是通过进程修改文件。我们要学习文件管理就是在学习OS进程管理与文件管理之间的关系。

1.4如何管理文件

        一个进程可以打开多个文件,多个进程可以打开很多文件,在OS中,这些文件分布在内存的各个地方,为了管理这些文件,Liunx必定会 先描述,在组织。即在使用struct结构体包含文件信息,然后再组织管理起来。如下图

        在Liunx中学习文件操作,就是学习进程管理与文件管理之间的关系。

2.C语言文件操作

        在C语言中,可以使用fopen打开文件,fwrite,fputs,fprintf等写入文件。

2.1 fopen

        

        path参数是文件地址,可以采用绝对地址,也可以采用想对地址。第二个参数是文件打开方式。函数失败返回空指针。

2.1.1 文件打开方式

        文件打开方式主要有6种,a,w,r及其附加类

  • r  以只读方式打开文件,(不可以修改文件)从文件开头读取。
  • r+ 以读写方式打开文件,从文件开头读取。
  • w 以写方式打开文件,文件存在清除,不存在创建新文件,从文件开头写入
  • w+ 以写方式打开文件,文件存在清除,不存在创建新文件,可以读取文件内容,从文件开头写入
  • a 以追加写入方式打开文件,文件不存在则创建,从文件结尾写入
  • a+ 以追加读取写入方式打开文件,文件不存在则创建,从文件结尾写入。但读取从文件开头开始读取

        总而言之,C语言提供了丰富的文件接口使用。不同场景下使用合适的方式。

#include<stdio.h>


int main()
{
    FILE * fp=fopen("hello.txt","w");//不加路径则默认为当前路径下


    fprintf(fp,"%s","hello files!\n");

    fclose(fp);


    return 0;
}

        运行上述程序后,会在当前目录下生成hello.txt文件。

2.2 fclose

       使用起来相对简单,只需要把文件指针传入即可。

         在打开文件后,必须要关闭文件,当我们在修改文件时,文件数据是在内存中的,如果直接结束进程,内存中的数据被释放,文件修改后的数据就可能不存在了。关闭文件实际上就是把在内存中的文件写入到磁盘当中去。

2.3 默认打开文件

        当C语言程序运行时,就会默认打开三个文件。

  • stdin 标准输入       (一般为键盘)
  • stdout 标准输出 (一般为显示器)
  • stderr 标准错误  (一般为显示器)

        2.3.1 键盘与显示器为什么是文件

                OS是软硬件资源的管理者,对上要提供良好的开发环境,对下要管理好硬件。实际上OS要管理好硬件,就是要做好对于数据的IO操作,从硬件读取数据,或者是向硬件写入数据。

        对于硬件来说,他会有许多,OS为了管理硬件,一定会先描述在组织,对下管理硬件资源。创建hardware_struct,里面有设备名称,硬件编号,以及函数指针等。其中的函数指针就是为了读写操作准备的。

        

        由此,当我们想要向硬件写入的时候,不必关注底层硬件设计,只需要使用write函数即可,剩下的交给硬件驱动。这就是Linux下的一切皆文件的实现方式。

        实际上驱动程序主要就是IO操作相关的函数,所以向电脑插入一个设备的时候,电脑首先就会查看有没有相关的驱动程序,如果没有就运行不了。我们之前学习的C语言函数就是对应上图的函数库操作。

        如下源代码

2.4 fwrite

        打开文件就是为了读取或者写入文件,fwrite就是C语言提供的向文件写入函数,通过上述理解此时文件不单指磁盘级文件,还可以是显示器,扬声器,网卡等设备。

  • ptr:指向待写入数据的指针。
  • size:每个元素的大小(字节数)。
  • nmemb:要写入的元素个数。
  • stream:目标文件的指针(由 fopen 返回)。

        返回值是写入元素个数,即nmemb,如果返回值为0表示未写入。

        如下代码。打开一个文件并且向文件中写入数据。

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

int main()
{
	FILE* fp=fopen("log.txt","w");
	
	char*s="hello fwrite!";

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

	fclose(fp);

	return 0;
}

        

        运行结果如下。

注意此时元素长度为strlen(s),不需要加一。在C语言规则中,字符串以\0结尾,在存储时要额外增加一个字节,但此时我们想要向文件中输出字符,文件时OS管理,不必遵循C语言的规则,所以不必加一。假如加上之后,结果如下。

在存储时就是乱码。至于最后如何处理,就要看OS规定了。

2.5 fread

        有写入就一定会有读取,C语言提供了fread函数,帮助读取文件。

  • ptr:指向存储数据的内存地址(需预先分配内存)。
  • size:每个元素的大小(字节数)。
  • nmemb:要读取的元素个数。
  • stream:源文件的指针(由 fopen 返回)。
  • 返回值:实际读取到的元素个数(若返回值 < nmemb,可能是文件末尾或错误)。

        运行下述代码,

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

int main()
{
	FILE* fp=fopen("log.txt","r");
	
	char s[1024];

	fread(s,1,1024,fp);//我们往往无法知道读取文件大小,写1024是数组最大的存储个数比较保险

	printf("%s",s);
	fclose(fp);

	return 0;
}

2.6 fscanf

        上述的文件读写可以用,但是不是很方便,在刚开始写C语言的时候,我们一定使用过scanf从键盘中获取输入,其实就是从标准输入文件中获取输入,那么fscanf就是从指定文件中读取输入。

        他的参数十分简单,第一个参数是文件指针,写想读取的文件指针,后面的参数与scanf的参数一模一样,怎么写scanf就怎么写fscanf

        例如修改log.txt内容为 aaa 1024 bcde

        然后用下述程序读取该文件。

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

int main()
{
	FILE* fp=fopen("log.txt","r");
	
	char s1[1024];
	char s2[1024];
	int num=0;

	fscanf(fp,"%s%d%s",s1,&num,s2);

	printf("读取内容为:%s %d %s",s1,num,s2);


	//关闭文件
	fclose(fp);
	return 0;
}

        有了fscanf函数后,就可以根据自己的想法读取数据了。

2.7 fprintf

        有了读取自然少不了写入,同样他的第一个参数指定文件,后面参数与printf一模一样。

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

int main()
{
	FILE* fp=fopen("log.txt","w");
	
	char s1[1024]="aaaaa";
	char s2[1024]="bbbbbbbb";
	int num=1111000;

	fprintf(fp,"%d %s %s",num,s1,s2);


	//关闭文件
	fclose(fp);
	return 0;
}

2.8 二进制写入与文本写入

        通常习惯称fwrite,fread是二进制读写,fprintf,fscanf是文本读写

        如下代码。fwrite是二进制写入

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

int main()
{
	FILE* fp=fopen("log.txt","w");
	int a=128;

	fwrite(&a,1,sizeof(int),fp);

	//关闭文件
	fclose(fp);
	return 0;
}

        运行上述代码,结果如下。在vim下打开如下图

        如下代码。用fprintf写入结果如下图。

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

int main()
{
	FILE* fp=fopen("log.txt","w");
	int a=128;

	//fwrite(&a,1,sizeof(int),fp);
	fprintf(fp,"%d",a);
	//关闭文件
	fclose(fp);
	return 0;
}

        看起来似乎fwrite写入的结果是乱码,实则不然,假如我们换个角度来看。以二进制的角度来看上述结果。

hexdump -C 文件名 以十六进制打印文件

        打开第一个程序执行完文件

       大小端是计算机存储数据的模式,大端模式是将高位字节存储在内存的低地址,而小端模式是低位字节存储在低地址。比如数字0x12345678,大端模式存储为12 34 56 78,而小端则是78 56 34 12

        这里我的机器采用的就是小段存储,低位放在低地址,所以里面存的数据就是128(0x80).

        打开第二个程序执行完文件,查看十六进制数据

        由上述分析可知,我的机器是小端存储(不同机器可能不一样),所以数据是 3224120(0x31 32 38),但从ASCII表角度来看就是 128!

        这说明对于计算机而言根本就不存在所谓的二进制写入与文本写入,对于计算机而言都是二进制,但从解析数据的角度而言存在差异,以ASCII表解读数据就是文本写入,以二进制解读数据就是二进制写入。

判断机器大小端代码

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

struct A
{
	int num;
};

int main()
{
	struct A s;
	s.num=0x01;
	char * p=(char *)&s;

	if(*p==1)
	{
		//如果为小端 存储数据从低字节开始为 01 00 00 00
		printf("小端存储\n");
	}
	else
	{
		//如果为大端 存储数据从低字节开始为 00 00 00 01
		printf("大端存储\n");
	}
	return 0;
}

3.系统文件操作

        根据之前的操作系统图,C语言函数一定是封装了系统调用接口的。OS不允许用户越过自己直接访问硬件。(当然单片机除外,简单的单片机没有OS)

3.1 open

        open就是操作系统提供的打开文件操作。

        int open(const char *pathname, int flags);该函数第一个参数是文件路径,第二个参数是标记位,可以选择对文件如何操作。有如下主要选项。

  • O_RDONLY:以只读方式打开文件。
  • O_WRONLY:以只写方式打开文件。
  • O_RDWR:以读写方式打开文件。
  • O_CREAT:如果文件不存在,则创建该文件。使用此标志时,需要提供第三个参数 mode 来指定文件的权限。
  • O_TRUNC:如果文件存在且以可写方式打开,则将文件截断为零长度。
  • O_APPEND:以追加方式打开文件,每次写操作都会将数据追加到文件末尾。

        上述标记位可以组合使用,用 | 分隔开。例如O_WRONLY | O_CREAT | O_TRUNC 就可以达到C语言fopen中W的效果,或者说C语言a,r,w就是open不同选项封装的。

3.1.1 返回值

            返回值是int,没有看错,返回值就是朴实无华的int。在Linux进程中我们必定要管理文件,但是一个int类型怎么指向一个文件?怎么根据int确定是那个文件。

        运行下述代码。

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



int main()
{
    
    int f1=open("text1",O_WRONLY|O_CREAT);
    int f2=open("text2",O_WRONLY|O_CREAT);
    int f3=open("text3",O_WRONLY|O_CREAT);
    int f4=open("text4",O_WRONLY|O_CREAT);
    int f5=open("text5",O_WRONLY|O_CREAT);

    printf("f1:%d f2:%d f3:%d f4:%d f5:%d",f1,f2,f3,f4,f5);

    return 0;
}

        我们可以发现如果我们连续打开文件,这是一连串的数字从3开始。计算机中有什么结构有如此规整的数字么?答案显然是数组,至于为什么从3开始,因为我们每个进程默认打开三个文件,标准输入,标准输出,标准错误。

        实际上进程就是使用数组来管理文件的,如下图。

        

        3.1.2 标记位如何实现

        实际上flags就是个32位的位图,每个O_RDONLY,O_WRONLY都是宏定义,且是某个比特位为1的特殊值。如下图

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    printf("%d %d %d %d\n",O_RDONLY,O_WRONLY, O_CREAT,O_TRUNC);

    return 0;
}

      |实际上就是按位或,有1为1,这样在传入参数后,可以根据每个比特位的情况来实现不同的功能,而避免写过多的函数。

3.1.3 open

        在 C 语言里并没有像 C++ 那样的函数重载机制,C 语言要求函数名必须唯一。不过在 Linux 系统中,open 函数存在两种不同参数列表的形式,这并非是通过函数重载实现的,而是借助预处理器和编译器的协作来达成的。

        int open(const char *pathname, int flags, mode_t mode);前两个参数与之前一样,第三个参数是设置文件的权限。

        这里使用数字,实际上也是运用位图的思想,每个选项对应一个比特位。

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

int main() {
    
    open("hello.txt", O_TRUNC | O_WRONLY|O_CREAT, 0777);
    //这里0777表示8进制,二进制为 0 111 111 111

    return 0;
}

        运行上述程序,得到如下结果。

        但看起来与我们设置的777不符合,为0775,这是因为还有掩码umask作用,真实权限是将默认权限值与 umask 值按位取反的结果进行按位与运算。简单记位减去掩码就是真正的权限。权限=open设置-掩码)

        111 111 111 (默认权限)

        111 111 101 (取反后的掩码位)

         ---------------

        111 111 101 (计算结果)

3.2 close

        这个是Linux系统提供的接口,可以关闭文件,他的参数比较特殊是fd,就是文件在进程管理数组中的下标。关闭成功返回0,失败返回-1.

        标准输入,标准输出,标准错误也是文件,在数组下标为0,1,2.一定也可以被关闭。如下代码。

        

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



int main()
{
    
    close(2);
    int f1=open("text1",O_WRONLY|O_CREAT);
    int f2=open("text2",O_WRONLY|O_CREAT);
    int f3=open("text3",O_WRONLY|O_CREAT);
    int f4=open("text4",O_WRONLY|O_CREAT);
    int f5=open("text5",O_WRONLY|O_CREAT);

    printf("f1:%d f2:%d f3:%d f4:%d f5:%d\n",f1,f2,f3,f4,f5);

    return 0;
}

        可以发现下标从2开始。在运行下述程序,关闭0号文件。

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



int main()
{
    
    close(0);
    int f1=open("text1",O_WRONLY|O_CREAT);
    int f2=open("text2",O_WRONLY|O_CREAT);
    int f3=open("text3",O_WRONLY|O_CREAT);
    int f4=open("text4",O_WRONLY|O_CREAT);
    int f5=open("text5",O_WRONLY|O_CREAT);

    printf("f1:%d f2:%d f3:%d f4:%d f5:%d\n",f1,f2,f3,f4,f5);

    return 0;
}

        此时下标从1开始,由此我们可以得出下标的分配是从最小的没开始用的数组下标开始

3.3 write

        系统写入,在打开文件之后就可以对其进行写入读取的操作了。

  • fd表示文件描述符,文件在管理数组中的下标,即open返回值
  • buf是指针,指向数据开始位置
  • count表示要写入的大小是多少字节
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd=open("hello.txt", O_TRUNC | O_WRONLY|O_CREAT, 0777);


	char s[]="hello world\n";
	write(fd,s,strlen(s));

	close(fd);

	return 0;
}

3.4 read

        

  • fd表示文件描述符
  • buf表示读取后缓存的地方
  • count表示最大读取个数,当读取到文件结尾或者读取到最大个数时结束函数
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd=open("hello.txt", O_RDONLY, 0777);


	char s[1024];
	int size=read(fd,s,1024);
	s[size]='\0';//注意这里要在结尾加上\0

	printf("%s",s);

	close(fd);

	return 0;
}

        结果如下

        如果不加上\0,运行下述代码

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

int main()
{
	int fd=open("hello.txt", O_RDONLY, 0777);


	char s[1024];
	int size=read(fd,s,1024);
	//s[size]='\0';

	printf("%s %d",s,strlen(s));

	close(fd);

	return 0;
}

        可以再次得出\0是C语言的规定,OS没有在字符串后面加\0的规则,在读取字符串的时候我们必须自己手动的加。

        实际上上述的C语言函数就是对于open,close这样的系统函数的封装,但FILE是个结构体,里面有许多的属性,但一定会有fd。

        运行下述代码

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

extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

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

	return 0;
}

        由上述的程序就可以得到,三个标准文件的fd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值