linux入门---三个操作系统调用接口

Posted 叶超凡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux入门---三个操作系统调用接口相关的知识,希望对你有一定的参考价值。

getpid

在内存中的每个进程都有一个属于自己的PID,我们可以通过ps ajx指令查看进程的基本属性从而查看一个进程的pid,那么这里我们就可以创建一个文件然后往里面写入一个死循环的代码,再创建一个makefile文件在里面实现一些基本的功能,那么这里的操作就如下:

输入make指令并执行新生成的可执行程序就会出现下面的现象:

这时屏幕上就开始不停的打印内容说明我们的进程真正不停的被执行所以这时就可以通过指令ps ajx 查看进程对应的属性:

但是这里显示的进程特别的多,而我们只想查看一个名为myproc的进程,所以这时就可以使用grep指令和管道来查找这里打印出来的内容,这里的指令为:ps ajx| grep 'myproc'

但是这样的查找有一个缺点就是没有显示头部的属性信息,我们无法知道这里的数据代表的是什么意思,所以这里就得将指令改成这样:ps ajx| head -1 && ps ajx|grep 'myproc'


这样显示进程的时候就会将进程的头部信息也显示出来,仔细地观察一下就可以看到myproc进程的pid为26893,但是这样查看进程PID的方法就有点麻烦,所以操作系统就提供了getpid函数这个函数可以直接返回本进程的PID0值(也可以把这里的本进程称之为子进程),并且这个函数没有任何的参数,我们来看看这个函数的介绍:

有了这个函数就可以对之前的文件进行修改,在文件里面调用这个函数并打印这个函数的返回值来显示一个进程的pid,那么修改后的代码如下:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     while(1)
  7     
  8         printf("我是一个进程!我的pid为:%d\\n",getpid());
  9         sleep(1);
 10     
 11     return 0;
 12  

重新输入make指令再运行一下可执行程序就会出现下面的现象:

并且跟指令ps ajx| head -1 && ps ajx|grep 'myproc'显示的值是一样的

那么这就是getpid函数的作用。

getppid

getpid的作用是获取子进程的id这个我们都可以理解,当不停的把程序加载进内存的时候子进程的id会不停的发生变化比如说下面的操作:

而getppid的作用就是获取子进程的父进程的id,我们来看看这个函数的介绍:

这个函数没有参数并且该函数的返回值就是父进程的id,所以通过这个函数我们就可以在程序里面获取该进程的父进程的id,那么修改后的代码就如下:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     printf("我是一个进程!我的pid为:%d 我的父进程为:%d",getpid(),getppid());                                                               
  7     return 0;
  8      

然后不停的执行程序就可以看到下面的现象:

我们发现这里的执行结果就与上面的有一些不一样:子进程的id在不停的发生变化,但是该进程的父进程的id却一直没有发生改变此时父进程的id为32282那这是为什么呢?我们退出服务器重新登录再运行一下这个程序看看结果又是什么样的?

此时父进程的id发生了改变变成了8368,而且我们再创建一个文件

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<sys/types.h>
  4 int main()
  5 
  6     printf("我是另外一个进程我的id为:%d 我的父进程为:%d",getpid(),getppid());
  7   

并生成可执行程序打印这个进程的父进程id时我们依然会发现这个id是8368

那这是为什么呢?这个8368到底是谁啊?那么为了解决这个问题我们可以通过指令
ps ajx| head -1 && ps ajx|grep 8368来查找一下这个id对应的进程的名字是什么?那么这里的查找结果如下

