首页 > 技术文章 > IO学习笔记2

Zs-book1 2021-04-21 19:51 原文

1.2 用户态和内核态

操作系统在运行过程中,cpu有两种状态,用户态和内核态。

用户态:运行所有的用户程序。

内核态:操作系统运行在内核态。内核态主要负责系统调度以及与硬件的交互。

用户态和内核态的区别

  • 内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;

  • 当程序运行在0级特权级上时,就可以称之为运行在内核态。

  • 运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。

  • 这两种状态的主要差别是

    • 处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的

    • 处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。

用户态和内核态之间的切换

通常来说会有三种情况导致cpu状态切换

  1. 系统调用: 用户程序通过系统调用申请使用操作系统提供的服务完成工作。比如java中的创建线程,就是通过操作系统的fork()操作创建线程的。而fork()操作最终实际是通过操作系统来创建线程的。这里就存在一个用户态到内核态的切换,主要是通过 80中断来完成的。
  2. 异常: 当cpu在用户态执行程序时,发生了某些不可知的异常。这时就会从用户态切换到处理此异常的内核相关程序中,也就切换到了内核态。
  3. 外围设备中断: 当外围设备完成用户请求时,就会向cpu发出相应的中断信号。这时cpu就会暂停执行下一条将要执行的指令,转而去处理与中断信号对应的处理程序。

1.3 fd--文件描述符

Linux系统中,每一个进程都对应着三个文件描述符:

0: 输入
1: 输出
2: 报错

下面我们来验证上面的结论:

lsof命令

lsof命令可以现实进程打开了哪儿些文件。

[root@node01 fd]# lsof -p $$
COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF     NODE NAME
bash    18572 root  cwd    DIR    0,3         0   118863 /proc/18572/fd
bash    18572 root  rtd    DIR  253,0       224       64 /
bash    18572 root  txt    REG  253,0    964608 50332791 /usr/bin/bash
bash    18572 root  mem    REG  253,0 106075056   258036 /usr/lib/locale/locale-archive
bash    18572 root  mem    REG  253,0     61624   249276 /usr/lib64/libnss_files-2.17.so
bash    18572 root  mem    REG  253,0   2151672   249258 /usr/lib64/libc-2.17.so
bash    18572 root  mem    REG  253,0     19288   249264 /usr/lib64/libdl-2.17.so
bash    18572 root  mem    REG  253,0    174576   258079 /usr/lib64/libtinfo.so.5.9
bash    18572 root  mem    REG  253,0    163400   249251 /usr/lib64/ld-2.17.so
bash    18572 root  mem    REG  253,0     26254   258035 /usr/lib64/gconv/gconv-modules.cache
bash    18572 root    0u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    1u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    2u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root  255u   CHR  136,0       0t0        3 /dev/pts/0

上面展示信息中每一列分别表示如下信息:

  1. COMMAND:命令
  2. PID:进程号
  3. USER:打开进程的用户
  4. FD:文件描述符
  5. TYPE:文件类型
  6. DEVICE:设备号
  7. SIZE/OFF:文件大小/偏移量
  8. NODE:文件的Inode
  9. NAME:文件名

抛开前三列,从第四列开始看:

  • cwd:current work dir当前工作目录

  • rtd: root根目录在哪儿

  • text:当前进程的解释程序文本

  • mem:当前进程占用的内存资源

  • 然后下面就是0u、1u、2u三个文件描述符,u表示这个文件可以读也可以写。除了使用lsof可以看到这三个文件描述符。还可以通过下面方式来看这三个文件描述符:

    [root@node01 pts]# cd /proc/$$/fd
    [root@node01 fd]# ll
    总用量 0
    lrwx------. 1 root root 64 4月  19 16:25 0 -> /dev/pts/0
    lrwx------. 1 root root 64 4月  19 16:25 1 -> /dev/pts/0
    lrwx------. 1 root root 64 4月  19 16:25 2 -> /dev/pts/0
    lrwx------. 1 root root 64 4月  19 16:29 255 -> /dev/pts/0
    

每一个进程都会有这三个文件描述符。这三个文件描述符对应的文件就是字符类型的:

[root@node01 fd]# cd /dev/pts/
[root@node01 pts]# ll -i
总用量 0
3 crw--w----. 1 root tty  136, 0 4月  19 16:51 0

因为文件描述符0、1、2最终都重定向到了/dev/pts/0这个文件,因此直接查看这个文件信息,可以看到Inode号为3,文件类型为c。字符型文件就代表着我们的输入输出设备。键盘,显示器等。

[root@node01 fd]# cd /home/zhaoshuai/
[root@node01 zhaoshuai]# touch test.txt
[root@node01 zhaoshuai]# vi test.txt 
hjkjkhjbhgjhjkh
jhjkbjkkj
jkbjbjhbj
bjnbjhjbhj jh
jbjbhjjgjhbh
njbjhbhjhhghnk
jknjknk
jbjkkb
jbkb
jkbhjkbjh
jkbkjb
jkbkjbkjjk
jbkbjkbj
jkbkbknjk

手动创建一个文件,然后随表输入一些数据。使用当前进程读取这个文件内容:

[root@node01 zhaoshuai]# echo $$
18572
[root@node01 zhaoshuai]# exec 8< test.txt 

创建一个名为8的文件描述符,用来读区test.txt的数据。

