1 Linux综述
将linux比作一个外包公司,则有如下图的运作:
操作系统内核体系结构图:
简要叙述
鼠标和键盘是计算机的输入设备。大部分的普通用户想要告诉计算机应该做什么,都是通过这两
个设备。
例如,用户移动了一下鼠标,鼠标就会通过鼠标线给电脑发消息,告知电脑,鼠标向某个方向移动了多少距离;
对于操作系统来讲,输入设备工作时会发送一个中断,此为中断事件,在操作系统里面调用中断处理函数
屏幕,也就是显示器,是计算机的输出设备,将计算机处理用户请求后的结果展现给客户,要不
然用户无法知道自己的请求是不是到达并且执行了;
显示器上面显示的东西是由显卡控制的。
无论是显示器还是显卡,这里都有个“坐标”的概念,也就是说,什么图像在哪个坐标,都是定义好了才画上去的。本来在某个坐标画了一个鼠标箭头,当接到鼠标移动的事件之后,应该按相同的方向,按照一定的比例(鼠标灵敏度),在屏幕的某个坐标再画一个鼠标箭头。
显卡会有显卡驱动,在操作系统中称为输出设备驱动,用来交付给显卡、输出设备
电脑上的程序有很多,它们都以二进制文件的形式保存在硬盘上。
硬盘是个物理设备,要按照规定格式化成为文件系统,才能存放这些程序。
文件系统需要一个系统进行统一管理,称为文件管理子系统(File Management Subsystem)。
当操作系统拿到 QQ(或者其他程序) 的二进制执行文件的时候,就可以运行这个文件了。
QQ 的二进制文件是静态的,称为程序(Program);
而运行起来的 QQ,是不断进行的,称为进程(Process)。
系统调用子系统(System Call)能列出来提供哪些接口可以调用,进程有需要的时候就可以去调用。
任何一个程序要想运行起来,就需要调用系统调用,创建进程;
例子:如发送网络包功能。(网络子系统)
当用户输入完毕之后,回车一下,会通过键盘驱动程序告诉操作系统,操作系统会找到
QQ,QQ 会将用户的输入发送到网络上;
QQ 进程是不能直接发送网络包的,需要调用系统调用,内核使用网卡驱动程序进行发送。
在操作系统中,进程的执行需要分配 CPU 进行执行,也就是按照程序里面的二进制代码一行
一行地执行;
于是,为了管理进程,我们还需要一个进程管理子系统(Process ManagementSubsystem);
如果运行的进程很多,则一个 CPU 会并发运行多个进程,也就需要 CPU 的调度能力了。
在操作系统中,不同的进程有不同的内存空间,但是整个电脑内存就这么点儿,所以需要统一的
管理和分配,这就需要内存管理子系统(Memory Management Subsystem)。
2 Linux命令
passwd:修改密码
useradd pdc:创建pdc用户,默认创建一个同名组并加入
passwd pdc:给pdc用户设置密码
-h:参数,help
用户放在/etc/passwd文件里,可以使用cat /etc/passwd查看;
组信息放在/etc/group
cd . :切换到当前目录
cd …:切换到上一级目录;
ls -l:用列表的方式列出当前目录下的文件
rpm -qa、dpkg -l:查看安装的软件列表
rpm -qa | grep jdk:将带有jdk的形成一个输出;
| 是管道,用于连接两个程序,前面 rpm -qa 的输出就放进管道里面,然后作为 grep 的输入,grep 将在里面进行搜索带关键词 jdk 的行,并且输出出来。
grep 支持正则表达式,因此搜索的时候很灵活,再加上管道,这是一个很常用的模式
rpm -e、dpkg -r:删除软件
yum search jdk、apt-cache search jdk:使用类似软件管家的功能,搜索jdk;
如果数目太多,可以通过管道 grep、more、less 来进行过滤;
yum install java-11-openjdk.x86_64、aptgetinstall openjdk-9-jdk:选择其中一个进行安装
yum erase java-11-openjdk.x86_64、apt-get purgeopenjdk-9-jdk:卸载
tar xvzf jdk-XXX_linux-x64_bin.tar.gz:解压缩
export:配置环境变量;仅在当前命令行的会话中管用
vim .bashrc:可以永久
./filename:运行程序
为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 关闭运行的程序。
关机:shutdown -h now;reboot就是重启
strace:用来跟踪进程执行时系统调用和所接收的信号
- 关于文件的信息描述
如:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VHR4yda6-1574210703780)(…/…/markdownPicture/assets/1567322456584.png)]
其中第一个字段的第一个字符是文件类型。
如果是“-”,表示普通文件;如果是 d,就表示目录。当然还有很多种文件类型
第一个字段剩下的 9 个字符是模式,其实就是权限位(access permission bits);
3 个一组;
每一组 rwx 表示“读(read)”“写(write)”“执行(execute)”;
如果是字母,就说明有这个权限;如果是横线,就是没有这个权限。
这三组分别表示文件所属的用户权限、文件所属的组权限以及其他用户的权限;
例如,上面的例子中,-rw-r–r-- 就可以翻译为:这是一个普通文件,对于所属用户,可读可写不能执行;对于所
属的组,仅仅可读;对于其他用户,也是仅仅可读。
如果想改变权限,可以使用命令 chmod 711 hosts。
第二个字段是硬链接(hard link)数目
第三个字段是所属用户,第四个字段是所属组。第五个字段是文件的大小,第六个字段是文件被
修改的日期,最后是文件名;
可以通过命令chown改变所属用户,chgrp改变所属组。
- vim
移动光标的位置,通过上下左右键就行。
如果想要编辑,就把光标移动到相应的位置,输入i,意思是 insert。
进入编辑模式,可以插入、删除字符
要想保存编辑的文本,我们使用esc键退出编辑模式,然后输入“:”,然后在“:”后面输入命令w,意思是 write,这样就可以保存文本,冒号后面输入q,意思是 quit,这样就会退出 vim。如果编辑了,还没保存,不想要了,可
以输入q!。
- 文件系统命名请看第四节
- 程序运行的方式
1.常用的一种方式:通过 shell 在交互命令行里面运行。
2.后台运行。
3.以服务的方式运行
总结:
Linux命令本质是一个程序,也可以自己编写
3 Linux常见的系统调用
3.1 进程管理
在 Linux 里,要创建一个新的进程,需要一个老的进程调用 fork 来实现;
其中老的进程叫作父进程(Parent Process),新的进程叫作子进程(Child Process);
为什么要复制老的?因为从头启动太复杂,没必要
创建新进程的处理过程:
对于 fork 系统调用的返回值,如果当前进程是子进程,就返回 0;
如果当前进程是父进程,就返回子进程的进程号;
这样首先在返回值这里就有了一个区分,然后通过 if-else 语句判断:
如果是父进程,还接着做原来应该做的事情;
如果是子进程,需要请求另一个系统调用execve来执行另一个程序;
这个时候就产生了一个分支(fork)了。
谁是第一个进程?
启动的时候先创建一个所有用户进程的“祖宗进程”
它要对所有进程执行负最终的责任。
父进程如何得知子进程的信息?
有一个系统调用waitpid,父进程可以调用它,将子进程的进程号作为参数传给它,这样父进程就知道子进程运行
完了没有,成功与否。
3.2 内存管理
在操作系统中,每个进程都有自己的内存,互相之间不干扰,有独立的进程内存空间。
对于进程的内存空间来讲,放程序代码的这部分称为代码段(Code Segment)。
对于进程的内存空间来讲,放进程运行中产生数据的这部分,我们称为数据段(DataSegment)。
其中局部变量的部分,在当前函数执行的时候起作用,当进入另一个函数时,这个变量就释放了;
也有动态分配的,会较长时间保存,指明才销毁的,这部分称为堆(Heap)。
延迟使用:进程自己不用的部分内存不用管,只有进程要去使用部分内存的时候,才会使用内存管理的
系统调用来登记,说自己马上就要用了,希望分配一部分内存给它,但是这还不代表真的就对应到了物理内存。
只有真的写入数据的时候,发现没有对应物理内存,才会触发一个中断,现分配物理内存。
因为内存没有那么多,这样可以节约内存
当分配的内存数量比较小的时候,使用 brk,会和原来的堆的数据连在一起,这就像多分配两三个位置,在原来的区域里面增加就行了。
当分配的内存数量比较大的时候,使用 mmap,会重新划分一块区域,也就是说,当空间需要太多的时候,索性来个一整块。
3.3 文件管理
Linux 里有一个特点,那就是一切皆文件:
启动一个进程,需要一个程序文件,这是一个二进制文件。
启动的时候,要加载一些配置文件,例如 yml、properties 等,这是文本文件;
启动之后会打印一些日志,如果写到硬盘上,也是文本文件。
但是如果我想把日志打印到交互控制台上,在命令行上唰唰地打印出来,这其实也是一个文件,是标准输出stdout 文件。
这个进程的输出可以作为另一个进程的输入,这种方式称为管道,管道也是一个文件。
进程可以通过网络和其他进程进行通信,建立的Socket,也是一个文件。
进程需要访问外部设备,设备也是一个文件。
文件都被存储在文件夹里面,其实文件夹也是一个文件。
进程运行起来,要想看到进程运行的情况,会在 /proc 下面有对应的进程号,还是一系列文件。
对于文件的操作,下面这六个系统调用是最重要的:
1.对于已经有的文件,可以使用open打开这个文件,close关闭这个文件;
2.对于没有的文件,可以使用create创建文件;
3.打开文件以后,可以使用lseek跳到文件的某个位置;
4.可以对文件的内容进行读写,读的系统调用是read,写是write。
文件之所以能保存,一方面是因为介质,另一方面是因为格式。
每个文件,Linux 都会分配一个文件描述符(File Descriptor),这是一个整数。
有了这个文件描述符,我们就可以使用系统调用,查看或者干预进程运行的方方面面。
所以说,文件操作是贯穿始终的,这也是“一切皆文件”的优势:就是统一了操作的入口,提供
了极大的便利。
3.4 信号处理(异常)
当进程遇到异常情况,例如进程中断,做到一半不做了。这时候就需要发送一个信号(Signal)给进程管理系统。经常遇到的信号有以下几种:
1.在执行一个程序的时候,在键盘输入“CTRL+C”,这就是中断的信号,正在执行的命令就会中止退出;
2.非法访问内存,如其他进程访问了当前进程的内存;
3.硬件故障,设备出了问题;
4.用户进程通过kill函数,将一个用户信号发送给另一个进程。
对于一些不严重的信号,可以忽略,该干啥干啥;
但是像 SIGKILL(用于终止一个进程的信号)和 SIGSTOP(用于中止一个进程的信号)是不能忽略的,可以执行对于该信号的默认动作。
每种信号都定义了默认的动作,例如硬件故障,默认终止;
也可以提供信号处理函数,可以通过sigaction系统调用,注册一个信号处理函数。
3.5 进程间通信
首先就是发个消息,不需要一段很长的数据,这种方式称为消息队列(Message Queue)。
由于多个进程通信时,这个消息队列是在内核里的,我们可以通过msgget创建一个新的队列;
msgsnd将消息发送到消息队列;
而消息接收方可以使用msgrcv从队列中取消息;
当两个进程需要交互的信息比较大的时候,可以使用共享内存的方式,也即两个进程共享一
块内存(这样数据就不需要拷贝来拷贝去)。
这时候,我们可以通过shmget创建一个共享内存块,通过shmat将共享内存映射到自己的内存空
间,然后就可以读写了。
问题:两个进程共同访问一个内存的数据,就会存在“竞争”的问题:如果大家同时修改同一块数据咋办?
这就需要有一种方式,让不同的人能够排他地访问,这就是信号量的机制Semaphore。
简单的场景:
对于只允许一个人访问的需求,我们可以将信号量设为 1。当一个人要访问的时候,先调用sem_wait。
如果这时候没有人访问,则占用这个信号量,他就可以开始访问了。
如果这个时候另一个人要访问,也会调用 sem_wait。
由于前一个人已经在访问了,所以后面这个人就必须等待上一个人访问完之后才能访问。
当上一个人访问完毕后,会调用sem_post将信号量释放,于是下一个人等待结束,可以访问这个资源了。
3.6 网络通信
这台 Linux 要和另一台 Linux 交流需要用到网络服务。
不同机器的通过网络相互通信,要遵循相同的网络协议,也即TCP/IP 网络协议栈。
Linux 内核里有对于网络协议栈的实现。如何暴露出服务给进程使用呢?
网络服务是通过套接字 Socket 来提供服务的。
在通信之前,双方都要建立一个 Socket。
可以通过 Socket 系统调用建立一个 Socket。
Socket 也是一个文件,也有一个文件描述符,也可以通过读写函数进行通信。
3.7 Glibc(中介)
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 的系统调用。
3.8 总结
接下来讲解Linux的系统初始化