0、引言
在几十年前的大型机时代,一台主机是拥有多个终端。今天,我们拥有了各种各样的小型设备,智能手机,平板电脑,智能手表,然而这些东西,其实仅仅只是一系列的终端而已!
那么既然这些东西都成了终端,真正的计算机在哪儿?当然在各大机房里了,只是现在不叫大型机了,而叫做云端,这种技术叫做云计算(似乎有点炒作概念的意思)。你花了几千上万块的钱买来的设备仅仅是一个完成输入输出功能的终端。远端服务器和终端的关系,相当于主机和显示屏的关系,服务器之间是相互连通的,所以可以通过终端连上任何一个服务器。
学技术只是一个手段,为了实现某种功能或完成某个项目,所以要实用主义,不能求全。
学服务器是搭后端框架,让前端调用后端函数(前端向后端访问数据),后端响应其请求
命令解析器(终端执行对应命令的应用程序):bash,其前身叫shell
Ubuntu on Windows 10 对比原生 Ubuntu:
1、文件管理
1.1 Linux文件定义形式
Linux 不使用磁盘分区符号来访问文件系统,通过 “挂接” 的方式把所有分区都放置在 “根” 下各个目录里。
可以随意的挂载磁盘
1 mount /dev/disk1 /usr / download2 disk1 1T
3 mount/ dev/ disk2 /usr/up1oad4disk2 100T
5 mount /dev/ disk3 /usr/up1oad/photo6disk3 1P
目录访问:
1、绝对路径:从系统磁盘起始节点开始描述的路径(根) /
2、相对路径
当前目录:.
上级目录:..
3、家目录:~/ == /home/user/
$表示普通用户,#表示超级用户
tab键: 可以补全命令和文件名,如果补全不了快速按两下tab键,可以显示备选选项
pwd: 显示当前路径
ctrl C: 取消命令,并且换行
ctrl L: 清屏
ctrl + a 光标移到行首
ctrl + e 光标移到行尾
软件的安装和卸载:
1、在线安装更新卸载:
apt install/remove/update
2、deb包安装卸载:
dpkg -i *.deb
dpkg -r *.deb
命令的帮助文档。
help
内置命令的帮助文档
man
外部命令的帮助文档
因为当前系统为minimal的,very basic没有man包,需要手动安装man
yum install man man-pages -y
1.2 文件管理命令
cd xxx 进入xxx目录下,
cd .. 返回上层目录
cd - 返回上一次的目录
cd / 进入根目录
cd ~/ 进入家目录
mkdir xxx: 创建文件夹
touch XX: 创建文件
mkdir -p aa/bb/cc/dd 创建多级目录
rm xxx: 删除普通文件
rm xxx -r: 删除文件夹 大部分要进行文件夹操作都要加 -r
find XX 查找目录下的文件名
grep " " 查找目录下的字符串名
cat XX: 展示文件中的内容,只能cat文件
cp xx YY 将xx文件复制到YY,xx和YY可以是一个路径(也是复制粘贴+重命名)
cp a b -r 将a目录整个放到b目录下,a是b的子目录
cp a/* b/ 将a文件夹下的所有文件拷贝一份到b文件夹下
mv xx YY 将xx文件移动到YY,xx和YY可以是一个路径,重命名也是这个指令
ls 查看指定文件下的文件和目录
ls -l 显示详细信息
ls -lh 在命令后面加个h,会更容易看,比如把b->KB或者MB
ls -a 显示所有文件(以.开头的文件会被隐藏)
ll == ls -la(查看所有文件的详细信息)
ln -s 给文件/目录创建软链接(快捷方式)
ln -s hello.c hello.soft (相对路径)
ln -s 绝对路径 hello.soft (绝对路径,保证可以全文件使用)
ln 只能给文件创建硬链接(备份文件)
1.3 压缩包命令
1、rar --必须手动安装该软件
压缩:
rar a + 压缩文件名称(temp)+ 压缩文件目录
解压缩:
rar x + 解压缩文件名称(temp)+ 解压缩文件目录
2、zip
压缩:
zip + 压缩文件名称(temp)+ 压缩文件目录
解压缩:
unzip + 压缩文件名称(temp) -d + 压缩文件目录
3、tar --不使用z/j参数,该命令只能对文件或目录进行打包
参数:
c -- 压缩
x -- 解压缩
v -- 显示提示信息--可以省略
f -- 指定压缩文件的名字
z -- 使用gzip的方式压缩文件--.gz
j -- 使用bizp2的方式压缩文件-- .bz2
压缩:
tar zcvf + 生成的压缩包的名字(xxx.ar.gz) + 要压缩的文件或目录
tar jcvf + 生成的压缩包的名字(xxx.tar.bz2) + 要压缩的文件或目录
解压缩:
tar jxvf + 压缩包的名字(解压到当前目录)
tar jxvf + 压缩包名字 -C + 压缩的目录(解压到指定路径目录)
管道
ps aux | grep 字符串(bash)
杀死进程
kill pid
查看当前进程的环境变量
env | grep path
1.4 网络命令
ifconfig
ifconfig | grep inet
netstat -a——本选项显示一个所有的有效连接信息列表,
包括已建立的连接(ESTABLISHED),也包括监听连接请求(LISTENING)的那些连接。
netstat -anp 监听端口
netstat -natp
ping 查看与目标IP能否连通
telnet 查看与目标IP的指定端口能否连通
排查网络故障:
1、检查本地协议命令包是否安装正确:ping localhost
2、检查网卡驱动是不是有问题: ping 192.168.1.1(ip)
2、检查网关是否连通: ping 192.168.1.0(网络号)
3、检查与外网是否连通: ping www.baidu.com
1.5 vim命令
vim的中文版教程:vimtutor zh_CN
vim XXX 打开文件
1、i 进入编辑模式
ESC 从编辑模式退出
u 撤销 ctrl + r 反撤销
x(小写): 删除后面单个字符
X(大写): 删除前面单个字符
dd: 删除一整行
dw: 删除一个单词
0 移动到当前行行首
$ 移动到当前行行尾
G: 最后一行
gg: 第一行
行跳转 20G
yy 复制一行
nyy 复制多行
p 粘贴
编辑完之后
Esc + :wq 保存并退出文件
Esc + :q! 不保存直接退出
:sp 水平分屏
:vsp + 文件名 垂直分屏
双屏切换 ctrl + ww
2、v 进入可视模式:
复制:y;删除:d
查找: /需要查的内容
1.6 管道和重定向
管道
| 将前面的输出作为后面的输入
重定向
> 覆盖原来的信息
>> 追加到原来信息的后面
1.7 软件安装命令
1、安装包: rpm -ivh aa.rpm
2、压缩包: tar -zxf aa.tar.gz
mkdir
cp
3、yum
1.8 三剑客命令
三剑客命令主要是用来处理文本的
cut、sort、wc
1、grep 找文件中的内容 (find找文件名)
2、sed -i 直接修改文件内容,而不是输出到终端
如果不使用-i选项sed命令只是修改在内存中的数据并打印到终端,不会影响磁盘上的文件
3、awk
2、Linux用户管理
2.1 用户
规范不同用户的使用资源
1、新增用户 useradd username
2、设置密码 passwd 123456
3、切换用户 su username
4、删除用户 userdel username
2.2 组
1、创建组 groupadd name
2、修改用户所在组 usermod -g name username
2.3 权限
根据UGO模型,一般更改文件的权限有两种方式
1、更换属主或者属组
chown dufu file
2、更改其他组(O组)的文件rwx权限
chmod o+w file
2.4 用户分类
/etc/passwd 保存系统中所有的用户信息
/etc/shadow 保存用户的密码信息
/etc/group
3、gcc、g++
linux平台没有系统化的ide,gcc就是一个编译器,主要工作分为 4 个阶段:预处理、编译、汇编和链接。
![DVEVXNZ3}{E$KOMRS3F$C1.png
-E 预处理 把.h和.c 展开形成一个 .i 文件 (宏定义直接替换)
gcc -E hello.c -o hello.i
-S 编译 .i生成一个汇编文件.s
gcc -S hello.i -o hello.s
-c 汇编 .s生成一个二进制文件.o(windows) .obj (linux)
gcc -c hello.s -o hello.o
无参数 链接 .o 链接成可执行程序 .exe (windows) .elf (linux)
gcc hello.o -o hello
Linux下执行可执行程序命令 ./hello
gcc参数:
-I 参数的应用场景:
源文件中包含的头文件无法被找到。通过提供的目录结构可以得知头文件 head.h 在 include 目录中
gcc *.c -o hello -I ./include
-D 参数的应用场景:
在发布程序的时候,一般都会要求将程序中所有的 log 输出去掉,如果不去掉会影响程序的执行效率,解决方案是这样的:将所有的打印 log 的代码都写到一个宏判定中,可以模仿上边的例子
在编译程序的时候指定 -D 就会有 log 输出
在编译程序的时候不指定 -D, log 就不会输出
gcc hello.c -o hello -D DEBUG
4、进程管理
3.1 进程(Process)
3.1.1 定义
运行中的程序,被称为进程
PCB 是进程存在的唯一标识,这意味着一个进程的存在,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失。
3.1.2 进程调度
各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让一个进程切换到另一个进程运行,称为进程的上下文切换。
进程上下文切换发生的时机:
时间片耗尽、内存不足、当进程程序中含有休眠函数(sleep)、遇到高优先级的进程想被执行、硬件中断
并发进程:在同一时刻,多个进程同时存在,但不一定在同时运行。
并行进程:在同一时刻,多个处理器同时处理多个不同的任务。所以无论从微观还是从宏观来看,二者都是一起执行的。
3.2 线程(Thread)
3.2.1 定义
对于父进程创建的子进程会完全复制一份,造成资源浪费,因而引出线程:进程当中的执行流
共享的好处:
1、占用的存储空间更少
2、访问信息更加快捷
对于进程里的多个线程共享同一个code、data、heap和文件等资源,并且每个执行流并发执行,但是栈空间、寄存器都有自己单独的一套,这样可以确保线程的控制流是相对独立的。
但是线程也有个坏处:
当进程中的一个线程奔溃时,会导致其所属进程的所有线程奔溃,因此在许多用户设计中不使用多线程方式。
线程的实现,主要有三种线程的实现方式:
用户线程(User Thread):在用户空间实现的线程,不是由内核管理的线程,是由用户线程库来管理;
内核线程(Kernel Thread):在内核中实现的线程,由内核管理;
轻量级进程(LightWeight Process):在内核中来支持用户线程;
3.2.2 线程调度
优先是调度real-time的线程,之后再会去调用normal的线程,当线程中含有多个real-time的线程,先按照优先级进行调度,之后按照FCFO机制进行。
对于normal的线程会去使用调度算法:分时调度、CFS、动态优先级(NICE)等。
3.2.3 线程常见函数
在创建线程时先包含头文件include
线程ID的数据类型 pthread_t
在创建子线程过程中,对于创建的子线程功能可能并没有执行,虽然子线程创建成功,但一直没有抢占到cpu的时间片,故在主线程结束之后子线程也一并被销毁了。有两种方式可以实现子线程的函数执行
1、使用sleep函数让主线程挂起,等待子线程执行完主线程才去执行
2、join()函数
对于将子线程参数传出有两种方法:
1、将参数定义为全局变量或者静态变量存储在data段,因为主线程和子线程的data段是共享资源
2、利用主线程的栈空间,在主线程中定义变量,通过pthread_create()函数的第四个参数将变量的地址传递给子线程
struct Test
{
int num;
int age;
};
struct Test t; //定义全局变量t,线程之间资源共享来传出线程参数
void* working(void* arg)
{
//2、pthread_self()函数用于获取本子线程id,返回值类型是pthread_t
printf("子线程ID: %ld\n", pthread_self());
for(int i=0; i<9; ++i)
{
printf("child == i: = %d\n", i);
}
//struct Test t; //可以通过将t定义为全局变量或者静态变量就可以存储线程的共享区data段
t.num = 100;
t.age = 6;
pthread_exit();
return NULL;
}
int main()
{
pthread_t tid;
//1、创建子线程函数
pthread_create(&tid, NULL, working, NULL);//这里working是子线程的函数地址
//2、pthread_join()可以理解为一个阻塞函数,主线程执行到这里会等待子线程执行完毕
void* ptr; //ptr用于接收子线程函数传递出来的参数地址
pthread_join(tid, &ptr); //join函数一次只能回收一个子线程
struct Test* pt = (struct Test*)ptr;
std::cout << pt->num << pt->age <<std::endl;
printf("子线程创建成功, 线程ID: %ld\n", tid);
//3、线程退出函数(调用该函数当前线程会马上退出,但地址空间不会被释放,因此不会影响其他线程运行)
pthread_exit(NULL);
return 0;
}
编译:gcc a.c -o app -lpthread
3.3 进程的同步与互斥
互斥:并发进程中的交互关系,对于有竞争关系的线程肯定希望他们是互斥的,不能同时执行,不然容易出错。
同步:对于并发线程在一些关键点上可能需要互相等待与互通消息,这种相互制约的关系称为线程同步。
3.3.1 锁(互斥锁)
自旋锁:这是最简单的一种锁,一直自旋,利用 CPU 周期,直到锁可用。在单处理器上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU
无自旋锁:当没获取到锁的时候,把当前线程放入sleep队列,然后执行调度程序,把 CPU 让给其他线程
读写锁:读写锁是互斥锁的升级版,这只是一把锁,既可以锁定读操作也可以锁定写操作,但写操作优先级要高于读操作,而且对于写操作一次只有一个线程访问,读操作可以多个线程并行操作。
读写锁的应用场景大多是读操作较多的线程中
Tip:对于临界区的加锁和解锁范围一定要越小越好,紧靠着临界区,如果无意中将临界区扩大,那么会陷入死锁状态。
3.3.2 信号量
对于整型变量S,除了初始化之外,后续只能做PV的原子操作。
PV操作之后,如果程序运行正确则不会改变初始值大小。
当s > 1一般将信号量初始化为共享资源的数目,需要两个锁,记得锁的先后顺序
当s = 1一般将信号量初始化为1,进行PV操作就行
当s = 0多线程的同步问题
Tip: 对于信号的PV操作在同一个进程中的称为互斥信号量 对于信号的PV操作在不同的进程称为同步信号量
3.4 进程间通信
Linux 下的进程间的通信方式主要有管道、信号、消息队列、共享内存和套接字等方法
3.5 关于进程与线程的命令
ps -ef 查看所有进程
ps -aux 查看所有进程具体信息(cpu、内存使用情况)
top 当前服务器内存使用
kill -9 pid 杀死进程
5、内存管理
对于没有操作系统的简单机器,如51单片机,是直接操作内存的物理地址。
而对于我们平常使用的电脑,直接访问物理地址会占用大量的内存空间,效率极低。然后就引出了虚拟地址,操作系统为每个进程分配一套独立的「虚拟地址/逻辑地址」,自己玩自己的地址就行,互不干涉。
那么真正的物理地址 = 虚拟地址 + 基地址(起始地址)
编译:虚拟地址
加载:前提要知道起始地址R
可执行:由MMU将虚拟地址转换成物理地址(基址寄存器)
解决的问题:
1、存取效率:引入缓存,加快速度。
高速缓存是一种内存小,但速度快的储存器,对于现在的多核多线程,一般会有好几级的缓存,级数越小,内存越小,越靠近cpu,速度也越快。
2、保护操作系统和用户进程。用户进程不可以访问操作系统的内存数据,且用户之间的进程也不能互相影响。
5.1 进程在内存中布局
代码段:存放执行的指令(二进制码) 只读权限,对于进程中公共部分,可以只存一份进行数据共享
数据段:存放全局变量、静态变量、常量数据
堆:运行时分配的地址(存大数据、图片视频啥的)
栈:存放局部变量、函数参数
5.2 内存分段
对于每个进程是由若干个逻辑分段组成的,如可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。将他进行切片,这样可以更好地利用碎片化的内存空间,虽然增加了存储负担,计算负担。这就体现了计算机在解决问题的一个权衡思想,虽然解决问题又带来了一个新的问题,但是如果利大于弊,还是可以作为一个解决方案的。
对于内存分段:先找段号,在段表里找到段号对应的段基地址(初始地址),然后找到段内偏移量,就可确定物理地址了。 段基地址 + 偏移量 = 物理地址
分段的好处就是能产生连续的内存空间,但是会出现内存碎片和内存交换的空间太大的问题。为了解决内存碎片和内存交换效率低的问题,就出现了内存分页。
5.3 内存分页
分页是把整个虚拟内存和物理内存都切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫页(Page)。在 Linux 下,每一页的大小为 4KB。
页表就是页号(page number,虚拟地址)和帧号(frame number,物理地址)的对应关系
所以知道page number就能找到页表里对应的frame number,(物理页号×4)+偏移量=物理地址
计算机真正在页内进行地址转换的时候直接将页号替换,然后将偏移量追加到后面即可。
Tip:分段和分页对比
缺点: 1、地址之间映射的速度问题(每次访问内存都要两次甚至更多次)
2、如果虚拟地址的空间很大,那么页表也会占用大量的存储空间
5.4 TLB
为了解决页表出现的问题,引入TLB。
TLB:是一块很小,而且查找速度很快的缓存。它与页表配合使用
1、TLB包含若干个进程中的部分页表项(也称为快表)
2、对于将虚拟地址转换为物理地址过程中,先从TLB中查找有没有已经存在的页号,若存在则直接将page number替换frame number(只需要访问一次内存),若没有则按照上面的去执行(访问很多次内存)。