下载链接
链接: https://pan.baidu.com/s/1hRTh7rSesikisgRUO2GBpA?pwd=utgp 提取码: utgp
Linux系统
基础指令
目录相关
cd
ls
pwd
文件相关
touch
cp
mv
rm
mkdir
rmdir
less
less [选项] [文件名]
-i 忽略搜索大小写
-N 显示行号
/ ? 向上下搜索字符串
n/N 前进/后退一个搜索
head
head [选项] [文件名]
-n 行数
tail
-f
- 不断刷新最后一行到屏幕上
-n
- 行数
find
find [从哪里搜] [按什么搜] [搜什么]
find ~ -name Http.hpp
grep
-i 忽略大小写
-n 输出行号
-v 反向选择
压缩相关
zip
zip [压缩包名] [文件位置]
zip test.zip test/*
unzip
unzip [目标文件名] [解压到某一目录下]
unzip test.zip -d /tmp
tar
tar [选项] [目标文件名] [从某一目录]
tar -cvf /tmp/etc.tar
进程相关
ps
kill
网络相关
- netstat
IPC相关
ipcs
- 查看通信资源
ipcrm
- 删除通信资源
系统资源相关
top
free
fdisk
df
- 挂载的分区使用情况
du
- 查看目录下文件的空间占用
权限相关
切换用户
- su [用户名]
更改权限
- chmod 777 [文件名]
更改所属组
- chgrp
其他
常用工具
vim
模式
插入
功能
- 插入文字
底行
功能
- 文件保存退出,文件替换,找字符串,列出行号
操作
/ 字符串
- 查找字符串
普通
功能
- 控制屏幕光标移动,删除,复制等
操作
光标移动
hjkl
G:移动到最后
gg:文本的开始
删除
- dd:删除行
复制
- yy:复制
粘贴
- p:粘贴
剪切
- 上面组合一下
撤销/反撤销
u:撤销上一次
R:撤销恢复
gcc/g++
编译过程
预处理
宏定义替换,头文件展开,条件编译,去除注释
gcc -E hello.c -o hello.i
编译
检查语法错误等,最后翻译成汇编语言
gcc -S hello.i -o hello.s
汇编
把汇编转换为二进制代码
gcc -c hello.s -o hello.o
链接
链接成可执行文件
gcc hello.o -o hello
链接在做什么
链接方式
静态链接
- 把库文件的代码全部加入到可执行文件中,但是也就不需要库文件了
动态链接
- 动态链接则将链接过程推迟到了程序运行时。编译时,编译器和链接器不会将库的实际内容合并到可执行文件中
默认的链接方式
- 默认采取的是动态链接
动态库和静态库的生成
动态库生成
先生成 .o 文件
- gcc -fPIC -c sub.c add.c
再生成动态库
- gcc -shared -o [目标包的名字] [所依赖的.o文件]
静态库生成
先生成 .o 文件
- gcc -c xxx.o xxx.c
再生成静态库
- ar -rc [目标包的名字] [所依赖的.o文件]
动态库和静态库的使用
程序生成时库的链接搜索路径
- gcc test.c -Imymath_lib/include/ -lmymath -L mymath_lib_so/lib
程序运行时库的加载搜索路径
- gcc test.c -I mymath_lib/include/ -l mymath -L mymath_lib/lib
结论
- 静态库提供给别人去使用,只需要把对应的.h头文件和对应的.a文件交给别人就够了,其中这个.a文件就是我们前面所说的.o文件的集合
gcc常见选项
-E
生成预处理后的代码
- 还是文本代码
-S
生成编译后的代码
- 汇编
-c
生成汇编后的代码
- 二进制
-o
进行链接
- 生成可执行程序
-static
- 必须采取静态链接的方式
-fPIC
与位置无关码
- 一般用于生成动态库
-shared
shared
- 表示生成共享库格式
-L
link
- 告诉编译器,编译的时候需要使用的库的存储路径
-I
include
- 告诉编译器,编译的时候要使用的头文件路径
-l
link
- 告诉编译器,编译的时候需要使用这个库的名称
-g
debug
- 告诉编译器,要产生调试信息
gdb
功能
程序调试的前提
- gcc -g
gdb加载程序
- gdb ./main
常见调试操作
开始调试,加载运行参数
run -I -a
start -I -a
逐步调试
n
s
I
p
until
断点调试
b
i b
d
watch
函数调用栈信息
- bt
make
git
进程概念
冯诺依曼
外设
IO
数据流
存储分级与IO效率
所有硬件都是围绕内存工作的
操作系统
目的
- 与硬件交互,管理软硬件资源
定位
- 给应用程序提供良好的运行环境
管理
- 先描述,再组织
库函数与系统调用接口
- 库函数内部是封装了系统调用接口的
进程概念
原因
- 当程序运行时,要从硬盘加载到内存当中,操作系统为了更方便的管理每个程序的运行,就要首先描述出每一个进程的相关信息,所以有了进程控制块,而Linux中的PCB就是task_struct
描述信息
内存
- mm_struct
文件
files_struct
- struct file * file_arrays[]
数据
- 寄存器中存储的上下文数据
信号
信号的阻塞位图
- sigset_t blocked
信号的处理方法
- struct sigaction
进程控制
进程创建
fork
复制
- 以父进程为模板,为子进程创建PCB,子进程和父进程共享代码和数据
返回值
父进程返回子进程的pid
子进程返回0
vfork
- 共享同一个虚拟地址空间
创建一个进程的流程
找到父进程的PCB对象,malloc一个PCB来存放子进程,用父进程的PCB初始化子进程,子进程的PCB指向父进程的代码和数据,子进程和父进程都加入调度队列开始排队等待调度
当有一方要改变数据时,就会触发写时拷贝
进程终止
终止的几种情况
正常终止,结果正确:代码正常执行完毕,正确退出码是0
正常终止,结果不正确:代码也正常执行结束了,但是不在合适的地点退出,退出码不为0
异常:代码没有正常执行结束,可以使用echo $?来查看
exit code:当进程收到信号而退出后,可以父进程可以通过回收资源收到退出码,以查看子进程的退出情况
终止的操作对比
exit和_exit:_exit是系统调用,exit内部封装了这个系统调用
main和return
进程终止系统的行为
-
- 根据被终止进程的标识,操作系统会从它的PCB集合中找到该进程的PCB,读取该进程的状态
-
- 如果该进程处于执行状态,会立即停止执行,并修改状态标识,表示这个进程被终止
-
- 回收进程的资源
-
- 从运行队列中移除
-
进程等待
进程等待的原因
防止内存泄漏,子进程退出,父进程不回收,就会导致僵尸状态的出现
僵尸进程:父进程创建子进程后,子进程异常终止,父进程并没有调用对应系统调用获取子进程的退出信息,子进程的进程描述符等资源仍在系统中占用资源,那么此时子进程就变成了僵尸进程
进程等待的方式
wait:pid_t wait(int *status);
waitpid: pid_t waitpid(pid_t pid, int *status, int options);option为0,表示阻塞等待,WNOHANG表示非阻塞等待
status是一个输出型参数,0-7是退出信号,8-15是位图
signal和exit code
阻塞等待和非阻塞等待
阻塞等待是停在函数调用位置等待,直到回收资源成功
非阻塞等待是,每到接口处就检测,搭配循环使用,可以做其他事
进程状态
创建,就绪,运行,阻塞,终止状态
每一种状态就是宏定义
状态变换本质就是修改宏的值,放入不同的队列
就绪状态
- 当进程的时间片用完之后,就会从CPU上剥离下来,此时会保存寄存器的代码和数据,存储到PCB当中,紧接着把进程的PCB加入到就绪队列当中,等待下一次调度运行
挂起状态
- 当进程在阻塞状态时,如果内存资源不足,会暂时把阻塞进程的代码和数据置换到磁盘的swap分区中,当进程被调度的时候再把数据加载进来
转换流程
僵尸进程
产生原因
- 子进程终止,父进程不回收子进程资源
危害
- 子进程的数据没有被回收,就会内存泄漏
避免
进程等待
- 主动调用接口,waitpid进行阻塞或非阻塞等待
SIGCHLD
- 子进程结束后,会向父进程发送一个信号,父进程可以对于该信号进行自定义设置方案,即可处理
孤儿进程
产生原因
- 子进程未终止,父进程先终止了
处理
- 这是无害的,bash会领养孤儿进程,由bash进程进行回收资源
守护进程
后台运行
- 不接受任何终端的输入
脱离控制
- 自己创建一个会话,成为会话组长进行管理自己
独立生命周期
- 用孤儿进程的方式由bash领养,避免父进程影响
总结
- 连续2次fork进行变成孤儿进程,再自己创建一个会话管理自己
环境变量
概念
用来指定操作系统运行环境的一些参数
- 动静态库的位置
PATH
- 指定命令的搜索途径
HOME
- 指定用户的主工作目录
SHELL
- 当前shell,一般是bash
相关命令
echo 查看环境变量
export 新增一个环境变量
env 显示所有环境变量
unset 清除环境变量
set 显示本地定义的环境变量
特性
main函数参数的第三个参数
环境变量信息是以脚本配置文件形式存在的
每次登陆从 .bash_profile 当中读取内容创建表
本地变量不能被子进程继承,环境变量可以
环境表本质上是一个字符指针数组
命令
常规命令
- fork子进程让子进程执行该命令
内建命令
- shell命令行的函数,直接读取环境变量
代码中获取设置环境变量
getenv
putenv
setenv
虚拟地址空间
是什么
- 一个mm_struct结构体
为什么
提高内存利用率
增加内存访问控制
保持进程独立性
怎么做
分段式内存管理
- 程序由若干个逻辑分段组成,代码分段,数据分段,栈段,堆段等,不同的段有不同的属性,用分段的形式可以把段分离开
分页式内存管理
- 产生连续的内存空间,把整个虚拟地址和物理内存空间划分为固定尺寸的大小,每一页是4KB
段页式内存管理
先划分成逻辑意义的段,也就是分段机制
再把每一个段划分成多个固定大小的页
由段号,段内页号,页内偏移定位信息
进程替换
替换原理
创建新进程:调用成功后,整个进程的代码和数据会替换为目标的代码和数据
后续进程处理方式:不会处理后续的进程
系统调用接口
execl
execle
execlp
execv
execvpe
execvp
MySQL shell
进程信号
信号概念与本质
软件中断,通知进程发生了某个事件,打断进程当前操作,去处理这个事件
信号是提前定义好的内容,进程提前就知道应对措施
信号的本质是对位图做写入工作
信号种类
非可靠信号
- 1-31
可靠信号
- 34-64
信号生命周期
信号的产生
硬件产生
键盘产生:硬件的光电信号->中断信号->中断方法
异常产生:状态寄存器->操作系统检测寄存器内容->发信号
软件产生
系统调用产生:kill/raise/abort
软件条件产生:alarm设定闹钟提供软件条件/管道触发
信号在进程中注册
原理
- 把函数指针添加到处理方法的数组中
信号在进程中注销
原理
- 把函数指针从处理方法的数组中移除
信号的处理
原理
处理方式
默认
- SIG_DFL
忽略
- SIG_IGN
自定义
void (*sighanler_t)(int signo)
自定义处理方式捕捉
修改处理方式
signal
sigaction
用户内核转换
用户态:是一种受限的状态,能访问的资源是有限的
内核态:是一种操作系统的工作状态,能访问大部分资源
信号的保存
信号递达:信号准备被处理,处理方式有:默认,忽略,自定义
信号未决:信号传输成功但还未处理这个状态
信号阻塞:表示信号被阻塞,信号不会被识别
信号的阻塞
原理
- 在阻塞信号集合中标记,即可不处理
如何实现
- sigprocmask
函数可重入与不可重入
函数可重入
- 在多个执行流中同时进入一个系统调用
如何判断
对于临界资源进行调用
临界资源的操作是否安全
volatile
保持内存可见性
- 防止过度优化
信号的相关操作
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
int sigpending(sigset_t *set);
int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
void (*signal(int sig, void (*func)(int)))(int);
核心转储
- 将错误信息存储到一个文件中,方便调试,由于占用内存过大默认关闭
进程通信
System V IPC
InterProcess Communication
System V是一种规范标准,在规范标准下定义的接口和设计都有相似性,使用成本不会很高
为什么操作系统需要向用户提供进程间通信
进程隔离和地址空间保护
- 为了保护数据独立,每个进程有独立的进程地址空间,所以操作系统要提供专用的机制来进行数据交换
协同工作和资源共享
- 在多任务中,可能会需要多个进程共同协作完成任务
系统模块解耦
- 让进程和进程之间解耦,可以通过自己独立的方式进行资源共享,而不是相互耦合
进程间通信种类
管道
作用
- 实现进程之间数据传输
本质
- 内核中的一块缓冲区
原理
- 多个进程访问同一块缓冲区实现通信
分类
匿名管道
缓冲区没有标识符
只能用于具有亲缘关系的进程间通信
命名管道
缓冲区有标识符文件
- 管道文件
任意进程都可以打开管道文件进行缓冲区访问
特性
四种情况
管道没有数据,写端必须等待
管道写满数据,读端必须等待
写端关闭,读端读取到0
读端关闭,写端收到13号信号
五种特性
默认给读写端提供同步机制
匿名管道允许具有血缘关系的进程间通信
面向字节流
生命周期随进程
单向通信
理解进程阻塞
管道接口
pipe:int pipe(int fd[2]);(输出型参数)
mkfifo:int mkfifo(const char * pathname,mode_t mode)
共享内存
原理
通信的本质是让不同的进程看到同一份资源
共享内存的本质,在系统层面上把内存申请好,再映射到两个进程的地址空间中,映射结束之后,此时只需要把映射在虚拟地址中的起始地址返回给用户,用户就可以通过起始地址进行访问了
key和shmid
对于key值是用来创建共享内存时的参数,可以由函数ftok创建:key_t ftok(const char *pathname, int proj_id);
shmid是创建共享内存的返回值,对于共享内存的操作都要靠id来做,有些类似于文件描述符fd
接口
shmget:int shmget(key_t key, size_t size, int shmflg);
size:一般设置为4096的整数倍,开辟空间是以4096为单位开辟的
IPC_CREAT:没有就创建,有就返回
IPC_EXCL:搭配上面选项使用,保证只有一个共享内存
0666:表示创建文件所需要的权限
shmat:void *shmat(int shmid, const void *shmaddr, int shmflg);
创建共享内存返回的shmid
将共享内存挂接在虚拟地址的指定位置,一般设置为nullptr
挂接方式:设置为0
返回值:需要强转,参考malloc
shmdt:int shmdt(const void *shmaddr);
- 挂接成功后虚拟空间的地址
shmctl:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
创建时返回的shmid
IPC_RMID:立即删除
通常设置为nullptr
特性
效率高:共享内存是进程间通信中效率最高的一种方式
原因:减少拷贝,直接控制
生命周期:随内核
ipc指令
ipcs
ipcs -m:显示共享内存
ipcs -s:显示信号量
ipcs -q:显示消息队列
ipcrm
ipcrm -m shmid:移除共享内存
ipcrm -s semid:移除信号量
ipcrm -q msgid:移除消息队列
消息队列
信号量
本质:本质是一个计数器,用来保护公共资源
临界资源:被保护起来的资源
临界区:访问临界资源的代码
同步性
互斥性:计数器上限为1
原子性:指只有两态,要不然完成,要不然没完成,原子性是保证信号量处于安全的必要前提
PV操作:申请资源和释放资源导致计数器产生变化
多线程
线程概念
进程概念
把一个可执行程序运行起来就是进程
代码和数据+内核数据结构(PCB)
Linux下线程实现
进程是资源分配的基本单位
线程是调度的基本单位Linux没有线程概念,采用的是轻量级进程,复用进程的PCB结构进行处理
线程间的独有与共享
共享
代码和数据
自定义函数和全局变量
文件描述符表
信号处理方式
工作目录
独有
一组寄存器的数据内容(重要)
线程独有自己的栈空间(重要)
线程ID
信号屏蔽字
调度优先级
errno
多线程/多进程任务处理的优缺点
优点
创建新线程的代价比新进程小
线程切换比进程切换要容易
线程占用资源比进程占用资源少
充分利用多处理器并行
计算较多的进程,可以把计算分配到其他线程中
线程可以等待不同的IO操作
缺点
性能损失,增加了额外的切换的损耗
健壮性较低,可能会出现不该共享的变量
一个线程异常会导致全部异常
编写难度比较高
线程控制
线程创建
线程在虚拟地址空间中有自己独有的线程地址空间
线程的数据会存储在mmap区域,有线程的局部存储和线程自己的栈结构
线程终止
- pthread_exit函数
线程等待
已经退出的线程,如果不回收资源,还会存储在进程的地址空间当中
创建新线程就不会复用刚才的退出的线程的地址空间
线程分离
线程可以自己调用函数把自己和主线程分离
int pthread_detach(pthread_t thread);
如果不关心线程的返回值,那么就可以让线程分离,当线程结束的时候可以直接由操作系统回收
线程安全
概念
- 多个线程对于临界资源的争抢访问操作,但不会造成数据的二义性
如何实现
互斥
- 保证安全性
同步
- 保证合理性
互斥实现
互斥锁
信号量
- 信号量初始化为1,当有资源申请时,如果信号量满足就申请成功,如果不满足就阻塞等待
同步实现
条件变量
- 判断流程和循环判断是否满足条件
信号量
生产消费者模型
full_count:记录缓冲区中已有产品数量
empty_slot:记录缓冲区可用空位数量
生产消费者模型
场景
- 并发编程的模型,用来解决多线程共享数据的问题
作用
解耦合
- 分离生产者和消费者的任务,使得它们可以独立工作,互不影响
支持忙闲不均
- 有缓冲区,有同步的概念
支持并发
- 生产者和消费者可以同时执行,提高系统的效率,允许存在处理速度的不同
实现
线程安全队列
生产者消费者线程创建
总结
三二一原则
三个原则
生产者和生产者之间是互斥关系
消费者和消费者之间是互斥关系
生产者和消费者之间又是互斥又是同步
生产者生产,要不然生产了要不然没生产,在这个过程中消费者不可以介入,必须要保持互斥
消费者消费前提是生产者要生产,所以要有同步的概念存在
两种角色
生产者
消费者
一个交易场所
- 某个特定的内存空间
读者写者问题模型
读写锁
概念
- 允许多个线程进行读取,只允许一个线程写入
实现原理
读锁获取
-
- 检查写锁是否持有,如果没有持有就允许读锁
-
- 如果写锁持有,或者读锁满了,就阻塞等待
-
- 当满足条件后会唤醒阻塞的线程
-
写锁获取
-
- 先检查是否有读锁,再检查是否有写锁,必须独占
-
- 如果不满足,就阻塞等待
-
- 如果满足了,就唤醒阻塞的线程
-
读锁释放
- 如果读完了,就释放读锁,如果没人读了,就唤醒阻塞的写进程
写锁释放
- 如果写完了,就释放写锁,此时写进程和读进程都可以申请,根据特定策略进行发放
优先级信息
- 按照线程请求锁的先后顺序来决定锁的分配
自旋锁
概念
让试图获取锁但发现锁已被占用的线程(或执行单元)在原地循环等待(即“自旋”)
这样可以避免被切换上下文调度
实现原理
- 循环申请某个锁,直到申请成功
使用场景
- 用于申请锁临界区比较少的情况
悲观乐观锁
- 上述基本都是悲观锁,乐观锁一般是说不管怎么说先把数据改了再说,如果冲突了再放弃,一般适用于冲突几率很小的场景
线程池
概念
- 预先创建一组可复用的线程,当有新任务来就选择一个空闲线程执行,任务结束回到线程池等待下一次分配
解决的问题
解决了线程创建和销毁的开销
方便于对于线程进行管理和控制
实现
线程池管理,工作线程,任务队列,任务接口
创建线程池,提交任务,任务分配,执行任务,回收线程
线程安全的单例模式
设计模式
单例模式概念
- 资源只需要被加载一次
单例模式实现
饿汉模式
初始化加载所有资源
所有对象共享一个资源
手撕实现
懒汉模式
需要的时候再加载
所有执行流对象共享一个资源
手撕实现
STL容器安全
- 不安全,需要自己保证
智能指针
- shared_ptr 安全
网络
网络基础1
网络套接字
- 本质可以看成是进程间通信
网络基础2
应用层
HTTP协议
概念
超文本传输协议
- 就是超过了普通的文本,也可以传输图片文字音频视频…这些都叫做超文本
常见状态码
1xx
- 表示中间状态
2xx
- 成功
3xx
- 资源重定向
4xx
- 客户端错误
5xx
- 服务端错误
常见字段
HOST
- 服务器域名
Content-Length
- 数据长度
Connection
- 决定长短链接
Content-Type
- 数据格式
Content-Encoding
- 数据压缩方法
GET/POST
区别
GET是从服务器获取资源
POST是对资源做处理
安全和幂等
- GET是,POST不是
特性
HTTP/1.0
- 短连接,性能不好,一般不考虑
HTTP/1.1
优点
- 简单,灵活,扩展,广泛,跨平台
缺点
- 明文传输,无状态(Cookie)
性能优点
长连接+管道
- 这个功能基本不开放
解决了请求阻塞问题
性能缺点
头部没压缩,相同的头部占用资源
响应按序响应,可能会阻塞
没有请求优先级控制
只能客户端请求,服务端被动响应
HTTP/2.0
优点
头部压缩,二进制
并发传输
- 注意一下并发传输,说的是在一条TCP连接中可以有多个Stream,每一个流里面有Message,Message里面放的Frame,一个或多个Frame二进制流构成了是HTTP/1的数据,单个Frame是最小单位
主动连接
- 比如对于前端CSS的内容,如果使用的是HTTP/1,要不停的申请,但是如果使用HTTP/2.0,可以主动发送CSS的相关信息
缺点
- TCP层的队头阻塞问题没有解决
HTTP/3.0
- 直接把底层协议换成UDP,但是普及很慢
缓存
实现方式
- 把请求和响应的数据缓存在本地,如果请求的方式是一样的,那么直接从本地拿响应的数据
强制缓存
- 服务器发回数据时预估一个过期时间,如果数据没过期就从本地拿
协商缓存
- 服务器对比一下更改时间,如果数据没变,就发回响应告诉客户端直接从缓存里面拿就行
发展历史
HTTPS协议
解决问题
- 窃听,篡改,冒充
途径
- 信息加密,校验机制,身份证书
传输层
UDP
特性
无连接
- 只要知道对端地址就可以发数据
不可靠
- 不关心是否安全送达
面向数据包
- 只能整个整个发,但是不会粘包问题
协议字段
源端口、目的端口、数据报长度、校验和
- 如果校验和出错就会丢弃
数据包长度的特性
数据类型uint16_t
数据报长度不超过64k
面向数据报
TCP
字段
源端口/目的端口
序号/确认序号
首部长度
标志位
URG 紧急指针是否有效
ACK 确认号是否有效
PSH 让接收端赶快读取数据
RST 重新建立连接 复位报文段
SYN 请求建立连接 同步报文段
FIN 提示本端要关闭了 结束报文段
窗口大小
校验和
紧急指针
特性
面向连接
三次握手和四次挥手
原因
第三次握手是可以携带数据的
可靠传输
面向字节流
系统中的概念
同步和异步
同步
- 同步讲究一个先后顺序,比如说管道访问的时候必须要先有写的内容,才能读,如果不符合这样的需求就进行阻塞,只有当满足要求了才能进行运行,读取数据等等,是一个有先后的问题
异步
- 表示的是这个操作不依赖其他的步骤,即使资源不就绪也不会阻塞,比如说可以把read使用fctl设置为非阻塞,一种场景是在网络中发送请求后,无需阻塞等待,可以等到收到回复后再处理
同步和互斥
同步和互斥中的同步和同步与异步之间的同步不太一样,侧重点不同,但是也比较相似,正常来说同步与互斥中的同步说的是同步访问
同步访问
- 强调的是对于多个线程访问临界资源要有一定的顺序性,这样来维护数据一致性,避免竞争
互斥
- 互斥强调的是排他性,在同一时刻我只允许一个线程进行访问临界资源,互斥可以看做是同步的一种特例情况
并发和并行
并发
多个任务在同一时间内交错执行
通过快速切换,营造出是同一时刻一起执行的假象
并行
并行是真正意义上的一起执行
操作系统把任务分配给不同的处理器核心
原子性
完整性
- 原子性只有两态,要不然不开始,要不然完成,如果开始了就必须完成,不会受到任何的影响
一致性
- 因为有上述的特性,所以原子性保证不会出现数据竞争的情况
隔离性
- 从外界的视角来看,原子性的操作是一瞬完成的,因为要不然是没有开始前的状态,要不然已经完成了,只有两态
基础IO
fd的本质
文件描述符,本质上是数组的下标
fd本质上是files_struct结构体下的fd_array的下标
分配规则:寻找最小的,没有被使用的数据的位置,分配给指定的打开文件
创建的每一个进程会默认打开三个文件,标准输入/输出/错误
0:标准输入
1:标准输出
2:标准错误
重定向函数:dup()/dup2()
int dup(int oldfd);
- 返回旧文件描述符
int dup2(int oldfd, int newfd);
将新文件描述符覆盖到旧文件描述符,
并关闭新文件描述符覆盖成功返回新文件描述符的值,
覆盖失败返回-1
重定向的本质:新文件描述符覆盖旧文件描述符
FILE*和fd
FILE*描述文件的结构体对象,其中包含了文件操作的基本属性,其中包括fd
fd是文件描述符,本质上是数组的下标
缓冲区
用户级缓冲区:由语言本身提供的缓冲区,由外部加载到内存
内核级缓冲区:内核用的缓冲区,将内核数据刷新到磁盘
网络缓冲区
文件缓冲区
文件页缓冲区:存储数据
文件系统缓冲区:存储元数据
FILE中就包含了文件描述符和用户级的文件缓冲区
文件系统
磁盘
物理地址(CHS地址)
磁道–Cylinder柱面
盘面–Head磁头
扇区–Sector扇区
逻辑地址(LBA地址)
- logical block address
分区
将一整个磁盘分成不同的分区进行管理
分治的思想体现
格式化
在使用磁盘前,提前对磁盘的组进行数据的初始化,有多少被占用,当前还有多少可用
提前将对于磁盘的管理信息写入文件系统中,这个写入的过程就是格式化的过程
格式化确实会清楚文件的数据内容,但是文件的管理数据从来都在,不会被清空
分区的各个组成模块
Boot Block:引导启动块,负责启动整个系统,检查相关外设信息,存储引导加载程序的代码和数据,位于起始地址,通常加载一些配置信息
Block Group:用于将文件系统的数据划分为较小的逻辑单元,以提高文件系统的性能和管理效率
Super Block:超级块,包括了文件系统的元数据信息,超级块通常有备份,磁盘的文件系统的管理转换成了在内核中对于超级块的管理
Group Descriptor Table:块组描述符表,主要是用来存储的是块组的起始位置和使用情况
Block Bitmap:位图,用来记录数据块的分配使用情况
Inode Bitmap:位图,用来记录索引节点的分配情况
Inode Table:一张用来存储文件属性的表,表中存储的是Inode结构体,有固定的大小,可以根据偏移量定位到需要的Inode
Data Blocks:一个用来存储文件内容的表,表中存在多个数据块,每一个数据块有固定大小,可以根据偏移量快速定位到需要的数据块
inode的理解
文件=内容+属性(inode)
inode中存储的是文件的属性信息,也会存储文件的数据块的指针信息,有了inode就能访问文件的内容和属性了
软链接
ln -s test.c newtest.c
相当于是快捷方式的作用,拥有自己独特的inode编号,是一个独立的文件
硬链接
不是一个独立文件,和原文件享有一个inode编号
硬链接是在当前目录下新增了一个文件名到inode的映射关系,同时引用计数加一
目录文件
创建一个目录,本质上要分配新的inode编号和数据块,并在当前目录下加入当前目录和上级目录,在上级目录新增该目录的信息,修改时间和硬链接数
创建一个文件,本质上要分配新的inode编号和数据块,在上级目录中新增文件名到inode的映射关系,更新修改时间和硬链接数
创建软链接,会在该目录下分配新的inode编号和数据块,数据块中存储的是链接的路径
创建硬链接,会在指定位置创建一个硬链接,具有原文件的inode编号以及数据内容,相当于是另外一个入口点
动静态库
打包静态库
- ar -rc <xxx.o> <xxx.a>
打包动态库
gcc -fPIC -c <xxx.c> -> 与位置无关码
gcc -shared -o <xxx.o> <xxx.so>
include/lib
include文件夹下放置的是程序所需要的头文件
lib文件夹下放置的是程序所需要的动态库
编译选项:gcc test.c -I mymath_lib/include/ -l mymath -L mymath_lib/lib
头文件的搜索路径
库的名称
库的搜索路径
动态库的理解