这个PID对应的进程叫做bash。之前我给大家讲过一个故事说张三特别喜欢如花但是张三的情商特别的低不善于和女生沟通所以张三就找当地一个特别著名的媒婆:王婆,王婆受到了张三的请求跑去找了如花可是如花一个跟别人与子偕手白头到老了,所以王婆也没有办法完成张三的请求可是张三这个人脑子一根筋他非如花不娶而且他的爸爸是村长,他就用着他爸爸的权力强迫王婆完成她的请求,王婆一看这有点进退两难啊,所以王婆就不停的招聘实习生让实习生去跟如花说好话,一旦这个实习生没有完成任务或者放弃任务了王婆就将这个实习生炒鱿鱼并招聘其他的实习生来继续完成这个任务,实习生有没有崩溃实习生有没有完成任务都和王婆本人没有任何的关系,这样王婆既不用与如花直接接触又可以逃避村长和张三的强迫,那么这里的王婆就是bash这里的实习生就是我们在命令行上面执行的各种可执行程序,可执行程序是否运行成功可执行程序是否存在问题都不会影响到bash的稳定,因为这里的程序是以子进程的方式执行,子进程的父进程是bash也就是命令行,我们可以使用kill -9指令来杀死父进程看看有什么什么反应:

我们可以看到当杀死了父进程bash之后就直接断开了连接让我们重新登录,而再次登录的时候就会发现父进程的pid又发生了变化,也就是说我们每次登录机器的时候操作系统都会给我们分配一个新的shell或者说是bash再或者说是命令行,而命令行上启动的进程,一般它的父进程没有特殊情况的话都是bash。

那么这就是父进程希望大家能够理解

fork

我们上面解释了什么是子进程什么是父进程,而fork函数的功能就是在父进程中创建子进程,我们来看看fork函数的介绍:


啊这里的内容就非常的多大家就只用知道几点就行,首先这个fork函数是没有参数的并且该函数有个返回值,学习这个函数的第一步就是如何证明fork函数创建了子进程,我们来看看下面的代码:

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     printf("我是一个进程!我的pid为:%d 我的父进程为:%d\\n",getpid(),getppid());
  7     return 0;
  8  

将其运行一下就可以看到屏幕上只打印了一行语句:

但是此时我们将程序进行修改在打印语句前面调用一次frok函数看看结果会变成什么样:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     fork();
  7     printf("我是一个进程!我的pid为:%d 我的父进程为:%d\\n",getpid(),getppid());
  8     return 0;                                                                                                                                
  9      

再次运行一下程序就可以看到这里打印出来了两句话,并且这两句话的内容是不一样的

那这就能够说明我们这段程序在执行的过程中创建了一个子进程,并且在子进程中也执行了printf语句,可是这里就有个问题,该程序在创建子进程的时候是从代码一开始创建的就创建了子进程还是将fork函数运行完就创建了子进程的呢?所以为了解决这个问题就可以将文件中的代码进行修改在fork函数之前再添加一个printf函数,那么这里的代码如下:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     printf("hello world\\n");
  7     fork();
  8     printf("我是一个进程!我的pid为:%d 我的父进程为:%d\\n",getpid(),getppid());
  9     return 0;
 10    

如果hello world被打印了一次就说明父进程是在调用fork函数的时候创建的子进程,如果hello world被打印了两次就说明子程序的创建应该是在父进程刚开始被执行的时候,那么上述代码执行的结果就如下:

这就说明子进程的创建是在调用fork函数的时候,并且当fork函数创建子进程之后会有两个进程来执行fork函数之后的代码所以这里就会打印两次我是一个进程。看了上面的函数介绍我们可以知道一件事就是fork函数会有一个返回值,而且父进程的返回值和子进程的返回值是不一样的,父进程会把子进程的id作为fork函数的返回值,而子进程会把0作为父进程的返回值,比如说下面的代码:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     int i=fork();                                                                                                                            
  7     printf("fork函数的返回值为%d\\n",i);
  8     return 0;
  9 

这段代码的执行结果为

我们可以看到这里打印了两个数据其中一个为0,那么这个0就是子进程fork函数的返回值,非0就是父进程的fork函数的返回值,这就是fork函数的特性,该函数虽然只调用了一次但是却有两个返回值,子进程的fork永远都返回0父进程的fork永远都返回子进程的pid,所以我们就可以根据父子进程的fork函数的返回值来让父子进程执行接下来的不同的代码比如说下面的代码:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 int main()
  5 
  6     int i=fork();
  7     int a=10;
  8     int b=20;
  9     if(i!=0)
 10     
 11         printf("我是父进程\\n");
 12         printf("a+b的结果为:%d\\n",a+b);
 13     
 14     else
 15     
 16         printf("我是子进程\\n");
 17         printf("b-a的结果为:%d\\n",b-a);                                                                                                     
 18     
 19 
 20     return 0;
 21 

