Linux操作系统基础综述笔记

一、Linux内核基础白话

1. 在操作系统中,输入设备驱动其实就像客户对接员。有时候新插上一个鼠标的时候,会弹出一个通知安装驱动,这就是操作系统这家外包公司在配备对接人员。当客户告诉对接员需求的时候,对于操作系统来说,输入设备会发送一个中断。这个概念很好理解,客户肯定希望外包公司把正在做的事情都停下来服务它。所以,这个时候客户发送的需求就被称为中断事件(Interrupt Event)。

显卡驱动在操作系统中称为输出设备驱动,也就像是交付人员。有了客户对接员和交付人员,外包公司就可以处理用户“在桌面上点击QQ图标”的事件了。首先,鼠标双击会触发一个中断,相当于客户告知客户对接员“有了新需求需要处理”。操作系统事先把处理这种问题的方法教给客户对接员,就是调用中断处理函数。操作系统发现双击的是一个图标,就准备运行QQ。

对QQ这个程序来说,它能做哪些事情,每个事情怎么做和顺序,都已经作为程序逻辑写在里面,并且编译成为二进制了,这个程序就相当于项目执行计划书。电脑上各程序都以二进制文件的形式保存在硬盘上,而硬盘要按照规定格式化成为文件系统,才能存放这些程序。文件系统需要一个系统进行统一管理,称为文件管理子系统(File Management Subsystem)。当操作系统拿到QQ的二进制执行文件时,就可以运行这个文件了。QQ的二进制文件是静态的,称为程序(Program),而运行起来的QQ,是不断进行的,称为进程(Process)。

2. 项目立项过程中,需要用到公司各种资源,这里有个两难的权衡,一方面资源是有限的,甚至是涉及机密的,不能由项目组滥取滥用;另一方面就是效率,保证项目申请资源的时候只跑一次,这样才能比较高效。为了平衡这一点,一方面涉及核心权限的资源,还是应该被公司严格把控,审批了才能用;另外一方面,为了提高效率,最好有个统一的办事大厅,明文列出提供哪些服务,谁需要可以来申请,然后就会有回应。

在操作系统中也有同样的问题,例如多个进程都要往打印机上打印文件,如果随便乱打印进程,就会出现同样一张纸,第一行是A进程输出的文字,第二行是B进程输出的文字,全乱套了。所以,打印机的直接操作是放在操作系统内核里面的,进程不能随便操作。但是操作系统也提供一个办事大厅,也就是系统调用(System Call)。系统调用列出来提供哪些接口可以调用,进程有需要的时候就可以去调用。项目立项是办事大厅提供的关键服务之一,同样任何一个程序要想运行起来,就需要调用系统调用,创建进程

3. 就像项目经理管理调度开发人员在各项目组中进行工作,在操作系统中,进程的执行也需要分配CPU进行执行,也就是按照程序里面的二进制代码一行一行地执行。于是,为了管理进程还需要一个进程管理子系统(Process Management Subsystem)。如果运行的进程很多,则一个CPU会并发运行多个进程,也就需要CPU的调度能力了。

每个项目都有自己的私密资料,这些资料不能被其他项目组看到。如果不同项目的办公空间不隔离,一方面,项目的私密性不能得到保证,A项目的细节B项目也能看到;另一方面项目之间会相互干扰,A项目组的人刚在白板上画了一个架构图,出去上个厕所,结果 B 项目组的人就给擦了。如果把不同的项目组分配到不同的会议室,就解决了这个问题。当然会议室是有限的,需要需要一个会议室管理系统。在操作系统中,不同的进程有不同的内存空间,但是整个电脑内存有限,所以需要统一的管理和分配,这就需要内存管理子系统(Memory Management Subsystem)。

4. QQ启动之后,有一部分代码会在显示器上画一个对话框,并且将键盘的焦点放在了输入框里面。CPU根据这些指令,就会告知显卡驱动程序,将这个对话框画出来。当用户通过键盘打字的时候,键盘也是输入设备,也会触发中断,通知相应的输入设备驱动程序。假设用户输入了一个“a”,QQ进程是不能直接发送网络包的,需要调用系统调用,内核使用网卡驱动程序进行发送。这就像客户对接员接到一个需求,但是这个需求需要和其他公司沟通,这就需要依靠公司的对外合作部,对外合作部在办事大厅有专门的窗口。各子系统的比喻和关系如下所示:

