Linux---文件

文件 = 内容 + 属性

文件分为打开的文件和没打开的文件。

打开的文件:文件是谁打开的 --- 进程,学习文件,本质上是学习 进程和文件的关系

没打开的文件: 在哪里放着 --- 磁盘,我们最关注什么问题呢? 没有被打开的文件非常多,也就是磁盘上的文件非常多,这些文件有序的放在目录里。文件时如何有序的放置好的呢? -- 我们要快速的对文件进行增删查改,在此之前,要先找到文件。

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

学习C语言的时候,在使用文件操作的时候,如果文件不存在,会自动的在当前路径下创建一个文件。

#include 

int main()
{
  FILE *fd = fopen("1.txt", "w");
  if (fd == NULL)
  {
    perror("fopen");
    return -1;
  }
  fclose(fd);
  return 0;
}

系统是如何找到当前路径的,在执行程序的时候,系统会在一个文件中存储该程序的信息。

Linux---文件_第1张图片

ls /proc/pid可以查看对应进程号的信息。

cwd就是当前路径 --- 如果更改了当前进程的cwd,就可以把文件新建到其他目录

#include 
#include 
int main()
{
  chdir("/home/sxk");
  FILE *fd = fopen("1.txt", "w");
  if (fd == NULL)
  {
    perror("fopen");
    return -1;
  }
  fclose(fd);
  printf("%d\n", getpid());
  sleep(100);
  return 0;
}

Linux---文件_第2张图片

文件确实在sxk目录下。这样就可以把文件创建到其他目录中。


在用代码把内容写入到文件的时候,w是在文件的开头写入,第一次写入 hello world,之后,在写入abcd,为什么hello world都没了,为什么不会是替换前四个字符 --- w 写入之前,都会对文件进行清空处理。

在回想一下重定向,

Linux---文件_第3张图片

也是先清空文件内容,只要把文件打开了,就会先清空,然后再进行写入。


再使用C语言写入文件的时候,strlen (hello world)的长度是11,最后的 字符串结尾标志 ‘\0’要写入吗? --- 不需要,这个结尾标志是C语言的结尾标志,跟文件没什么关系。


还有一种写入方式就是 a,以追加的形式进行写入。


曾经说过,Linux中一切皆文件,我们电脑中的磁盘,网卡,显示器,键盘等外设,其实都有相应的结构体,结构体中有对应的属性,指针等,然后通过结构体指针以数据结构的形式将其关联起来。通过对文件的管理来实现对硬件的管理。


C程序默认再启动的时候,会打开三个文件

STDIN 键盘文件

STDOUT 显示器文件

STDERROR 显示器文件


文件其实是在磁盘上的,磁盘是外部设备,访问磁盘文件其实是访问硬件。

要访问硬件就必须通过系统调用,C语言中的printf,fprintf库函数,都是封装后的系统调用接口,操作系统不相信任何人,它只会放开一些系统调用接口供使用。


Linux---文件_第4张图片

通过 man 2 open可以查看打开文件的接口

第一个参数:要打开文件的文件名

第二个参数:要打开文件的模式

第三个参数:当你想创建一个文件的时候,该文件的权限。

手册里,关于flage的选项也挺多的。

都是大写的,他们都是宏,是二进制序列。比如: 001 代表读, 010 代表写 那么 001 | 010 == 011 代表 读写。

#include 
#include 
#include 
#include 
#include 
#include 

#define ONE (1 << 1)
#define TWO (1 << 1)
#define THREE (1 << 1)
#define FOUR  (1 << 1)

void show(int flags)
{
  if (flags & ONE) printf("function1\n");
  if (flags & TWO) printf("function2\n");
  if (flags & THREE) printf("function3\n");
  if (flags & FOUR) printf("function4\n");
}


int main()
{
  show(ONE);
  show(TWO);
  show(THREE);
  show(FOUR);

  return 0;
}

比如这个代码,通过宏,可以看它的二进制,然后表述打开文件的方式。