这段代码的运行结果如下:

我们可以看到这里虽然创建了一个子进程但是子进程和父进程却在各自的程序中根据fork函数的返回值执行者不同的代码,那么这就是fork函数的返回值的意义希望大家能够理解。

Linux入门基础IO

基础IO

✔回顾C文件的接口

在学习C语言时我们了解了一些C语言的对于文件操作的接口。
其中有fopen、fclose、fputc、fgetc、fputs、fgets、fprintf、fscanf、fread、fwrite等

用一段代码简单回顾一下:

   #include<stdio.h>
   #include<string.h>
   int main()
   
     	FILE* fp=fopen("myfile","a+");                                                               
     	if(!fp)
     	
      		printf("fopen error\\n");
     	
    	int count=5;
    	const char *m="hello linux\\n";
    	while(count--)
    	//对文件进行写入
    	fwrite(m,strlen(m),1,fp);
    
    //关闭文件
    fclose(fp);
    return 0;
  

我们先要了解,把内容写入文件中,先要有这个文件,然后就是要打开这个文件。
在上面的代码中

FILE* fp=fopen("myfile","a+");

第一个参数:文件的路径/文件名(不带路径会在当前路径下创建这个文件)。
当前路径:当前进程运行的路径。
第二个参数:就是以怎样的方式来。

在学习Linux时,我们经常听说“一切皆文件“。

那么显示器、键盘是文件吗?
在C语言时,我们经常用printf()函数来把内容显示到显示器上。
而现在,我们不用printf()函数来打印内容。

     #include<stdio.h>
     #include<string.h>
     int main()
     
    	char *m="hello linux\\n";  
        fwrite(m,strlen(m),1,stdout);                                                                                             
       	return 0;
    


这样我们可以了解,显示器也可以看作文件,也可以用fwrite()函数来写入。

重点来了:任何进程在运行时,都会默认打开三个输入输出流。
分别是:
标准输入(键盘)stdin
标准输出(显示器)stdin
标准错误(显示器)stderr
这三个流的类型都是FILE*,文件指针。


✔系统文件I/O

文件操作除了上述的C接口以外,还有我们的系统接口来进行对文件的操作。

我们用C接口操作文件在Linux上跑,其实是C在调用Linux的系统接口来完成的。
所以说,C库文件的接口是对系统调用接口的一次封装。

第一个接口:open接口,与C的区别是前面没有f。

   #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);

参数分别是:路径或文件名、选项、权限。

选项:

  • O_RDONLY: 只读打开
  • O_WRONLY: 只写打开
  • O_RDWR : 读,写打开
    这三个常量,必须指定一个且只能指定一个
  • O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  • O_APPEND: 追加写

其中注意的是这些接口的返回类型是int。
文件打开成功后,会返回一个较小的非负整数,表示该文件的文件描述符。
失败返回-1。

用一段代码来感受一下吧。

     #include<stdio.h>
     #include<sys/stat.h>
     #include<sys/types.h>
     #include<fcntl.h>
     #include<unistd.h>
     #include<string.h>
     int main()
     
       umask(0);
      int fd=open("myfile",O_WRONLY|O_CREAT,0666);
      if(fd<0)
      
        perror("open");
        return 1;
      
      char *buf="hello linux\\n";                                                                
      write(fd,buf,strlen(buf));
    
      close(fd);
      return 0;
    


其中一: open中的0666,表示创建文件的时候文件权限的666。当然这要设置一下默认掩码。
其中二:O_WRONLY|O_CREAT 表示如果有该文件就对该文件以只写的方式打开,如果没有就创建这个文件,权限为666,以只写的方式打开。

为什么要用O_WRONLY|O_CREAT来表示呢?
不难看出,这些用大写字母来表示的选项是用宏。这些宏都是对应一个bit位,像位图一样。
我们在传O_WRONLY|O_CREAT的时候,
会用if(O_WRONLY&F)来判断这个选项等等。

而在write中第一个参数是文件的描述符。而文件描述符又是什么呢?