二、常用Linux命令

5. 在Linux中,以管理员账户身份在命令行里使用useradd命令就能创建一个子用户:

 useradd username

执行这个命令,一个用户就被创建了。它不会弹出让输入密码之类的页面,就会直接返回了。因为接下来需要自己调用passwd username设置密码再进行登录。在Windows里设置用户的时候,用户有一个“组”的概念,比如“Adminsitrator组”“Guests组”“Power User组”等。同样Linux里也是分组的。前面创建用户的时候,没有说加入哪个组,默认就会创建一个同名的组

通过命令创建的用户,是放在/etc/passwd文件里的,这是一个文本文件。可以通过cat命令,将里面的内容输出在命令行上。组的信息放在/etc/group文件中,如下所示:

# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
......
username:x:1000:1000::/home/cliu8:/bin/bash


# cat /etc/group
root:x:0:
......
username:x:1000:

在/etc/passwd文件里,可以看到root 户和刚创建的username用户。x的地方是密码,密码当然不会放在这里,不然谁都知道了。接下来是用户ID和组ID,这和/etc/group里面就对应上了。/root和/home/username分别是root用户和username用户的主目录,主目录是用户登录进去后默认的路径

/bin/bash的位置是用于配置登录后的默认交互命令行的,不像Windows登录进去是界面,其实就是explorer.exe。而Linux登录后的交互命令行是一个解析脚本的程序,这里配置的是/bin/bash。

6. cd命令就是 change directory,就是切换目录;cd .表示切换到当前目录;cd ..表示切换到上一级目录;在Windows中使用dir命令,可以列出当前目录下的文件,Linux列出当前目录下的文件用的是ls,意思是list。常用的是ls -l,也就是用列表的方式列出文件,如下所示:

# ls -l
drwxr-xr-x 6 root root    4096 Oct 20  2017 apt
-rw-r--r-- 1 root root     211 Oct 20  2017 hosts

其中第一个字段的第一个字符是文件类型,如果是“-”表示普通文件;如果是d就表示目录。第一个字段剩下的9个字符是模式,其实就是权限位(access permission bits),3个一组,每一组rwx表示“读(read)”“写(write)”“执行(execute)”。如果是字母,就说明有这个权限;如果是横线,就是没有这个权限。这三组分别表示文件所属的用户权限、文件所属的组权限以及其他用户的权限。例如上面的例子中,-rw-r–r--就可以翻译为,这是一个普通文件,对于所属用户,可读可写不能执行;对于所属的组,仅仅可读;对于其他用户,也是仅仅可读。如果想改变权限,可以使用命令chmod 711 hosts

第二个字段是硬链接(hard link)数目,第三个字段是所属用户,第四个字段是所属组。第五个字段是文件的大小,第六个字段是文件被修改的日期,最后是文件名。可以通过命令chown改变所属用户,chgrp改变所属组。在Linux中,没有双击安装这一说(如果无图形界面),因此想要安装还得需要命令。CentOS下使用rpm -i XXX_linux-x64_bin.rpm进行安装,Ubuntu下使用dpkg -i XXX_linux-x64_bin.deb,其中-i就是install的意思。

7. 在Linux下,凭借rpm -qadpkg -l就可以查看安装的软件列表,-q就是 query,a就是all,-l的意思就是list。如果真的去运行的话,会发现这个列表很长很长,很难找到安装的软件。如果知道要安装的软件包含某个关键词,可以用一个很好用的搜索工具 grep。例如rpm -qa | grep jdk,这个命令是将列出来的所有软件形成一个输出。| 是管道,用于连接两个程序,前面rpm -qa的输出就放进管道里面,然后作为grep的输入,grep将在里面进行搜索带关键词jdk的行,并且输出出来。

grep支持正则表达式,因此搜索的时候很灵活,再加上管道,这是一个很常用的模式。同理dpkg -l | grep jdk也是能够找到的。如果不知道关键词,可以使用rpm -qa | more和rpm -qa | less这两个命令,它们可以将很长的结果分页展示出来,这样就可以一个个来找了。more是分页后只能往后翻页,翻到最后一页自动结束返回命令行,less是往前往后都能翻页,需要输入q返回命令行,q就是 quit。如果要删除,可以用rpm -edpkg -r。-e就是erase,-r就是remove。