[root@node01 zhaoshuai]# lsof -p 18572
COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF     NODE NAME
bash    18572 root  cwd    DIR  253,0        75 16781483 /home/zhaoshuai
bash    18572 root  rtd    DIR  253,0       224       64 /
bash    18572 root  txt    REG  253,0    964608 50332791 /usr/bin/bash
bash    18572 root  mem    REG  253,0 106075056   258036 /usr/lib/locale/locale-archive
bash    18572 root  mem    REG  253,0     61624   249276 /usr/lib64/libnss_files-2.17.so
bash    18572 root  mem    REG  253,0   2151672   249258 /usr/lib64/libc-2.17.so
bash    18572 root  mem    REG  253,0     19288   249264 /usr/lib64/libdl-2.17.so
bash    18572 root  mem    REG  253,0    174576   258079 /usr/lib64/libtinfo.so.5.9
bash    18572 root  mem    REG  253,0    163400   249251 /usr/lib64/ld-2.17.so
bash    18572 root  mem    REG  253,0    123267 50332815 /usr/share/locale/zh_CN/LC_MESSAGES/bash.mo
bash    18572 root  mem    REG  253,0     26254   258035 /usr/lib64/gconv/gconv-modules.cache
bash    18572 root    0u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    1u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    2u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    8r   REG  253,0       145 16781524 /home/zhaoshuai/test.txt
bash    18572 root  255u   CHR  136,0       0t0        3 /dev/pts/0
[root@node01 zhaoshuai]# cd /proc/$$/fd
[root@node01 fd]# ll
总用量 0
lrwx------. 1 root root 64 4月  19 16:25 0 -> /dev/pts/0
lrwx------. 1 root root 64 4月  19 16:25 1 -> /dev/pts/0
lrwx------. 1 root root 64 4月  19 16:25 2 -> /dev/pts/0
lrwx------. 1 root root 64 4月  19 16:29 255 -> /dev/pts/0
lr-x------. 1 root root 64 4月  19 17:00 8 -> /home/zhaoshuai/test.txt

可以看到就有了一个8的文件描述符,描述符为8r,r表示读取,然后可以使用cat命令查看8的内容。

查看如下命令:

[root@node01 fd]# read a 0<& 8

read表示读取数据,然后赋给变量a,默认的文件描述符为0,重定向到8,也就是说读区8的内容赋给变量a。

[root@node01 fd]# lsof -p $$
COMMAND   PID USER   FD   TYPE DEVICE  SIZE/OFF     NODE NAME
bash    18572 root  cwd    DIR    0,3         0   118863 /proc/18572/fd
bash    18572 root  rtd    DIR  253,0       224       64 /
bash    18572 root  txt    REG  253,0    964608 50332791 /usr/bin/bash
bash    18572 root  mem    REG  253,0 106075056   258036 /usr/lib/locale/locale-archive
bash    18572 root  mem    REG  253,0     61624   249276 /usr/lib64/libnss_files-2.17.so
bash    18572 root  mem    REG  253,0   2151672   249258 /usr/lib64/libc-2.17.so
bash    18572 root  mem    REG  253,0     19288   249264 /usr/lib64/libdl-2.17.so
bash    18572 root  mem    REG  253,0    174576   258079 /usr/lib64/libtinfo.so.5.9
bash    18572 root  mem    REG  253,0    163400   249251 /usr/lib64/ld-2.17.so
bash    18572 root  mem    REG  253,0    123267 50332815 /usr/share/locale/zh_CN/LC_MESSAGES/bash.mo
bash    18572 root  mem    REG  253,0     26254   258035 /usr/lib64/gconv/gconv-modules.cache
bash    18572 root    0u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    1u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    2u   CHR  136,0       0t0        3 /dev/pts/0
bash    18572 root    8r   REG  253,0       145 16781524 /home/zhaoshuai/test.txt
bash    18572 root  255u   CHR  136,0       0t0        3 /dev/pts/0

可以看到8的偏移量到了145。

此时再次打开一个窗口,然后再执行exec 6< test.txt,会发现两个进程打开了文件test.txt,两个进程都有自己的文件描述符,每个文件描述符维护自己的偏移量。

重定向

重定向,不是命令,而是一种机制。

在linux中,每一个文件都有输入、输出和报错文件,分别是0、1、2。输入输出都是流的形式,使用>表示输出,<表示输入。ll命令也是一个进程,他也会有输入和输出。我们平时使用时:

[root@node01 zhaoshuai]# ll ./
总用量 5
---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   24 4月  20 09:54 image
drwxr-xr-x. 2 root root   21 4月  19 11:01 link
drwxr-xr-x. 2 root root   22 4月  19 11:34 pipline
drwxr-xr-x. 3 root root 1024 4月  20 09:56 test
-rw-r--r--. 1 root root  145 4月  19 16:55 test.txt

ll表示启动脚本的命令,./是参数。其实启动ll后会创建0、1、2三个文件,而且默认输出流1重定向到了当前窗口展示目录。可以通过手动指定输出重定向:

[root@node01 zhaoshuai]# ll ./ 1>  ~/aaa.txt
[root@node01 zhaoshuai]# 

这样页面就没有输出了,因为bash再解释执行时,会发现ll表示命令,./是参数,而后面bash看到1>就会拦截,表示输出重定向到:~/aaa.txt,这样输出就被写入了文件中,查看文件:

[root@node01 zhaoshuai]# cd
[root@node01 ~]# cat aaa.txt 
总用量 5
---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   24 4月  20 09:54 image
drwxr-xr-x. 2 root root   21 4月  19 11:01 link
drwxr-xr-x. 2 root root   22 4月  19 11:34 pipline
drwxr-xr-x. 3 root root 1024 4月  20 09:56 test
-rw-r--r--. 1 root root  145 4月  19 16:55 test.txt

可以看到ll的输出信息。

重定向操作符>和文件描述符之间不能有空格,文件描述符必须紧跟着重定向操作符。如:0<1>。如果有空格,会认为0这个文件描述符是前面命令的参数。

如果右边是一个文件描述符,那么需要加一个&,如:1> aaa.txt 2>& 1

推荐阅读