#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
  int fd = open("log.txt", O_WRONLY);
  int fd = open("log.txt", O_WRONLY | O_CREAR);
    int fd = open("log.txt", O_WRONLY | O_CREAR, 0666);
  if (fd  < 0)
  {
    perror("open");
    return -1;
  }

  close(fd);
  return 0;
}

O_WRONDLY并不会以创建文件的形式打开文件。可以把 O_CREAT添上去。

再运行,会发现,这个文件

这个文件权限不对

这是因为,再创建文件的时候,要告诉系统,这个文件的权限是什么。用的是八进制,改完之后重新make一下。


也可以用 umask来修改掩码。操作系统中有一个umask,代码中也有一个umask,那么创建文件的时候应该听谁的?就近原则,并且代码中的并不会影响系统中的。

#include 
#include 
#include 
#include 
#include 
#include 

#define ONE (1 << 1)
#define TWO (1 << 1)
#define THREE (1 << 1)
#define FOUR  (1 << 1)

int main()
{
  umask(0);
  int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
  if (fd  < 0)
  {
    perror("open");
    return -1;
  }

  close(fd);
  return 0;
}

运行代码

此时就可以看到文件的权限变成了666 。


open的返回值是一个整数,file descriptor: 文件描述符 fd,后续对文件的操作靠这个整数就可以完成。

close 是关闭文件,把open的返回值当成参数写造close中即可。

write就是写文件,

Linux---文件_第5张图片

Linux---文件_第6张图片

Linux---文件_第7张图片

#include 
#include 
#include 
#include 
#include 
#include 

#define ONE (1 << 1)
#define TWO (1 << 1)
#define THREE (1 << 1)
#define FOUR  (1 << 1)

int main()
{
  umask(0);
  int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
  if (fd  < 0)
  {
    perror("open");
    return -1;
  }
  
  const char* msg = "hello world";

  write(fd, msg, strlen(msg));


  close(fd);
  return 0;
}

那么 要再strlen (msg) + 1吗? 不需要,只需要把文件的有效内容写入进去就行了。

Linux---文件_第8张图片

现在将msg中的字符串改为 123, 但是并不是跟C语言中的w权限一样,覆盖写,这个是覆盖似的往文件中写入。


可以再open中添加上一个权限, 0_TRUNC,每次打开文件的时候先将文件清空。

Linux---文件_第9张图片

int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

还有一个追加写,0_APPEND,追加写是和清空写是冲突的。先把清空写删了,再弄追加写。

Linux---文件_第10张图片

int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);

C语言中的文件操作是库函数,上面用的open,write,close等是系统调用接口。

像Py,C++,JAVA,只要再Linux中用文件操作运行,原来都是一样的。


前面说过,被打开的文件一定会被操作系统管理起来,进程把文件打开,操作系统会创建该进程的PCB,而文件打开之后,操作系统会创建出一个 struct file对象。直接或简介包含 一些属性如(再磁盘的什么位置,基本属性,权限,大小,读写位置,谁打开的,文件的内核缓冲区信息,struct file *next指针)。文件打开的多了之后,会以双链表的形式进行链接,管理文件就变成了以双链表的增删查改。

进程如何和该进程打开的文件建立关系的呢? 进程创建之后系统会创建PCB,PCB中会存在指针(struct file_struct *files),通过指针来找到打开的文件。


还有一个系统调用接口叫做 read,可以把文件中的内容读出来。

Linux---文件_第11张图片

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
  int fd = open("log.txt",O_RDONLY);
  if (fd < 0)
  {
    perror("open");
    return -1;
  }
  char buf[1024];
  ssize_t s = read(fd, buf, sizeof(buf) - 1);
  if (s < 0)
  {
    perror("read");
    return -1;
  }
  buf[s] = '\0'; // 写入文件的时候,不需要把 \0写入进去,但是输出回来的时候需要加上\0。
  printf("%s\n", buf);
  return 0;
}

重定向和缓冲区

查看一下文件描述符

#include 
#include 
#include 
#include 
#include 
#define filename "log.txt"