8. Linux也有自己的软件管家,CentOS下面是 yum,Ubuntu下面是apt-get。可以根据关键词搜索,例如搜索jdk可以使用yum search jdkapt-cache search jdk,可以搜索出很多很多可以安装的jdk版本。如果数目太多,可以通过管道grep、more、less来进行过滤。选中一个之后就可以进行安装了,可以用yum install java-11-openjdk.x86_64或apt install openjdk-9-jdk来进行安装。安装以后可以使用yum erase java-11-openjdk.x86_64apt purge openjdk-9-jdk来卸载JDK。

Linux允许配置从哪里下载这些软件,对于CentOS来说配置文件在/etc/yum.repos.d/CentOS-Base.repo里,如下所示:

[base]
name=CentOS-$releasever - Base - 163.com
baseurl=http://mirrors.163.com/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-7

对于Ubuntu来说,配置文件在/etc/apt/sources.list里,如下所示:

deb http://mirrors.163.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ xenial-backports main restricted universe multiverse

其实无论是先下载再安装,还是通过软件管家进行安装,都是下载一些文件,然后将这些文件放在某个路径下,然后在相应的配置文件中配置一下。例如在Windows里,最终会变成C:\Program Files下面的一个文件夹以及注册表里面的一些配置。对应 Linux里会放的更散一点。例如主执行文件会放在/usr/bin或者/usr/sbin下面,其他的库文件会放在/var下面,配置文件会放在/etc下面

9. 通过tar命令解压缩下载的压缩包之后,也需要配置环境变量,可以通过export命令来配置。如下所示:

export JAVA_HOME=/root/jdk-XXX_linux-x64
export PATH=$JAVA_HOME/bin:$PATH

export命令仅在当前命令行的会话中管用,一旦退出重新登录进来,就不管用了。在当前用户的默认工作目录,例如/root或者/home/username下面,有一个.bashrc文件,这个文件是以点开头的,默认看不到,需要ls -al才能看到,a就是all。每次登录的时候,这个文件都会运行,因而可以把该export命令放在这个文件里。当然也可以通过source .bashrc手动执行。要编辑.bashrc文件,可以使用文本编辑器vim。

10. Linux不是像Windows那样根据.exe后缀名来执行的。它的执行条件是这样的:只要文件有x执行权限,都能到文件所在的目录下,通过./filename运行这个程序。当然,如果放在PATH里设置的路径下面,就不用./了,直接输入文件名就可以运行了,Linux会自动找到路径。一般命令行被关闭了,该会话下的所有程序都会退出,为了持续后台运行一个程序,往往使用nohup命令,这个命令的意思是no hang up(不挂起),也就是说当前交互命令行退出的时候,程序还要在。当然这个时候,程序不能霸占交互命令行,而是应该在后台运行,因此最后再加一个&,就表示后台运行。

另外一个要处理的就是输出,原来什么都打印在交互命令行里,现在在后台运行了,输出到文件是最好的。最终命令的一般形式为nohup command >out.file 2>&1 &,这里“1”表示文件描述符1,表示标准输出,“2”表示文件描述符2,意思是标准错误输出,“2>&1”表示标准输出和错误输出合并了,合并到out.file里。

如果要关闭进程,假设启动的程序包含某个关键字,那就可以使用下面的命令:

ps -ef |grep 关键字  |awk '{print $2}'|xargs kill -9

其中ps -ef可以单独执行,列出所有正在运行的程序,awk工具可以很灵活地对文本进行处理,这里的awk '{print $2}'是指第二列的内容,是运行的程序ID。然后可以通过xargs传递给kill -9,也就是发给这个运行的程序一个信号,让它关闭。如果已经知道运行的程序ID,可以直接使用kill关闭运行的程序。

11. Linux也有相应的开机启动服务。例如MySQL就可以使用这种方式运行。例如在Ubuntu中可以通过apt install mysql-server的方式安装MySQL,然后通过命令systemctl start mysql启动MySQL,通过systemctl enable mysql设置开机启动。之所以成为服务并且能够开机启动,是因为在/lib/systemd/system目录下会创建一个XXX.service的配置文件,里面定义了如何启动、如何关闭。常用Linux命令如下所示:

三、常见系统调用