✔文件描述符

我们先用一段代码来感受文件描述符:

   #include<sys/types.h>
   #include<sys/stat.h>
   #include<fcntl.h>
   #include<stdio.h> 
   int main()
   
     umask(0);
    int fd1=open("myfile",O_WRONLY|O_CREAT,0666); 
    int fd2=open("myfile",O_WRONLY|O_CREAT,0666);
    int fd3=open("myfile",O_WRONLY|O_CREAT,0666);  
    int fd4=open("myfile",O_WRONLY|O_CREAT,0666);
  
    printf("fd1:%d\\n",fd1);
    printf("fd2:%d\\n",fd2);
    printf("fd3:%d\\n",fd3);
    printf("fd4:%d\\n",fd4);                                                                                        
    return 0;
  


open执行成功返回一个较小的非负整形,也就是文件描述符。
通过上面的代码执行效果来看,有点像一个数组的下标。
其实这就是一个数组的下标,其中数组的0、1、2下标分被键盘(标准输入)、显示器(标准输出)、显示器(标准错误)给占了。
所以分配下来的是3、4、5、6。

而为什么是数组呢?
那么我们先要了解内存文件和磁盘文件了。

上面的代码创建了myfile文件,其文件的属性(文件大小、文件名、最近一次修改文件的时间等)会以struct file结构体在内存中保存起来。
而该文件的内容是在磁盘上的,也就是磁盘文件。
而这些struct file会被操作系统用双链表的形式来组织起来,和PCB类似。

而一个进程创建会创建PCB,其中PCB中有一个指针指向一个叫files_struct的结构体,其结构体中有一部分是以指针数组的形式存在的,其中存放的内容就是struct file结构体的地址。

进程通过文件描述符找到这个存放文件地址的地放,进而来对文件进行操作。

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。
于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进
程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

文件描述符的分配规则

如果我们close(0)

     #include<sys/stat.h>
     #include<sys/types.h>
     #include<fcntl.h>
     #include<stdio.h>  
     #include<unistd.h>
     int main()
     
      close(0);
      umask(0);
      int fd1=open("myfile",O_WRONLY|O_CREAT,0666);
    
      printf("fd1:%d\\n",fd1);
      return 0;
    


文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

重定向

我们如果关闭close(1)

	 //输出重定向
     #include<sys/stat.h>
     #include<sys/types.h>
     #include<fcntl.h>
     #include<stdio.h>
     #include<unistd.h>
     #include<string.h>
     
     int main()
     
      close(1);
      umask(0);
   	  int fd1=open("myfile",O_WRONLY|O_CREAT,0666);    
      char *duf="hello linux\\n";
	  
	  //printf("%s",duf);
	  //fflush(stdout);//更新流的用户空间缓冲数据
      write(1,duf,strlen(duf));                                                                 
      return 0;
     

✔FILE

在学习C语言中的对文件操作的函数中有FILE*类型的。

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

那么FILE*是什么呢?

FILE是一个结构体,FILE*是一个结构体的指针。
我们都知道,C库中的IO相关的函数其实是对系统调用的封装。
在系统调用的IO型接口中,open函数的类型是int型。

int open(const char *pathname, int flags, mode_t mode);

open函数返回的是一个文件描述符。可以通过文件描述符来找到对应的文件。

而FILE结构体中就有一个int型的变量来表示这个文件描述符,这就是为什么C中的IO也可以找到对应的文件,这就是一种封装。

我们来看一下FILE结构体中的代码。

在/sur/include/stdio.h 可以找到
FILE中的int _fileno就是对文件描述符的封装。

struct _IO_FILE 
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */
 char* _IO_write_ptr; /* Current put pointer. */
 char* _IO_write_end; /* End of put area. */
 char* _IO_buf_base; /* Start of reserve area. */
 char* _IO_buf_end; /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 struct _IO_marker *_markers;
 struct _IO_FILE *_chain;

 int _fileno; //封装的文件描述符

//……

我们来看下面一段代码:

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