int main()
{
  int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
  if (fd < 0)
  {
    perror("open");
    return -1;
  }
  printf("fd:[%d]\n", fd);
  const char* msg = "HELLO WORLD\n";

  int cnt = 5;
  while (cnt)
  {
    write(fd, msg, strlen(msg));
    cnt--;
  }

}
// 输出结果 是 3
#include 
#include 
#include 
#include 
#include 
#define filename "log.txt"

int main()
{
  close(0);
  int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
  if (fd < 0)
  {
    perror("open");
    return -1;
  }
  printf("fd:[%d]\n", fd);
  const char* msg = "HELLO WORLD\n";

  int cnt = 5;
  while (cnt)
  {
    write(fd, msg, strlen(msg));
    cnt--;
  }

}
// 输出结果 是 0

如果关闭的不是0,而是1,再次运行程序,就不输出了。但是文件内容却写进去了。

如果关闭的是2,输出的结果就变成了 2。


关0,默认打开的新文件所返回的文件描述符就是0,关2返回2.

文件描述符对应的分配规则是从下标0开始,寻找最小的没有使用的数组位置,它的下标就是新文件的文件描述符。

如果我把循环中的写文件操作改为往文件描述符为1号的文件中写,执行程序后,会直接输出5个HELLO WORLD,如果我先关闭1号,再往1号中去写内容。执行之后发现,本来应该输出再显示器上的内容,却直接打印到了log.txt中。

#include 
#include 
#include 
#include 
#include 
#define filename "log.txt"

int main()
{
  close(1);
  int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
  if (fd < 0)
  {
    perror("open");
    return -1;
  }
  printf("fd:[%d]\n", fd);
  const char* msg = "HELLO WORLD\n";

  int cnt = 5;
  while (cnt)
  {
    write(1, msg, strlen(msg));
    cnt--;
  }
  return 0;
}

Linux---文件_第12张图片

先把1号关了,再打开log.txt文件,fd就变成了1号,while循环中的写文件操作也就变成了往log.txt中写了。

这是不是就是重定向?将要写入到显示器的内容重定向到log.txt文件中。


那么有没有一种函数,可以实现不显示的关闭文件,但是要完成重定向操作?

dup2

dup2函数可以完成。

Linux---文件_第13张图片

oldfd原来的文件描述符,newfd 复制成新的文件描述符。

使newfd成为oldfd的副本,两个文件描述符指向同一个文件

假设newfd已经指向了一个文件描述符,首先close原来打开的文件,然后newfd指向oldfd指向的文件。

#include 
#include 
#include 
#include 
#include 
#include 
#define filename "log.txt"

int main()
{
  int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
  if (fd < 0)
  {
    perror("open");
    return -1;
  }
  
  dup2(fd, 1);
  close(fd);
  printf("fd:[%d]\n", fd);
  const char* msg = "HELLO WORLD\n";

  int cnt = 5;
  while (cnt)
  {
    write(1, msg, strlen(msg));
    cnt--;
  }

}

Linux---文件_第14张图片


缓冲区

#include 
#include 
#include 

int main()
{
  const char *fstr = "hello fwrite\n";
  const char *str = "hello write\n";

  // C语言提供的
  printf("hello printf\n");
  fprintf(stdout, "hello fprintf\n");
  fwrite(fstr, strlen(fstr), 1, stdout);

  // 操作系统提供的
  write(1, str, strlen(str));

  return 0;
}

Linux---文件_第15张图片

这个代码运行之后是这样的结果。

这个结果也符合预期。

Linux---文件_第16张图片

把运行结果重定向到log.txt中后,查看该文件,得到的结果也符合预期。

现在把系统提供的接口write给删了,只留C语言提供的库函数,并在结尾加上close(1);

#include 
#include 
#include 

int main()
{
  const char *fstr = "hello fwrite\n";
  const char *str = "hello write\n";

  printf("hello printf\n");
  fprintf(stdout, "hello fprintf\n");
  fwrite(fstr, strlen(fstr), 1, stdout);

  close(1);

  // write(1, str, strlen(str));
  
  return 0;
}

Linux---文件_第17张图片

结果也符合预期

如果再结尾添加上fork