12. Linux中创建进程的系统调用叫fork。在 Linux 里,要创建一个新的进程,需要一个老的进程调用fork来实现,其中老的进程叫作父进程(Parent Process),新的进程叫作子进程(Child Process)。一个进程的运行是要有一个程序的,就像一个项目的执行,要有一个项目执行计划书。本来老的项目,按照项目计划书按部就班地来,项目执行到一半,突然接到命令,说要新启动一个项目需要涉及公司各个部门的工作,比如说,项目管理部门需要给这个项目组开好Jira和Wiki,会议室管理部要为这个项目分配会议室等等。

所以,现在有两种方式,一种是列一个清单,清单里面写明每个新项目组都要开哪些账号。但是,这样每次有项目,都要重新配置一遍新的Jira、Wiki,复杂得很。另一种方式就是程序员常用的方式,CTRL/C + CTRL/V。也就是说,如果想为新项目建立一套Jira,但是一个个填Jira里面的选项太麻烦,那就可以拷贝一个别人的,然后根据新项目的实际情况,将相应的配置改改。

Linux 就是这样想的。当父进程调用 fork 创建进程的时候,子进程将各个子系统为父进程创建的数据结构也全部拷贝了一份,甚至连程序代码也是拷贝过来的。按理说,如果不进行特殊的处理,父进程和子进程都按相同的程序代码进行下去,这样就没意义了,所以往往会这样处理:对于fork系统调用的返回值,如果当前进程是子进程,就返回0;如果当前进程是父进程,就返回子进程的进程号。这样首先在返回值这里就有了一个区分,然后通过if-else语句判断,如果是父进程,还接着做原来应该做的事情;如果是子进程,需要请求另一个系统调用execve来执行另一个程序,这个时候,子进程和父进程就彻底分道扬镳了,也即产生了一个分支(fork)了。

既然新进程都是父进程fork出来的,那到底谁是第一个呢?作为一个外包公司老板,有了新项目当然会分给手下做,但是当公司刚起步的时候没有下属,只好自己上了。先建立项目运行体系,等后面再做项目的时候,就都按这个来。对于操作系统也一样,启动的时候先创建一个所有用户进程的“祖宗进程”。有时候,父进程要关心子进程的运行情况,有个系统调用waitpid,父进程可以调用它,将子进程的进程号作为参数传给它,这样父进程就知道子进程运行完了没有,成功与否等。

13. 在操作系统中,每个进程都有自己的内存,互相之间不干扰,即有独立的进程内存空间。对于进程的内存空间来说,放程序代码的这部分称为代码段(Code Segment)。放进程运行中产生数据的这部分,称为数据段(Data Segment)。其中局部变量的部分,在当前函数执行的时候起作用,当进入另一个函数时,这个变量就释放了;也有动态分配的,会较长时间保存,指明才销毁的,这部分称为(Heap)。

只有进程要去使用部分内存的时候,才会使用内存管理的系统调用来登记,说自己马上就要用了,希望分配一部分内存给它,但是这还不代表真的就对应到了物理内存。只有真的写入数据时,发现没有对应物理内存,才会触发一个中断,现分配物理内存。而且尽管分配的逻辑虚拟内存地址是连续的,但是对应的物理内存地址并不一定连续,操作系统会维护内存地址分配的映射关系,通俗的理解如下所示:

在堆里面分配内存的系统调用有两种,即brkmmap当分配的内存数量比较小的时候,使用brk会和原来的堆的数据连在一起,这就像多分配两三个工位,在原来的区域旁边搬两把椅子就行了。当分配的内存数量比较大的时候,使用mmap会重新划分一块区域,也就是说当办公空间需要太多的时候,索性来个一整块。

14. 对于文件的操作,这六个系统调用是最重要的:(1)对于已经有的文件,可以使用open打开这个文件,close关闭这个文件;(2)可以使用creat创建文件;(3)打开文件以后,可以使用lseek跳到文件的某个位置;(4)读的系统调用是read,写是write

Linux里有一个特点,那就是一切皆文件。启动一个进程,需要一个程序文件,这是一个二进制文件。启动的时候,要加载一些配置文件,例如 yml、properties 等,这是文本文件;启动之后会打印一些日志,如果写到硬盘上,也是文本文件。但如果想把日志打印到交互控制台上,在命令行上打印出来,这其实也是一个文件,是标准输出stdout文件。这个进程的输出可以作为另一个进程的输入,这种方式称为管道,管道也是一个文件。进程可以通过网络和其他进程进行通信,建立的Socket,也是一个文件。进程需要访问外部设备,设备也是一个文件。文件都被存储在文件夹里面,其实文件夹也是一个文件。进程运行起来,要想看到进程运行的情况,会在/proc下面有对应的进程号,还是一系列文件

