zoukankan      html  css  js  c++  java
  • IO学习笔记

    一、IO前置知识--Linux系统

    根据冯诺依曼计算机结构,计算机的组成为:运算器、控制器、存储器、输入输出设备。

    在现代计算机中,运算器和控制器集成为了cpu,存储器按照功能可以拆分为内存和磁盘。而输入输出设备也就是键盘、显示器等,统称为IO。

    而Linux系统其实也是一个普通的程序,它运行在内存中。但是因为其特殊性,因此它在内存中存放的位置我们称之为kernel,即内核。而内存的其他位置可以用来运行应用程序。因此我们可以将内存分为内核空间和应用空间

    应用程序运行在kernel以外的空间上,应用程序默认整块内存都是可以使用的。

    kernel中,运行Linux系统程序,系统程序使用虚拟文件树(VFS)来管理应用程序。既然是文件,那么必然会伴随着IO的存在,因此kernel中还是用了pageCache来提升IO的效率。同时每一个文件都会有一个Inode标记文件唯一存在。

    1.1 Linux系统--虚拟文件系统(VFS)

    Linux是一个虚拟文件系统,在Linux中一切皆文件。

    1.1.1 首先我们来了解一下Linux中的文件描述

    -rw-r--r--. 1 root root 0 Apr 15 13:41 aaa.txt
    

    上面是一个名为aaa.txt的文件,文件的描述信息有-rw-r--r--. 1 root root 0 4月 15 13:41。下面解析此描述信息:

    1. 开头的-就代表这个文件的类型。

    2. 后面的rw-r--r--代表的是此文件的权限。每三个为一组,可以分为如下三组:u g o

      • u:表示文件所属用户对此文件所拥有的权限。
      • g:表示文件所属用户所在组的其他用户对此文件所拥有的权限。
      • o: 表示当前系统中所有用户对此文件所拥有的权限。

      除了以上三个级别外,还有一个a的组表示以上三组的总和。上面三组权限信息,每一组又对应了三种权限信息:

      • r: 读
      • w: 写
      • x: 可执行

      三种权限对应的---位置为rwx从前到后。因此我们给一个文件赋其他的权限时可以写:

      [root@node01 zhaoshuai]# chmod u+x aaa.txt 
      [root@node01 zhaoshuai]# ll
      总用量 0
      -rwxr--r--. 1 root root 0 4月  15 13:41 aaa.txt
      

      表示为aaa.txt文件所属用户赋予可执行的权限。chmod修改文件的权限信息。+表示添加,对应的-表示去除,还有=表示重新定义权限信息。

      除了使用上面的方式外,我们还会使用chmod 777 aaa.txt来为文件修改权限信息。因为一组的权限信息rwx,每一位都是用二进制表示时,当rwx同时存在,那么也就是111,正好是7。因此100也就是4就对应的r,2就对应的是w,1对用的x。每一位相加所得得和就是当前组所拥有得权限,最大为7

    3. 后面第一个root表示此文件所属用户名。

    4. 后面第二个root表示此文件所属用户所在组的组名。

    5. 再后面0表示此文件的大小--字节数大小。当前是一个空文件,所以为0。

    6. 后面Apr 15 13:41表示此文件的创建时间。

    7. 最后就是此文件的文件名aaa.txt

    1.1.2 文件类型

    上面了解了每一项的意思后,我们再来看第一项文件类型

    linux中的文件主要分为以下几种类型

    • -: 普通文件

      -rw-r--r--. 1 root root 0 Apr  15 13:41 aaa.txt
      
    • d: 文件夹

      drwxr-xr-x. 2 root root 6 Apr  15 14:28 dir
      
    • l: 软链接/硬连接

      链接是一种文件共享的方式。可以将链接简单的理解成window系统中的快捷方式。

      首先我们创建一个文件:

      [root@node01 zhaoshuai]# mkdir link
      [root@node01 zhaoshuai]# cd link
      [root@node01 link]# echo "hello world" > aaa.txt
      [root@node01 link]# ll
      总用量 4
      -rw-r--r--. 1 root root 12 4月  15 15:17 aaa.txt
      

      然后使用stat命令查看aaa.txt文件的信息:

      [root@node01 link]# stat aaa.txt
        文件:"aaa.txt"
        大小:12        	块:8          IO 块:4096   普通文件
      设备:fd00h/64768d	Inode:16781506    硬链接:1
      权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
      环境:unconfined_u:object_r:home_root_t:s0
      最近访问:2021-04-15 15:17:44.775499928 +0800
      最近更改:2021-04-15 15:17:44.775499928 +0800
      最近改动:2021-04-15 15:17:44.775499928 +0800
      创建时间:-
      

      可以看到一个Inode的值,每个文件都会有一个Inode号。

      Inode是什么?

      在Linux中,文件存储在硬盘上,硬盘的最小存储单位叫做“扇区”,每个扇区存储512字节。

      但是操作系统读取文件时并不会按照扇区来读,因为这样效率太低。而是一次读取连续的多个扇区,多个扇区组成一个“块”。操作系统按块读取。块大小常见为4K,即8个扇区。

      文件中的数据存放在块中,但是必须还要有一个地方存储文件的元数据信息,比如:文件创建者,创建时间,大小等。这种存放元数据信息的区域就叫做Inode,中文名:“索引节点”。

      Inode也是占用磁盘空间的。因此在磁盘格式化时,会自动将磁盘分成两个区域,一个用来存放数据信息的。一个是用来存放inode元数据信息的。

      每个Inode节点的大小为128字节256字节,在格式化阶段,就给定Inode节点的总数,一般每1K2K就会设定一个Inode。例如:磁盘大小为1G,每个Inode节点大小为128字节,每1K生成一个Inode,

      那么最终Inode大小就为128M

      每一个文件都会分配一个Inode,当Inode用完时,就无法再创建新的文件。

      • 硬链接:在上面解释了每一个文件都会分配一个Inode号,这个Inode号指向一个磁盘块,Linux系统通过这个Inode号找到对应的磁盘块,然后加载文件,这个Inode号就像一个指针一样。然后我们为aaa创建一个硬连接:

        [root@node01 link]# ln aaa.txt bbb.txt
        [root@node01 link]# ls -li
        16781506 -rw-r--r--. 3 root root 12 4月  15 15:17 aaa.txt
        16781506 -rw-r--r--. 3 root root 12 4月  15 15:17 bbb.txt
        

        ln 源文件 输出文件: 通过ln创建链接

        ls -li参数i可以展示Inode号。

        通过上面参数可以看出,aaa.txtbbb.txt两个文件指向了同一个磁盘块,他们的Inode号都是一样的。因此当删除其中一个文件时,并不影响另一个文件的访问。

        [root@node01 link]# rm -rf aaa.txt 
        [root@node01 link]# ll
        -rw-r--r--. 1 root root 12 4月  15 15:17 bbb.txt
        [root@node01 link]# cat bbb.txt 
        hello world
        

        即使删掉原文件aaa.txt,也不影响bbb.txt文件的访问。

        在linux系统中,会为每一个Inode维护一个引用计数,只要还有一个引用指向这个磁盘块,文件就不会被删除。而Inode就是这个引用。

      • 软连接:我们再对bbb.txt文件创建一个软链接

        [root@node01 link]# ll
        总用量 4
        -rw-r--r--. 1 root root 12 4月  15 15:17 bbb.txt
        [root@node01 link]# ln -s bbb.txt ccc.txt
        [root@node01 link]# ll -i
        总用量 4
        16781506 -rw-r--r--. 1 root root 12 4月  15 15:17 bbb.txt
        16781507 lrwxrwxrwx. 1 root root  7 4月  19 10:58 ccc.txt -> bbb.txt
        

        可以看到,bbb.txtccc.txt文件的Inode号并不一样,这就说明这两个文件指向的区块是不一样的。ccc.txt的文件类型为l

        [root@node01 link]# cat bbb.txt 
        hello world
        [root@node01 link]# cat ccc.txt
        hello world
        [root@node01 link]# rm -rf bbb.txt 
        [root@node01 link]# cat ccc.txt 
        cat: ccc.txt: 没有那个文件或目录
        

        通过上面的操作,可以看出,当源文件存在时,软连接可以正常访问,但是当删除原文件后,访问软连接会报错:没有那个文件或目录。

        可以理解为软链接其实内部存放的源文件的地址,当访问软链接时,系统自动重定向到源文件的地址。

        在软链接文件的后面会有一个 > xxx的展示,而在linux系统中,>也就是重定向的标志。

    • b: 块设备。磁盘就属于一个块设备。

    • c: 字符设备。我们的键盘,网卡就是字符设备。

      [root@node01 link]# cd /dev
      [root@node01 dev]# ll
      ...
      crw--w----. 1 root tty       4,  51 4月  15 11:52 tty51
      crw--w----. 1 root tty       4,  52 4月  15 11:52 tty52
      crw--w----. 1 root tty       4,  53 4月  15 11:52 tty53
      ...
      
    • p: pipline管道

      父子进程

      [root@node01 ~]# echo $$
      18388
      [root@node01 ~]# bash
      [root@node01 ~]# echo $$
      18410
      

      打开一个shell窗口,当前进程id为18388,然后在此窗口中执行bash命令,再次打开一个窗口,也就是说在18388这个进程中再次开启了一个子进程,这个进程的id为18410,然后执行ps命令:

      [root@node01 ~]# ps -ef |grep 18388
      root      18388  18384  0 13:37 pts/0    00:00:00 -bash
      root      18410  18388  0 13:42 pts/0    00:00:00 bash
      root      18420  18410  0 13:44 pts/0    00:00:00 grep --color=auto 18388
      

      从上面可以看出,18410的父进程是18388。那么我们在18388进程中创建一个变量,在子进程中是否能够访问?

      [root@node01 ~]# exit
      exit
      [root@node01 ~]# echo $$
      18388
      [root@node01 ~]# a=100
      [root@node01 ~]# echo $a
      100
      [root@node01 ~]# bash
      [root@node01 ~]# echo $a
      
      [root@node01 ~]# 
      

      此时再打开一个子进程,是访问不到变量a的,因为进程间互相隔离。但是使用环境变量的方式可以获取到a,因为子进程会继承父进程的环境变量:

      [root@node01 ~]# exit
      exit
      [root@node01 ~]# export a=100
      [root@node01 ~]# echo $a
      100
      [root@node01 ~]# bash
      [root@node01 ~]# echo $$
      18454
      [root@node01 ~]# echo $a
      100
      

      管道

      管道是一种通信机制,通常用于进程间的通信(也可通过socket进行网络通信),它表现出来的形式将前面每一个进程的输出(stdout)直接作为下一个进程的输入(stdin)。管道命令使用|作为界定符号。

      例如:

      [root@node01 zhaoshuai]# ll
      总用量 0
      ---xr--r--. 1 root root  0 4月  15 13:41 aaa.txt
      drwxr-xr-x. 2 root root  6 4月  15 14:28 dir
      drwxr-xr-x. 2 root root 21 4月  19 11:01 link
      drwxr-xr-x. 2 root root 22 4月  19 11:34 pipline
      [root@node01 zhaoshuai]# ll |grep aaa
      ---xr--r--. 1 root root  0 4月  15 13:41 aaa.txt
      

      将左边的ll命令的输出,作为右边grep命令的输入,最终打印出过滤后信息。

      在linux中,可以在shell命令行执行代码块,如下:

      [root@node01 ~]# { echo "hello"; echo "world"; }
      hello
      world
      

      那么执行如下代码:

      [root@node01 ~]# a=1
      [root@node01 ~]# echo $a
      1
      [root@node01 ~]# { a=9; echo "hello"; }|cat
      hello
      [root@node01 ~]# echo $a
      

      此时应该输出1还是9?答案是1;因为通过|创建的管道两边都是子进程。

      我们打开的shell窗口其实是执行bash命令打开的一个窗口,bash命令其实是一个解释执行的脚本。因此在执行{ a=9; echo "hello" }| cat命令时,会查看命令,当看到|符号时,会对管道左边的内容打开一个子进程执行,对右边的内容打开另一个子进程执行。

      验证上面的内容:

      [root@node01 ~]# echo $$
      18454
      [root@node01 ~]# echo $BASHPID|cat
      18487
      

      如果使用$$命令,会发现不生效,因为$$的优先级大于管道,因此bash在解释执行时,会先执行echo $$,然后再创建管道。

      验证管道pipline:

      [root@node01 ~]# echo $$
      18454
      [root@node01 ~]# { echo $BASHPID; read x; } | { cat; read y ; echo $BASHPID; }
      18491
      

      上面shell命令,首先打印了当前父进程的ID,然后通过管道开启两个子进程,左边子进程输出进程id,然后通过read阻塞左边进程,此时再次打开另一个shell窗口,然后通过ps命令查看子进程:

      [root@node01 ~]# ps -ef |grep 18454
      root      18454  18388  0 13:52 pts/0    00:00:00 bash
      root      18491  18454  0 14:13 pts/0    00:00:00 bash
      root      18492  18454  0 14:13 pts/0    00:00:00 bash
      root      18512  18495  0 14:15 pts/1    00:00:00 grep --color=auto 18454
      

      可以看到,父进程18454创建了两个子进程,分别是18491和18492。然后查看18491进程的文件描述符:

      [root@node01 ~]# cd /proc/18491/fd
      [root@node01 fd]# ll
      总用量 0
      lrwx------. 1 root root 64 4月  19 14:17 0 -> /dev/pts/0
      l-wx------. 1 root root 64 4月  19 14:17 1 -> pipe:[109901]
      lrwx------. 1 root root 64 4月  19 14:15 2 -> /dev/pts/0
      lrwx------. 1 root root 64 4月  19 14:17 255 -> /dev/pts/0
      

      可以看到文件描述符1后面内容为:pipe:[109901]指向了一个管道。通过上面内容,证明了管道的存在。

      然后回到阻塞界面,随便输入内容结束阻塞。

      [root@node01 ~]# { echo $BASHPID; read x; } | { cat; read y ; echo $BASHPID; }
      18491
      1
      18492
      

      可以看到输出左边子进程id为18491,右边子进程id为18492。验证了我们上面说的管道两边两个子进程。

    • s: socket网络通道

      通过socket获取百度网页:

      [root@node01 fd]# exec 8<> /dev/tcp/www.baidu.com/80
      [root@node01 fd]# ll
      总用量 0
      lrwx------. 1 root root 64 4月  19 10:45 0 -> /dev/pts/0
      lrwx------. 1 root root 64 4月  19 10:45 1 -> /dev/pts/0
      lrwx------. 1 root root 64 4月  19 10:45 2 -> /dev/pts/0
      lrwx------. 1 root root 64 4月  19 11:38 255 -> /dev/pts/0
      lrwx------. 1 root root 64 4月  19 11:47 8 -> socket:[97361]
      

      可以看到文件描述符8指向了一个socket类型的文件。

    • [eventpoll]: 多路复用

  • 相关阅读:
    Appium Android 元素定位方法 原生+H5
    Eclipse下Python的MySQLdb的安装以及相关问题
    Python模块包中__init__.py文件的作用
    如何调用另一个python文件中的代码
    Python单元测试框架unittest使用方法讲解
    python利用unittest进行测试用例执行的几种方式
    python随机生成手机号码
    eclipse 安装python后pydev不出现
    Appium 如何模拟返回按键
    python分布式进程
  • 原文地址:https://www.cnblogs.com/Zs-book1/p/14676662.html
Copyright © 2011-2022 走看看