int main()

	close(1);
	int fd=open("myfile",O_WRONLY|O_CREAT,0666);
	
	const char* arr="hello linux\\n";
	
	fwrite("arr",strlen(arr),1,stdout);
	
	return 0;


字符串并没有打印到显示器上,而是被写入到了myfile文件中。

为什么呢?

在这之前先要了解,C中的stdin、stdout、stdree这三个流都是FILE*型的,并且这三这FILE中的文件描述符被固定为0、1、2。这就是为什么在C中用stdin、stdout、stderr就能找到键盘、显示器、显示器。

而在上面代码中关闭了1,myfile的文件描述符是1,所以在fwrite函数中用stdout还是写入到了myfile文件中。这就是为什么显示器上没有打印,而写入到了myfile文件的原因。而通过这段代码,我们现在应该要了解FILE*是什么了。

最后,fopen究竟做了什么?
1、给调用的用户申请struct FILE结构体变量,并返回地址(FILE*)
2、在底层通过open打开文件,并返回fd,把fd填充进FILE变量的fileno中。

缓冲区

有两段代码:

#include<stdio.h>
#include<unistd.h>
void A()

	printf("hello linux\\n");
	sleep(3);


void B()

	printf("hello linux");
	sleep(3);


int main()

	A();
	B();
	return 0;

其中A是先显示hello Linux,再等待3秒。
B是先等待3秒,再显示hello Linux。

把内容回显给显示器时,内容先被写入缓冲区,采用的是行缓存,当遇到\\n时就会刷新缓冲区,把内容写人显示器中,当缓冲区内容被写满时也会刷新缓冲区。

缓冲区有:

  1. 无缓冲
  2. 行缓冲:遇到\\n就会把缓冲区\\n之前的内容刷新出来,否则等缓冲区写满。效率和和用性做的平衡。
  3. 全缓冲:等缓冲区写满

(常见对显示器内容刷新,用的是行缓存,这样才能更快的看到我们的内容)

看代码

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

int main()

  printf("hello printf\\n");
  fprintf(stdout,"hello fprintf\\n"); 
  const char*mag2="hello write\\n";
  write(1,mag2,strlen(mag2));
  fork();
  return 0;

运行结果是:

hello printf
hello fprintf
hello write

但是我们对该进程进行输入重定向到文件中:./a.out > myfile

hello write
hello printf
hello fprintf
hello printf
hello fprintf


因为

当我们重定向后,文件描述符1已经不表示显示器了,而是我们的文件。这时候,缓冲区采用的是全缓存。
系统调用的IO接口是无缓冲,可以直接写入。
当缓冲区中存放了“hello printf\\n”和“hello fprintf\\n”时,创建了子进程,在return 0;之前进行了写时拷贝,所以最后打印了两次字符串。

这里面的缓冲区是C提供的,也是由FILE结构体进行维护。
缓冲区是在内存中的,在用户层。
缓冲区的数据刷新不是直接刷新到文件中,而是要经过内核区再写入到文件,这里有OS自己的刷新机制,这里不谈(我还没学到,哈哈)。

fclose和close

fclose:在关闭1之前,刷新了C中的缓冲区。内容可以被写入到文件内。

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

int main()

  close(1);
  int fd=open("myfile",O_WRONLY|O_CREAT,0666);
  char *arr="hello linux\\n";
  fprintf(stdout,arr);
  fclose(stdout);
  return 0;


close:由于采用了全缓,当close(1)时,系统调用的看不见C的缓冲区,没有刷新缓冲区就关掉了,故没有写入到文件中。

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

int main()

  close(1);
  int fd=open("myfile",O_WRONLY|O_CREAT,0666);
  char *arr="hello linux\\n";
  fprintf(stdout,arr);
  //fflush(stdout);//可以在调用close之前,先刷新缓冲区。
  fclose(stdout);
  return 0;


调用fclose是先调用ffiush,再调用close。

✔dup2系统调用

在上面的重定向中,我们要先close(1),再打开文件,这样好繁琐。我们有一个更简单的方法。

int dup2(int oldfd, int newfd);
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>

int main()

  int fd=open("myfile",O_WRONLY|O_CREAT,0666);  
  char *arr="hello linux\\n"; 
  close(1);
  dup2(fd,1);  
  printf("%s",arr);
  return 0;