#include 
#include 
#include 

int main()
{
  const char *fstr = "hello fwrite\n";
  const char *str = "hello write\n";

  printf("hello printf\n");
  fprintf(stdout, "hello fprintf\n");
  fwrite(fstr, strlen(fstr), 1, stdout);

  // close(1);

  write(1, str, strlen(str));
  
  fork();

  return 0;
}

Linux---文件_第18张图片

现在将代码运行结果重定向到log.txt文件中后,再查看文件内存。

Linux---文件_第19张图片

结果和上面结果就不一样了。C语言中的库函数调用了两次。 但肯定和fork有关系。

#include 
#include 
#include 

int main()
{
  const char *fstr = "hello fwrite";
  // const char *str = "hello write\n";

  printf("hello printf");
  fprintf(stdout, "hello fprintf");
  fwrite(fstr, strlen(fstr), 1, stdout);

  close(1);

  // write(1, str, strlen(str));
  
  // fork();

  return 0;
}

现在将系统调用函数和fork给注释掉,再将所有字符串中的换行符给注释掉。

运行,发现,没有任何显示。

再执行完写入代码之后,才关闭的文件,为什么没有打印??

上面用的fprintf,fputs等C函数,底层都调用了write,其实都写入了,只不过是写入到了缓冲区当中。

只不过,这个缓冲区一定不在操作系统内。如果写入到了操作系统当中,就已经写入到了系统当中,再调用close的时候,就可以刷新缓冲区,将内容输出出来。C语言会给我们提供一个缓冲区,这个缓冲区是用户级的缓冲区。等到何时的时候,系统会自动的将C缓冲区自动的写入到系统缓冲区当中。再close的时候,缓冲区并没有内容,所以关闭之后,不会显示出来任何内容。

那为什么上图中加了换行符能输出出来?
显示器的刷新方案是行刷新。再printf执行完之后,遇到了换行符的时候,就会立即将数据刷新出去。

刷新的本质就是将数据通过write写入到内核当中。

目前我们认为:只要将数据刷新到了内核,数据就到达硬件了。

1.缓冲区刷新问题
	无缓冲---直接刷新
    行缓冲---不刷新,直到遇到换行符
    全缓冲---缓冲区满了,才刷新
2.为什么要有这个缓冲区
    解决效率问题---用户的效率问题
    配合格式化

Linux---文件_第20张图片

为什么要有这个C缓冲区

1.解决效率问题,用户效率问题。
2.配合格式化。

这个缓冲区在哪里

FILE里面有对应打开文件的缓冲区字段和维护信息。

这个FILE对象属于用户呢?还是属于操作系统呢?这个缓冲区是不是用户级的缓冲区呢?

不属于操作系统。是用户级缓冲区。


现在来解决一下上面重定向到文件,却出现了7条信息的问题。

向文件打印,缓冲方案变成了全缓冲。

遇到换行符不在刷新,而是等缓冲区域写满才刷新。

代码执行到fork之后,子进程会以写时拷贝的形式父子进程各自私有一份,那么这个用户级别的缓冲区,父子进程也是各自私有一份,这条消息就变成了两份,所以C接口的函数会打印出来两次。

FILE中的缓冲区意义是什么?

如果一次一次的写入到系统缓冲区中太浪费时间,不如一次性写入大量数据。把C语言调用该函数的速度更快。

软硬连接

通过 ln -s 文件名A 要指向A的文件名

可以创建一个软链接

软链接创建的文件是一个独立的文件。具有独立的文件


通过 ln 文件名A 要指向A的文件名

可以创建一个硬链接

Linux---文件_第21张图片


通过 ls -li可以发现,软链接的inode不同,但是硬链接的inode却相同

所谓的硬链接其实就是 再特定的目录的数据块中新增文件名和指向的文件的inode编号的映射关系。

软链接是一个独立的文件,有独立的inode,它的数据块里面保存的是指向文件的路径。就跟windows中的快捷方式一样,我们再桌面点击快捷方式,可以运行程序。

你可能感兴趣的:(Linux,学习,linux,算法)