每个文件Linux都会分配一个文件描述符(File Descriptor),这是一个整数。有了这个文件描述符,就可以使用系统调用,查看或者干预进程运行的方方面面。所以说文件操作是贯穿始终的,这也是“一切皆文件”的优势,就是统一了操作的入口,提供了极大的便利

15. 在进程运行的过程中,如果遇到各种异常情况,此时系统就需要发送异常处理信号。经常遇到的信号有以下几种:(1)在执行一个程序的时候,在键盘输入“CTRL+C”,这就是中断的信号,正在执行的命令就会中止退出;(2)如果非法访问无权限的内存区域,例如跑到别人的会议室,可能会看到不该看的东西;(3)硬件故障,设备出了问题;(4)用户进程通过kill函数,将一个用户信号发送给另一个进程。

对于一些不严重的信号,可以忽略,但是像SIGKILL(用于终止一个进程的信号)和SIGSTOP(用于中止一个进程的信号)是不能忽略的,可以执行对于该信号的默认动作。每种信号都定义了默认的动作,例如硬件故障,默认终止;也可以提供信号处理函数,可以通过sigaction系统调用,注册一个信号处理函数。

16. 进程之间的沟通方式有多种:

(1)首先就是发个消息,不需要一段很长的数据,这种方式称为消息队列(Message Queue)。消息队列是在内核里的,可以通过msgget创建一个新的队列,msgsnd将消息发送到消息队列,而消息接收方可以使用msgrcv从队列中取消息。

(2)当两个进程需要交互的信息比较大时,可以使用共享内存的方式,也即两个项目组共享一个会议室(这样数据就不需要拷贝来拷贝去)。这时候,可以通过shmget创建一个共享内存块,通过shmat将共享内存映射到自己的内存空间,然后就可以读写了。

但是,两个进程共同访问一个共享内存里的数据,就会存在“竞争”的问题。如果大家同时修改同一块数据,就需要有一种方式,让不同的人能够排他地访问,这就是信号量的机制Semaphore。例如,对于只允许一个人访问的需求,可以将信号量设为1。当一个人要访问的时候,先调用sem_wait。如果这时候没有人访问,则占用这个信号量,就可以开始访问了。如果这个时候另一个人要访问,也会调用sem_wait。由于前一个人已经在访问了,所以后面这个人就必须等待上一个人访问完之后才能访问。当上一个人访问完毕后,会调用sem_post将信号量释放,于是下一个人等待结束,可以访问这个资源了。

17. 当两台Linux机器通过网络相互通信时,要遵循相同的网络协议,也即TCP/IP网络协议栈。网络服务是通过套接字Socket来提供服务的。在通信之前,双方都要建立一个Socket。可以通过Socket系统调用建立一个Socket。Socket也是一个文件,也有一个文件描述符,也可以通过读写函数进行通信。对于64位操作系统,找到unistd_64.h文件,里面对于系统调用的定义,就是下面这样:

#define __NR_restart_syscall    0
#define __NR_exit      1
#define __NR_fork      2
#define __NR_read      3
#define __NR_write      4
#define __NR_open      5
#define __NR_close      6
#define __NR_waitpid      7
#define __NR_creat      8
......

18. Glibc是Linux下使用的开源的标准C库,它是GNU发布的libc库。Glibc为程序员提供丰富的API,除了例如字符串处理、数学运算等用户态服务之外,最重要的是封装了操作系统提供的系统服务,即系统调用的封装。每个特定的系统调用对应了至少一个Glibc封装的库函数,比如系统提供的打开文件系统调用sys_open对应的是Glibc中的open函数。有时候Glibc一个单独的API可能调用多个系统调用,比如Glibc提供的printf函数就会调用如 sys_open、sys_mmap、sys_write、sys_close等等系统调用。也有时候多个API也可能只对应同一个系统调用,如 Glibc下实现的malloc、calloc、free等函数用来分配和释放内存,都利用了内核的sys_brk的系统调用。Linux的诸多系统调用如下所示:

 

发布了35 篇原创文章 · 获赞 104 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_33588730/article/details/104099774