✔理解文件系统

文件系统是Linux的一个重要部分,在Linux中玩了有一段时间,我一直有一个困惑,文件是怎么创建出来的?通过学习,慢慢的我自己有了一点了解。

文件=文件的属性+文件的内容,我们在查看文件大小时,显示的是文件内容的大小,其属性信息并没有算在里面,这说明了,文件的属性和文件的内容是分离存储的,在磁盘上。文件属性叫做元信息。

我们先简单了解磁盘:
磁盘有扇区、磁道、柱面、磁头……
文件的写入到磁盘中,会对磁盘寻址,其中会对柱头、磁道、扇面来寻找要写入的内容的地方。

假设磁盘的大小为500GB,对这么大的空间进行管理,系统采用了分区(就像中国也有省,市,县一样)

inode

inode是任何一个文件属性的集合,Linux中几乎每一个文件都有一个inode编号。

文件的元信息就是保存在inode中的,inode是一个结构体。

上图为磁盘文件的系统图(内核内存映射肯定有所不同),磁盘是一个典型的块设备,磁盘的分区被划分为一个个block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。而上图中启动块(Boot Block)的大小是确定的。

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。如政府管理各区的例子
  • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
  • GDT,Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的可以在了解一下
  • 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用
  • inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
  • inode Table:存放文件属性 如 文件大小,所有者,最近修改时间等
  • 数据区(Data blocks):存放文件内容

数据区中是一个一个的块,每个块的大小是4KB,用来存放数据。(存在多级索引,我还没学,就不讲了)

inode结构体中有一个数组(int block[12])记录块(Data blockse)的位置。
一个普通文件的创建。

先要去inode位图中找到未被使用的inode,并申请下来把文件的属性记录其中,如果要对该文件写入内容,则系统会根据内容的大小去块位图中申请所需要的空闲块,并写入内容。内核在inode上的磁盘分布区记录了上述块列表。之后,内核会把该文件的inode编号和文件名添加到所在目录文件中。该文件的inode编号和该文件的文件名对应起来。

目录的创建

目录也是文件,也有自己的inode编号。目录在创建的过程中和上面普通文件的创建有点类似,不同的是,目录文件的内容是存放目录下的文件名和inode指针,使这些文件名和inode指针一一对应起来。

ls 命令:

ls -l 命令

这也可以看出,目录和文件之前的联系。

文件的删除

文件的删除并没有那么复杂,只要把对应inode的位图中的数据改掉(把1置成0),对应块位图数据也修改掉(把1置成0)。这也就是为什么删除的文件可以恢复过来的原因,只要把位图再置回来。

创建一个新文件主要有一下4个操作:

  1. 存储属性
    内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。
  2. 存储数据
    该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。
  3. 记录分配情况
    文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466 abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。

硬链接

ln 文件名 要创建的文件名


这两个文件的inode号相同,说明myfile-s不是一个独立的文件,只是在目录的数据中添加了一个新文件名,该文件名对应的ionde和myfile相同。

硬连接数

硬连接数的数量是,有多少个文件对应的inode编号相同。
myfile文件和myfile-s文件的inode编号相同,所以硬连接数位2。

想要释放这个文件对应磁盘空间释放,要把硬连接数变成0。删除一个相同inode编号的文件,该硬连接数-1。

也就是说:

  • abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode
    263466 的硬连接数为2。

  • 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬连接数-1,如果为0,则将对应的磁盘释放。

软链接

ln -s 文件名 要创建的文件名


硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件。

文件的三个时间

用stat 文件名可以查看

  • Access 最后访问时间
    Modify 文件内容最后修改时间
    Change 属性最后修改时间

以上是关于linux入门---三个操作系统调用接口的主要内容,如果未能解决你的问题,请参考以下文章

Linux入门基础IO

linux基础入门

我的Linux,我做主!rpm包管理器/yum前端工具/编译安装从入门到精通

我的Linux,我做主!rpm包管理器/yum前端工具/编译安装从入门到精通

linux系统构成(基本操作)及计算机组成原理

Linux 系统调用