不谋万世者,不⾜谋⼀时。不谋全局者 ,足谋⼀域 。
——陈澹然《寤⾔》《迁都建藩议》
操作系统
一.对文件简单操作的常用基础指令
ls
ls 选项 目录或⽂件名:罗列当前⽬录下的⽂件
-l:以长格式显示⽂件和⽬录的详细信息
-a 或 --all:显示所有⽂件和⽬录,包括以.开头的隐藏⽂件和⽬录
ls -l=ll
⽂件路径
绝对路径 相对路径
pwd:查看程序员当前所处的⽂件路径
ls /root 选项:对路径root下的⽂件进⾏相关操作
./:当前⽬录
../:上级⽬录
cd
cd ⽂件路径:进⼊指定⽂件路径
cd -:跃迁⾄跳转前的路径
cd ~:返回当前⽤户的家⽬录
touch
touch ⽂件名:创建⼀个新的⽂件
mkdir
mkdir ⽬录名:在当前⽬录下创建⼀个新⽬录
mkdir -p ⼀串路径:在当前⽬录下创建⼀串新路径
*通配符:表示指定路径下拥有指定特性的所有文件
man
man 指令:查看指定指令详细信息
nano
nano test.c:创建记事本“test.c”并打开
cp
cp test1.c test2.c:将⽂件test1的内容拷⻉到test2中去
cp -r lesson2 tar:将⽬录lesson2及其所有⽂件递归式拷⻉到tar中
mv
mv dir mydir:将⽂件dir重命名为mydir
mv dir /path:将⽂件dir移动到路径path中
cat/tac
cat test.c:打印⽂件test内容
cat -n test.c:打印时显示⾏号
cat -s test.c:不打印多⾏空⾏
cat -b test.c:空⾏不计⼊⾏号计算
tac test.c:将⽂件test以⾏为单位⾃下⽽上打印
echo
echo "hello" test.c:将字符串"hello"写⼊⽂件test
重定向操作符
>:输出重定向操作符
> new.txt:新建⼀个⽂件“new.txt”
echo "a" > new.txt: 覆盖添加到⽂件中
echo "a" > new.txt:将字符"a"同时写⼊新创建的⽂件new.txt
>>:追加重定向操作符
echo “a” >> test.txt:对已有⽂件的内容进⾏扩充 append
|:流水线管道指令
find ⽂件名:查找此⽂件所在路径
which 指令:查找此指令所在⽂件路径
grep 字符串 ⽂件:将指定⽂件中所有含有指定字符串的字段打印
grep -i 字符串 ⽂件:不分⼤⼩写
grep -n 字符串 ⽂件:打印此字段所在⾏
zip:压缩
zip 233.zip 233
zip -r dir.zip dir:解压⽬录
unzip:解压
unzip 233.zip unzip 233.zip -d mydir:将⽂件233解压到mydir
nano ⽂件名:新建⼀个记事本
uname -a:查看当前机器更详细的信息
shut down:关机
二.权限
1.有关权限的常用指令
su ⽤户名:成为预期⽤户
sudo touch ⽂件名:新建⼀个超级⽤户权限⽂件
2.文件权限属性简单概要
r:读 w:写 x:执⾏ d:⽬录 -:普通文本文件
3.文件访问权限
这么做的目的是为了精细管理
4.使用chmod进行权限更改
chmod u/g/o +/- rwx
eg:chmod u-w lesson1:给⽂件lesson1对拥有者解除指定权限
u:拥有者
g:所属组
o:other
+/-:添加或去除权限
chmod u-rwx,g-rwx,o-rwx file 去除所有权限
二进制更改权限法
chmod 664 lesson1:就是更改为;110:100:100,
⽬录的权限
新建⼀个⽬录,默认权ᴴ是775(111 111 101)
新建⼀个普通⽂件,默认权限是664(110 110 100)
5.chown
更改⽂件拥有者
chown AOKANA other.zip:将⽂件other.zip拥有者更改为AOAKNA
6.权限掩码umask概要
定义:umask是⼀个三位⼋进制数,⽤于定义在创建⽂件和⽬录时默认应该屏蔽掉的权限位。
功能:通过umask设置,系统能够确定新创建的⽂件和⽬录的默认权限 umask只能禁⽤权限,不能启⽤特殊权权限,如需修改权ᴴ需使⽤chmod命令。umask存在的意义是为了解决⽂件默认权限Linux系统中,⽂件的最⼤默认权限是666(即rw-rw-rw-),表示所有⽤户拥有读写权限,但没有执行权权限。 查看umask:umask (ps: 只关⼼最后三个数字) 修改umask:umask 000~777
7.umask相关计算
为什么我们新建⼀个⽬录
默认权ᴴ是775( 111 111 101) 新建⼀个普通⽂件, 默认权限是664( 110 110 100)
三.VIM
1.vim常用操作简述
vim模式及其切换简介
vim界⾯左下⻆可以看⻅当前vim所处模式
光标所在⾏相关命令
1. 复制:yy
2.粘贴:p
3.撤销:u
4.ctrl+r:撤销u
5.剪贴:dd p
6.删除:dd 数字+dd:连续删除⼏⾏
光标快速定位相关命令
shift+g(G):将光标直接定位到⽂件结尾
gg:将光标快速定位到⽂本开头
n+shift+g(n+G):将光标定位到指定⾏
shift+4=$:将光标移动⾄⾏尾
shif+6=^:将光标移动⾄⾏⾸
w:单词为单位,向后跳转 单词:以空格为分隔的字符串
b:单词为单位,向前跳转
x:光标所在位置向右→逐个字符进⾏删除
shift+x:光标所在位置向左←进行逐个字符删除
shift+~:⼤⼩写切换
shift+r替换模式:将当前字符替换
2.如何查看vim⽀持的模式
底⾏模式 :help vim-modes
ctrl+b:向后移动⼀⻚
ctrl+f:向前移动⼀⻚
ctrl+u:向后移动半⻚
ctrl+d:向前移动半⻚
3.如何对vim文件使用gcc进行编译
gcc -o code code.c:对⽂件进⾏编译
./code:运⾏此⽂件
底⾏模式输⼊ :set number 使代码前显示⾏号
4.调试前的准备⼯作
在对⼀个代码⽂件进⾏调试前,需要⽤-g对其添加调试信息
如果我有⼀个test.c⽂件,我要对其进⾏调试,那我需要使⽤指令:gcc -g -o test test.c
(ps:使⽤此执⾏⽣成新的可调式的可执⾏⽂件之前需要先清除旧的可执⾏⽂件)
5.有关打断点的操作
gdb+某编译过后的⽂件名:对此⽂件进⼊debug模式
quit:退出出debug模式
l+代码⾏号:看到指定⾏号及其附近的代码
b+⾏号:在对应的⾏号打上断点
info b:查看当前所有断点信息
d+断点编号:删除指定编号的断点
disable+断点编号:禁⽤此段点
enable+断点编号:启⽤此段点
r:运⾏当前程序
bt:显示当前线程的调⽤栈
n:单步执⾏,不进⼊函数内部
s:单步执⾏,进⼊函数内部
p+(&)变量:打印⼀个变量的值(or地址)
until+⾏号:让代码运⾏到指定的⾏
cgdb
watch+变量:监视指定变量的值发⽣的变化
condition+断点编号+变量名==数值:添加条件断点
四.makefile简介
make是指令 Makefile是⽂件 相当于cmake
将此段代码写⼊到vim⽂件Makefile中再使⽤make可以将此⽂件直接进⾏⾃动编译 Makefile的本质就是依赖关系和依赖⽅法的结合
⼀般这么写⼀个.exe⽂件
.PHONY是⽤来声明⽬标的,clean是⼀个伪⽬标,并不是⼀个真正的⽂件名
五.进程概述
1.计算机进程概念简述
在计算机中,进程(Process)是操作系统中资源分配和调度的基本单位,简单来说,它就是一个正在运行的程序。
更通俗地讲:
-
一个程序(如 Word、浏览器)本身是静态的文件;
-
当你运行这个程序时,操作系统会为它分配资源(如内存、CPU时间等),并将它变成一个活动的执行单元,这就是进程。
2.进程概念图示
3.什么是PCB
进程:PCB,进程控制块(任务结构体) PCB就是内核数据结构
PCB(Process Control Block,进程控制块)是⼀个⽤来描述和控制进程运⾏的数据结构,它是进程
实体的⼀部分,也是操作系统中最重要的记录型数据结构
PCB是⼀个包含了进程所有关Ძ信息和状态的数据结构,⽤于操作系统的进程管理。
进程=程序的代码和数据+内核数据结构(PCB:task_struct)
进程不仅仅是将相关⽂件从磁盘加载到内存中
task_struct就是程序运⾏时其所描述此段程序相关的所有信息,然后操作系统会将所有的task_struct使
⽤相关的数据结构ᬳ接起来
最终转换为对⼀个list的"增删查改"
为什么要有PCB:OS要以先描述,再组织的⽅式进⾏管理
4.Linux下如何查看进程相关参数
将程序运⾏起来,本质就是在系统中启动了⼀个进程
进程分两种:
1.执⾏完就退出
2.⼀旦运⾏,程序员不主动关ᳮ其将不会结束(常驻进程)
有关进程监视的⼀些指令
ps -e:显示所有进程
ps -ajx:显示所有⽤户终端控制进程,可⽤于查看PID,PPID
ps -ajx | head -1:提取⾸条进程快照
ps ajx l grep process 常⽤的查进程的指令
kill 9+pid:结束⼀个进程
ls /proc/28824:只要我们启动⼀个进程,此进程便会以其pid作为⽂件名放⼊到⽬录proc中以便管理员查
看(ps:这里的28824是⼀个pid)
proc并不存在磁盘上,它是由内存动态⽣成的数据
5.如何查看pid
查看子进程
查看父进程pid
6.fork⽗进程⼦进程的机制
Linux下,新创建的任何进程都是由⽗进程创建的
如何在vim中查看ppid
bash:命令⾏解解释器
六.使⽤系统调⽤创建进程
1.有关fork()函数简单介绍
fork 函数的返回值⽤于区分调⽤进程(⽗进程)和新创建的进程(⼦进程)。具体来说:
当 fork 在⽗进程中被调⽤时,它会返回新创建的⼦进程的进程ID(PID),这个值是⼀个正整数,⼤于零。
当 fork 在⼦进程中被调⽤时(实际上,⼦进程是从fork调⽤点开始执⾏的副本),它会返回0。
如果 fork 调⽤失败,它会返回-1,并设置errno来指示错误原因。
⼦进程会继承⽗进程的内核数据结构 还有代码段 数据段 堆栈段 包括变量fd等
⽗⼦进程不同点有 进程ID(PID) 内存空间 ⽂件偏移量 执⾏路径 系统资源
fork->两个进程->此⼆进程为⽗⼦关系->此⼆进程代码共享,但数据相互独⽴
2.进程的几个重要特性
继承性:⼦进程在创建时会继承⽗进程的环境和资源,但并⾮所有资源都会被继承。例如,
⼦进程会继承⽗进程的代码段和数据段,但常会获得⾃⼰的独⽴堆栈空间
独⽴性:⼀旦创建,⼦进程便独⽴于⽗进程运⾏。这意味着⽗进程的终⽌不会影响⼦进程的
运⾏(除⾮有特别的设置,如某些操作系统中⽗进程可以发送信号给⼦进程要求其终⽌)
反之亦然。
所以说⽆论啥进程,它们运⾏之间是相互独⽴的 所以其代码权限为只读 数据各⾃私有⼀份
七.操作系统进程状态
1.并⾏和并发
并发:多个进程在单个 CPU下⽤进程切换的⽅式,在⼀段时ᳵ内,让多个进程得以轮流运⾏
并⾏:多CPU的服务器可以同时运⾏多个进程(或CPU和其他硬件同时运⾏)
a.cpu执⾏代码,是给每⼀个进程预分⼀个时间⽚,基于时间⽚进⾏调度轮转
2.时间⽚
⺠⽤级别OS是分时OS,(⽬的是为了任务公平)
对应的还有实时操作系统,
3.进程具有独⽴性
4.等待的本质(power point):ᬳ⼊⽬标外部设备,CPU不调度
运⾏:只要PCB在队列中,该进程便被叫做运⾏状态
就绪和运⾏⼀般情况下是合⼆为⼀的
挂起:阻塞期间,如果OS内存不⾜,OS便会将特定等待外设的PCB换出到磁盘当中,磁盘中有专
门的swap分区⽀持此操作
被换出的PCB是处于阻塞挂起的状态
2.进程运行示意图
3.如何查询一个进程状态
ps aux:⽤于实时显示系统中各个进程的资源占⽤状况,包括CPU使⽤率、内存占⽤情况等
按键操作:在top界⾯下,⽤户可以通过按Ძ进⾏交互操作。例如,按P键可以按照CPU使
⽤率进⾏排序,按M可以按照内存使⽤率进⾏排序。
退出:按下qᲫ可以退出top命令。
3.进程优先级
ps -la:最终优先级的设置=PRI(default 80)+NI(NICE)
nice∈[-20,19]
所以说调整进程优先级可同过调整nice值进⾏控制
⽂件权ᴴ管理的实现原则就是通过进程的UID(⽤户标识符)和⽂件的拥有者所属组ID进⾏对⽐从⽽实现权限控制
⽂件可追溯是谁的,进程可追溯知道是谁的,⽽这进⾏对⽐,进⽽进⾏⽂件的权限管理
4.进程切换概述
5.进程切换流程的两个核⼼步骤:进程上下⽂数据的保存和恢复和CPU ⼯作顺序
6.Linux下进程调度基本算法图示
active和expried的协作
每个PCB⼀开始在active中,时间⽚⼀到便会进⼊expried中
active队列永远是⼀个存量进程竞争的情况(PCB减少)
时间⽚⼀到 active的进程进⼊expried对列
到达⼀定程度后,active指针和expired指向会互相交换指向内容,如此循环
八.命令行参数argc与argv
1.命令行参数简介
argv[0]常代表程序的名称
当你从命令⾏运⾏⼀个程序时,
操作系统会将程序的名称作为第⼀个参数传给该程序,
这个名称是⼀个字符串,
它包含了程序的路径(如果是从某个⽬录运⾏的话)和程序的⽂件名
argv[0]:程序的名称(可能包含路径)。
argv[1]:传给程序的第⼀个命令⾏参数。
argv[2]:传给程序的第⼆个命令⾏参数。
...
argv[argc-1]:传给程序的最后⼀个命令⾏参数。
argv[argc]:这是⼀个NULL指针,⽤于标记数组的结束。
它不是⼀个有效的参数,⽽是⽤作哨兵值,
以确保函数可以安全地遍历数组⽽不会越界。
2.命令行流程
main参数是谁传递的
所以指令不仅仅是指令,也是进程
user在命令⾏输⼊的是字符串,此字符串⾸先被shell(命令⾏解释器)拿到,shell将这些字符串按照空格打散,放⼊⼀张表(argv)并计算出⼀个参数argc
3.环境变量
环境变量是OS中的⼀些基本信息,具有全局属性
作⽤于存储配置信息
为系统的程序运⾏提供必要⽀持和保障
PATH:定义可执⾏⽂件查找路径
HOME:⽤户登录时,当前所在⽬录会设置成他们的家⽬录
SHELL:
PWD:保存当前进程所在的⼯作路径
LANG 和 LC_* 系列:控制程序的语⾔和地区设置。
TMPDIR:指定存放临时⽂件的⽬录。
4.环境变量与本地变量
本地变量(也称为局部变量)⼀般是由⽤户(即程序员)在编写程序时⾃定义的。
这些变量在函数或代码块的内部被声明,⽤于存储临时数据或作为函数执⾏过程中的中ᳵ结果。
5.杀进程
kill -9 1234 杀进程
向进程ID为1234的进程发ᭆSIGKILL信号,强制终⽌它。
常⻅信号(os发信号来杀进程)
SIGTERM (15) 请求终⽌进程,可以被捕获和处理。默认信号。
SIGKILL (9) 强制终⽌进程,不能被捕获,阻塞或忽略。
SIGHUP (1) 常⽤于让进程新读取配置⽂件。
SIGINT (2) 中断进程(常由Ctrl+C产⽣)。
SIGSTOP (19) 停⽌进程的执⾏,但保持其状态不变。
SIGCONT (18) 继续执⾏已经停⽌的进程。
6.进程的终止
exit(状态码) 退出状态码:⽤于判定代码跑完,结果对不对
return 0中的0是退出码
exit(0) 表示程序成功执⾏并正常退出。
⾮零的退出状态码 exit(失败状态码)
进程退出由退出码和退出信号值进⾏整体评估
ps:⼦进程只有程序执⾏失败才会执⾏exit
7.waitpid
等待的⽬的是为了房⽌僵⼫进程且获取⼦进程退出信息(为了让⽤户⾃⾏决定某些东⻄)
允许⽗进程等待⼦进程结束,
如果id为-1,则waitpid将等待任何⼀个⼦进程结束。
如果id为0(⼦进程),则等待与调⽤进程属于同⼀个进程组的任何⼦进程。
如果id⼤于0(⽗进程),则等待具有该PID的⼦进程。
NULL:这是waitpid函数的第⼆个参数,⽤于存储⼦进程的结束状态
返回值:成功则返回⼦进程号 0代表⽆已退出⼦进程等待(WNOHANG)
8.图示父进程和子进程间运作关系
9.写时拷贝简介
数据需要被写⼊才进⾏拷⻉操作 此前 对个对象或内存共享同⼀内存数据
10.进程程序替换
进程替换:进程数据结构不变 但是代码数据替换
调⽤接口execl后,此接⼝会⽤指定代码和数据替换调⽤此接⼝的代码和数据
新程序的代码和数据覆盖⽼程序的代码和数据
execl("带路径的⼆进制可执⾏程序","命令","选项",...);
成功时execl⽆返回值,失败了,覆盖失败返回-1
execl本质是将程序加载到内存
九.⽂件操作
1.将⽂化内容打印到显示器
printf()
fputs("a",stdout):将字符(串)"a"输⼊到指针(流)所指向的⽂件(屏幕)中
fwrite("a",1,4,stdout):将字符串"a"写⼊到标准输出流stdout中去
fprintf(stdout,"a")
2.flag
int open(char *pathname,int flag)
flag ⽂件打开标志 本质是宏
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:读取+写⼊
O_CREAT:创建⽂件
O_APPEND:向⽂件末尾追加(append)内容
注意:O_APPEND标志᭗常与O_WRONLY或O_RDWR一起使⽤。
int open(const char*pathname,int flags,mode_t mode);
mode_t mode:赋予此⽂件的"起始权限"
如果希望mode是最终权限,可以 umask(0)
进程的umask默认来⾃系统,如果在进程中设置了,那么就近原则
write(fd1, message, strlen(message));
将字符串message存储的数据写⼊到fd1所打开的⽂件
写⼊⼤⼩由strlen(message) 字符串实ᴬ⼤⼩所决定
3.fd简介
文件描述符是一种整数,用于标识一个打开的文件或 I/O 资源。
在 Unix/Linux 系统中,几乎一切皆文件,不仅是普通文件,包括目录、套接字、管道、终端、网络连接等都可以用 fd 表示。
每当你打开一个文件,操作系统会:
-
把这个文件信息记录到内核的一个文件表里;
-
返回一个整数给你,这个整数就是文件描述符(fd)。
你之后对文件的操作(读、写、关闭等),都是通过这个 fd 来进行的。
4.read write和fd如何形成联动进行文件数据的访问
、
fd:⼀个整数 File Descriptor 可看作是⽂件的下标
stdin(标准输⼊):⽂件描述符为0,常⽤于接收⽤户输⼊。
stdout(标准输出):⽂件描述符为1,常⽤于程序的正常输出。
stderr(标准Კ误):⽂件描述符为2,常⽤于程序的错误输出。
ssize_t read(int fd, void *buf, size_t count);
fd:⽂件描述符,唯⼀标识⼀个打开的⽂件。
buf:⽤户空ᳵ缓冲区指针,⽤于存储读取的数据。
count:希望读取的字节数。
返回值:实ᴬ读取的字节数,如果读取失败则返回-1
ssize_t write(int fd, const void *buf, size_t count);
fd:⽂件描述符,唯⼀标识⼀个打开的⽂件。
buf:⽤户空ᳵ缓冲区指针,指向要写⼊的数据。
count:希望写⼊的字节数。
返回值:实际写⼊的字节数,如果写⼊失败则返回-1
4.进程对文件系统进行访问简图
fd⽂件标识符是⼀个数组下标 fd是⽂件描述符表的索引 ⽬的是为了使进程快速定位到对应的⽂件表项
其⽬的是为了让⽂件和进程ᳵ产⽣关联 ⽂件表项包含了关于打开⽂件的信息 如状态标志(只读只写) 当前offset等
这是进程访问⽂件的唯⼀⽅式
c语⾔⽂件访ᳯ相关函数FILE就是对fd的封装 这样可以免user去记忆⽂件fd的麻烦
stdin stdout stderr就是cpp函数对⽂件类型的封装 成为struckt_file类型 任何语⾔要对fd=0 1 2进⾏封装
封装⼀⽅⾯是为了⽅便 另⼀⽅⾯是为了可移植性(跨平台性)
4.外存设备的文件管理系统概述
对于任何⼀个⽂件
其内容本身就是⼀个巨⼤的⼀维数组
标识⽂件任何⼀个区域
使⽤偏移量(offset)+⼤⼩的⽅式
偏移量(Offset)指的是从某个点开始
到某个位置ᳵ距离
偏移量主要作⽤于定位数据
偏移量就是⽂件开头到当前读写位置的字节数
5.LBA的计算公式
LBA的计算公式 LBA的计算公式为:LBA = NH × NS × C + NS × H + S - 1
其中: NH为磁头数(Heads Per Cylinder,HPC),即每个柱⾯的磁头数或磁᭲数。 NS为每磁᭲扇区数(Sectors Per Track,SPT),即每个块的扇区数。
C、H、S分别为柱⾯、磁头和扇区的编号 LBA=块号*8+[1,8]
6.磁盘对文件的分组管理图示
7.Block group简介
和启动有关
超级块(Super Block):就是⽂件系统 ⽤于表示整个分区的整体情况 影响整个分区 是⼀个分区⼼脏
Super Block挂掉后系统会⽤其他分区的Super Block来恢复它
存储⽂件系统本身的元数据,如块和inode的总数、未使⽤的块和inode的数ᰁ、块和inode的⼤⼩ 等
是⽂件系统的核⼼结构之⼀,如果超级块被破坏,整个⽂件系统结构可能会受到影响。
块组描述符(Group Descriptor Table, GDT):对以下四个信息进⾏管理
描述块组的属性信息,如块位图、inode位图、inode表和数据块的位置等。
在具备Flexible Block Groups特性的⽂件系统中,块组描述符⽤于描述管理数据(如位图)的位置。
块位图(Block Bitmap):删除数据就是将BB和IB中⽂件对应编号和数据块所占位图据⽐特位由1变0
记录数据块的使⽤情况,即哪些数据块已经被占⽤,哪些数据块还未被使⽤。
占⽤⼀个逻辑块的存储空间(常为4KB),其中每⼀位表示⼀个数据块的状态。
inode位图(Inode Bitmap):和inode table中的imode形成映射,对inode进⾏管理 Block Bitmap同理
记录inode表的使⽤情况,即哪些inode已经被分组,哪些inode还空ᳳ可⽤。
与块位图类似,也占⽤⼀个逻辑块的存储空间。
inode表(Inode Table):inode以分区整体为单位进⾏分组 分组时只要确定起始inode即可
每组inode固定 inode保存在GDT中 ⽂件加载到内存中本质是将其inode加载到内存中
以列表形式保存⽂件的元数据,包括⽂件的inode号、⼤⼩、扩展属性和访ᳯ时ᳵ等。
inode表的⼤⼩根据⽂件系统的属性和格式化时的设置⽽定。
数据块(Data Blocks):
存储⽂件数据的区域,是⽂件内容的实际存放位置。
数据块的数ᰁ和⼤⼩取决于⽂件系统的设计和配置。
8.有关Inode Table和Data Block的映射
9.磁盘对文件的操作
如何查找⼀个⽂件
先根据inode确定在哪个group(GDT)中
再⽤inode-start_node=inode bitmap
由此再确定inode table,然后也找到了data block
最终查找到想要的⽂件
OS查找⽂件夹是对绝对path⾃左向右遍历进⾏的
如何删除⼀个⽂件
找到后将bitmap由1->0,将block bitmap由1->0
如何修改
找到后对inode table,或者data block进⾏修改
如何新增
先将inode_bitmap和block_bitmap由0->1
申请完块后将属性填⼊,再对GDT进⾏更新
再返回⼀个inode给⽤户
10.有关目录dentry概述
⽬录的数据块存储⽂件名(inode的映射关系),根据⽂件名和inode的映射关系从⽽对其他⽂件进⾏查找,
之后属性内容便全有了
⽬录的data block中存储的其⼦⽂件的inode path
rwx如果没有r,就是系统不让你去读取此⽂件的data block,你也就⽆法获取inode和⽂件名的映射关系
Linux中不需要存储路径,他只要再Data Block中存储的⽂件名与inode的映射关系
路径解析:
dentry⽤于存储⽂件名和相关的⽗⽬录指针,帮助内核在⽂件系统的⽬录树中解析路径
OS对⽂件path缓存:每⼀个⽂件都有dentry 这样对⽂件的管理就可以转换为对dentry cache的管理
dentry缓存了⽬录项的信息,减少了复查找⽬录项所需的磁盘访问次数
缓存的dentry对象存储在dentry cache(也称为dcache)中,这是⼀个全局的哈希表,允许快速查找。
dentry 内存级别内核数据结构 在OS中是⼀个dentry tree⽤于path的cache
快速查找:⽬录的data block中存放的⽂件名 inode和⽂件名可以形成映射关系
当⼀个路径被解析时,内核会⾸先尝试在dentry cache中查找相应的dentry对象。如果找到,内核可以直接使⽤它
⽽⽆需再次访ᳯ磁盘。
如果dentry缓存中不存在所需的条⽬,内核会进⾏磁盘访ᳯ来查找,并将找到的条⽬添加到dentry cache中。
⽬录项管理:
dentry对象与⽂件系统上的实际⽬录项相对应,并且包含指向inode的指针
通过dentry和inode的结合,内核可以⾼效地管理⽂件和⽬录。
命名和删除:
dentry结构还⽤于处理⽂件᯿命名和删除操作。这些操作会相应地更新dentry缓存和⽂件系统上的⽬录项。
符号接解析:
当解析⼀个符号᱾接时,dentry结构会⽤于存储符号᱾接的⽬标路径,并帮助内核进⾏进⼀步的路径解析。
task_struct->FILE->path->denty->inode->外存
如何确认你在哪个分区下
访问任何⽂件都需要路径, ⽽这个路径的前缀就是分区
11.软硬链接
如何理解软硬᱾接
a.软链接是独⽴的⽂件 有独⽴的inode 内容上 保存的是⽬标(其指向)⽂件的路径 类似于Windows的桌⾯快捷⽅式
b.硬链接本质上是inode和数据块中⽂件名的映射关系
硬链接不是独⽴⽂件⽆独⽴的inode 其本质就是⼀组⽂件名和⼀组已经存在的⽂件
是同⼀个⽂件的两个不同名字。删除其中⼀个⽂件不会影响到另⼀个⽂件或⽂件的内容。
硬᱾接数(引⽤计数)
11.软硬链接存在意义
a.软链接
如何创建⼀个软链接 ln -s [⽬标⽂件或⽬录] [软链接名称]
如何删除⼀个软链接 unlink/rm 软链接名
b.硬链接
如何创建⼀个硬链接
硬链接数=当前⽂件(./ 1个点)+此路径下⼀个⽂件(../ 2个点)+此路径下⼀个⽂件的下⼀个(.../)...
硬链接的存在很好的解决了⽂件备份的题 但是Linux不允许对⽬录建⽴硬链接其⽬的是为了防止环状路径
软链接可以是因为系统对软链接不做处理
软链接可以看作是⼀个简化的⽂件路径
将user想要快速访问到的⽂件的路径变成⼀个⽂件放在易于接触到的区域
就⽐如桌⾯
硬᱾接就是⽬标⽂件的倒影
对硬᱾接的数据块会inode进⾏改动其⽬标⽂件对应数据也会发⽣改动
十.动态库和静态库:库就是.o⽂件的打包
1.如何使⽤动态链接库
a.编写源代码
b.编译成⽬标⽂件
使⽤-c选项编译原⽂件 但不进⾏᱾接
gcc -c -fPIC mylib.c -o mylib.o
-fPIC选项⽣成与位置⽆关的代码
Position Independent Code
这是创建共享库所必需的。
共享库(Shared Library)是⼀种特殊的程序库,
它在操作系统的内存中只被加载⼀次,
但可以被系统中的多个程序同时使⽤o
c.创建共享库
使⽤-shared选项⽣成共享库⽂件
gcc -shared -o libmylib.so mylib.o
2.如何使⽤静态链接库
a 编写源代码 mylib.c
b 编译成⽬标⽂件 使⽤-c选项编译源⽂件
gcc -c mylib.c -o mylib.o
c 创建静态库 使⽤ar⼯具⽣成静态库⽂件
ar rcs libmylib.a mylib.o
3.动静态链接库在运行时的差别
动态链接库:在运⾏时加载,
需要设置运⾏时库搜索路径(如LD_LIBRARY_PATH)
静态链接库:在编译时嵌⼊到可执⾏⽂件中,
运⾏时不需要额外的库⽂件
4.elf形成原理概括
size ⼆进制⽂件:⽤于查看⼆进制⽂件⼤⼩信息
静态库在编译时被᱾接到程序中(和其他程序᱾接打包成ELF) ⽽动态库则在程序运⾏时被加载到内存中。
5.elf组成概括
elf格式图
6.elf和静态库archive在内存中的加载
elf还未加载到内存时,已经按照[000,FFF]进⾏编址了
这就是虚拟地址
所以虚拟地址不仅是OS在内存中形成进程时才会⽣成
在编译器编译时就形成了
平坦模式下,逻辑地址=起始地址(为0)+虚拟地址 ps:主流OS皆为平坦模式(offset=0)
逻辑地址(磁盘 ELF) == 虚拟地址(加载到内存中)
程序内存依靠虚拟地址来跳转函数
mm_struct的<未初始化数据><初始化数据><正⽂代码>区域的数据是
加载elf的各个section的各个具体adress得来的
7.有关页表的图示介绍
mm_struct是什么
加载⼀个ELF⽂件只需要将它的⼊⼝地址加载⼊内存即可
如果CPU在寻址时虚拟地址匹配失败未转化为物理地址
进⾏懒加载section即可
(pc:当前正在执⾏的指令虚拟地址的寄存器)
在编址时,ELF的头部就已经记录了Enter point address
(整个程序的⼊⼝地址,放⼊pc即可)「_start」
CR3:保存⻚表起始物理地址的寄存器 ⽤于切换进程
EIP:将pc中的地址通过MMU转化为物理地址
8.动态库的加载的理解
动态库加载到内存需要OS管理
因为OS中可能同时存在多个库
管理⽅式依旧是「先描述 后组织」
十一.通信
1.进程间通信IPC⽬的和⼿段
进程间通信存在⽬的:
数据传输 资源共享 通知事件 进程控制
进程间通信前提是让不同进程看到同⼀份资源
同⼀份资源
某种形式的内存空间
提供资源者有且仅有OS
2.进程间通信分类
管道
anonymous pipes
命名管道
System V(std) IPC
system V 消息队列
system V 共享内存
3.pipe简单图示意
4.本地间通信
a.进程间通信IPC⽬的和⼿段
进程间通信存在⽬的:
数据传输 资源共享 通知事件 进程控制
进程间通信前提是让不同进程看到同⼀份资源
同⼀份资源
某种形式的内存空ᳵ
提供资源者有且仅有OS
b.anonymous pipe⼯作原理概述
特性
若pipe为空且正常 读取数据会阻塞
管道为满且管道正常 表示会阻塞
pipe写端关闭且读端继续 读端读到0 表示读到⽂件结尾
管道写端正常 读端关闭 OS会直接杀掉写⼊进程 (OS会给写端的进程发信号 13)SIGPIPE)
5.进程间通信system V共享内存
6.共享内存的创建与使⽤
共享内存获取函数shmget的使⽤
shmget(key_t key,size_t size,int shmflg);
size:user期望⽣成的shm⼤⼩
shmflg:
IPC_CREAT:单独使⽤此选项,如果shm不存在,创建,如果存在,则获取它并返回————保证进程可以拿到共享内存————获取共
享内存选项
IPC_EXCL:存在就出错
IPC_CREAT|IPC_EXCL:若shm不存在,那么就创建它,如果存在,则出错并返回————只要成功就⼀定是新的shm(不使⽤旧shm)
————创建共享内存选项
IPC_EXCL 单独使⽤⽆意义 ⼆者需要组合使⽤
key:user输⼊
如何保证shm唯⼀性:让shm的标识符key不同即可
如何保证不同的进程看到的是同⼀个共享内存:这个key为何不让OS⾃⼰形成?
如果由操作系统形成,那么将由⼀个进程形成另⼀个进程则⽆法获取到此key
返回值:shmflg成功后会返回获取的shm标识符(int)(成功返回值和fd(⽂件下标,shm本质上是⽂件)⼀样)
shmat:让进程虚拟地址和shm标识符形成关联
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:需要挂接的user创建的共享内存(下标) shmget成功返回值
shmaddr:由user指定,挂接到指定虚拟地址上(暂时不需要,程序应⽤
层不强调虚拟地址空指针这⼀概念,故shmaddr=nullptr即可)
shmflg:设置为0即可
返回值:因为是挂接到地址上,所以返回值类型是共享内存地址(指针)
若成功:返回挂接的共享内存段的起始地址,⽅便user后续对共享内
存进⾏操作
若失败:返回-1
shmdt:让shm下标和进程虚拟地址去关联
int shmdt(const void *shmaddr);
shmaddr:shmat的返回值
和哪⼀个地址去关联,所以传⼊地址
返回值:成功返回0,失败返回-1
shmctl:控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:shm的下标
cmd:控制命令,⽤于指定要执⾏的操作
常⽤的命令包括IPC_RMID(删除共享内存段)和IPC_SET(修改共享内存段的属性)
IPC_STAT(⽤于获取IPC对象状态)
buf:指向struct shmid_ds结构的指针,⽤于存储或获取共享内存段的信息
可以为NULL,表示不获取或设置共享内存段的信息。
返回值:成功返回0,失败返回-1
msgsnd,msgqid消息信号send
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
返回值:成功返回0,失败返回-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
msgqid:消息队列的id msgp:⾃定义结构体对象
msgsz:消息块⼤⼩
msgtyp:指定要接收的消息类型.如果msgtyp为0,
则接收队列中的第⼀条消息(⽆论类型).
如果 msgtyp⼤于0,则接收类型等于msgtyp的第⼀条消息
msgflg:0
返回值:
成功时,msgrcv 返回接收到的消息的实际字节数
(不包括消息类型和消息长度字段)
失败时,返回 -1,
7.信号量队列常用接口概述
semget信号量的创建
int semget(key_t key, int nsems, int semflg);
key:ftok
nsems:指定要创建的信号量集中信号量的数量(user所需信号量数)
要保护的shm个数
semflg:IPC_CREATE,IPC_CREATE|IPC_EXCL
返回值:
成功时:semget 返回⼀个⾮负整数,即信号量集的标识符(semid)
这个标识符在后续对信号量的操作中会被⽤到。
失败时:semget 返回 -1
semctl信号量的删除
int semctl(int semid, int semnum, int cmd, ...);
semid:信号量集的标识符,通过 semget 函数获得。
semnum:信号量的下标(从0开始)
cmd:指定要执⾏的控制操作的命令
... :取决于 cmd 的值,
这个参数可能是⼀个指向 union semun 的指ᰒ
该联合体⽤于传递接收与命令相关的数据
十二.信号量
IPC资源必须主动删除 其⽣命周期随内核 信号ᰁ不同于信号 信号量本质上是⼀个计数器
1.sem的pv操作
对多个信号量的pv操作(申请资源和释放)
int semop(int semid, struct sembuf *sops, size_t nsops)
semid:通过semget⽣成的信号标识符
*sops:数组
nsops:数组个数
2.C语⾔使⽤继承多态封装的⽅式构建ipc
在OS层⾯ IPC是同类资源
IPC资源⼀定是全局资源(变量)
3.查看ipc属性
十三.信号的产⽣
1.
前台进程受user的操作⼲扰:ctrl+z是终⽌前台进程 后台进程不会 依旧被解释
如何让后台进程运⾏⽽不显示:nohup ./sig &
将该进程后台运⾏并打印到nohup.out⽂件中去
1.设置信号处理函数signal
作⽤:⾃定义信号处理
typedef void (*sighandler_t)(int);
void (*sighandler_t)(int):函数指针类型
sighandler_t signal(int signum, sighandler_t handler);
signum:要处理的信号编号,可以写⼊1-31号指令数字或指令
如,SIGINT表示中断信号(由 Ctrl+C 产⽣),SIGTERM表示终⽌信号。
sighandler_t handler:输⼊信号要考传递函数指针
返回值:
成功时,返回之前的信号处理程序的地址
(如果之前未设置,则可能是SIG_ERR或SIG_DFL)
失败时,返回SIG_ERR,并设置errno以指示错误原因。
kill -l:信号查询指令,1-31为重点 其本质为宏定义
2.signal的使⽤
默认终⽌->执⾏⾃定义⽅法:Handler
SIGINT效果:按下ctrl+c后给前台发送号信号后执⾏Handler函数
SIGQUIT效果:按下ctrl+\后给前台发送3号信号后执⾏Handler函数
3.信号产⽣的三种⽅式
4.raise函数
发送信号给进程本身的函数
int raise(int sig);
int sig:这是要发送的信号。
使⽤信号进⾏进程终⽌
向⾃⼰发送终⽌信号
有点类似于exit
void abort(void);
6) SIGABRT
5.OS如何通过信号知道CPU出错
十四.信号的保存
1.信号的捕捉signal
2.信号的处理
delivery:信号处理也叫信号递达
pending:产⽣到递达之ᳵ家谱信号未决,也是信号保存
block:进程可以选择阻塞特定信号,阻塞信号也叫屏蔽信号
信号⼀直处于pending状态,会被保存起来
阻塞信号就是将信号置于等待处理的状态
被阻塞的信号将保持在pending状态
ps:忽略不等于阻塞,阻塞在递达之前,忽略在递达之后
signal_t也叫信号屏蔽字
信号操作函数:对信号集进⾏⽐特位级别调整
delivery:信号处理也叫信号递达
block(信号屏蔽字):block作⽤是屏蔽(阻塞某些信号)
修改block位图,进程可以控制哪些信号到达,哪些信号忽略或延后处理(预设处理⽅法)
进程可以选择阻塞特定信号(阻塞信号也叫屏蔽信号)
阻塞的信号会处于pending状态,信号⼀直处于pending状态,会被pending表保存起来
pending(未决信号集):⽤于保存被block的信号
产⽣到递达之ᳵ家谱信号未决(进程⽆法接受或处理信号)
pending位图⼀般⽤于记录已发送到进程但未处理的信号(阻塞)
ps:忽略不等于阻塞,阻塞在递达之前,忽略在递达之后
signal_t也叫信号屏蔽字
handler:保存每个信号的处理⽅法
3.信号的保存位图简介
4.对信号block中屏蔽字的操作
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
读取或更改进程的信号屏蔽字,对block表进⾏操作
how:
SIG_BLOCK将set中所有信号添加到block表中,阻塞这些信号
SIG_SETMASK将新的屏蔽字覆盖到⽼的屏蔽字上(set)
SIG_UNBLOCK解除屏蔽
set:指定要更改的信号集
oldset:保存旧的信号屏蔽字,以便恢复
int sigpending(sigset_t *set);
获取当前信号的pending信号集
set:这是⼀个指向 sigset_t 类型变量的指针
函数会将当前阻塞且未处理的信号集合写⼊这个变量中、
十五.信号的处理
信号⼀般何时处理?
进程从内核态切回⽤户态 监测当前进程的pending和block
决定是否结合handler表处理信号
1.信号处理流程简图
2.有关用户态和内核态
3.信号处理函数signation
#include <signal.h>
int sigaction(int signum,
const struct sigaction *act, struct sigaction *oldact);
signum:要处理的信号编码
act:指向⼀个struct sigaction结构体,
该结构体指定了新的信号处理⽅式
oldact:保存原有的信号处理⽅式
struct sigaction {void (*sa_handler)(int); //信号处理程序,不接受额外数据void (*sa_sigaction)(int, siginfo_t *, void *);//实时信号处理 略sigset_t sa_mask; //阻塞关Ძ字的信号集int sa_flags; //影响信号的⾏为 默认为0int (*sa_restorer)(void);//略
};
4.可重⼊函数
可重⼊函数简单定义
⼀个函数被两个及其以上执⾏流重复进⼊
执⾏流是指程序在运⾏过程中的控制流程,即程序执⾏路径或执⾏顺序,其描述了程序指令被CPU执⾏的过程
哪些函数属于可重入函数
1 使⽤了全局资源为不可重⼊函数
2 不使⽤任何全局资源 所有变量是临时的 则此函数为可重⼊函数
九成函数皆为不可重⼊函数
函数名带_r为可重⼊函数
5.易变关键字volatile
volatile⽤于告诉编译器某个变脸的值可能在程序控制之外改变
这通常⽤于寄存器或内存映射的I/O 或⽤于多线程编程中共享变量
volatile可停⽌编译器对该变量的优化 确保每次访ᳯ该变量从其内存地址读取其值
所以易变关键字⼀旦修饰某变量 则该变量变不可优化进⼊编译器 以保持内存可⻅性
6.SIGCHILD
子进程退出时会给父进程发送信号SIGCHLD
signal(SIGCHLD, SIG_IGN) 对SIGCHLD进行忽略
在 Linux 和类 Unix 操作系统中,SIGCHLD 是一个信号,用于通知父进程其一个子进程的状态已经发生了变化。
这个信号通常用于以下几种情况:
-
子进程停止:当子进程停止(例如,因为接收到 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU 信号)时,父进程会收到 SIGCHLD 信号。
-
子进程继续运行:当之前停止的子进程继续运行(例如,因为接收到 SIGCONT 信号)时,父进程也会收到 SIGCHLD 信号。
-
子进程退出:当子进程终止(调用 exit 或因为接收到致命信号而退出)时,父进程会收到 SIGCHLD 信号。
SIGCHLD 信号的处理通常涉及以下几个方面的操作:
-
清理子进程资源:父进程可以通过 wait 或 waitpid 系统调用来获取子进程的退出状态,并释放系统资源(例如,子进程的进程表项)。
如果父进程不调用 wait 或 waitpid,子进程会变成僵尸进程(zombie process),仍然占用系统资源。 -
监控子进程:父进程可以使用 SIGCHLD 信号来监控子进程的状态,并根据需要进行相应的处理。
例如,如果子进程因为错误而退出,父进程可以记录错误日志或重新启动子进程。
在默认情况下,SIGCHLD 信号通常会被忽略(在某些系统上,如 Linux,默认行为是将其设置为 SIG_IGN)。
这意味着父进程不会自动收到这个信号的通知,除非它显式地通过 signal 或 sigaction 函数来捕捉或设置这个信号的处理函数。
⽗进程不等待⼦进程
⼦进程执⾏独⽴任务或⽗进程需要继续执⾏任务时⽗进程等待⼦进程 ᭗过设置sigaction结构体中的sa_flags为SA_NOCLDWAIT, 使得⽗进程在⼦进程结束时不会收到SIGCHLD信号, 并且⼦进程的结束状态会被⽴即丢弃,从⽽免产⽣僵⼫进程
⽗进程等待⼦进程
十六.线程
1.进程概念的formulate
定义:线程是进程内部的执⾏分⽀ 进程是线程的容器
a:进程是承担分成sys资源的基本实体
b:线程是sys调度的基本单位
⽤于划分进程申请的各项sys资源
2.多线程的创建及其运⾏机制
3.OS的内存管理
页表图示
⼆级⻚表
MMU⼯作简图
缺页中断概述
缺⻚中断的应⽤
new和malloc就是对虚拟地址空间申请
或对Heap扩容 并对⻚表维护
处理过程 所以申请内存本质是填充⻚表
处理过程 所以申请内存本质是填充⻚表
缺⻚中断的处理过程包括以下⼏个步骤:
保存上下⽂:操作系统保存当前进程的上下⽂信息,以便在处理完缺⻚中断后能够恢复执⾏。
查找⻚⾯:操作系统检查该⻚⾯是否已被换出(swapped out)到磁盘上的交换空间(swap space)或其他地⽅。
⻚⾯调⼊:
如果⻚⾯在交换空ᳵ中,操作系统会将其从磁盘读⼊物理内存。
如果⻚⾯根本不存在(如⾮法访ᳯ),操作系统可能会引发⼀个错误并终⽌进程。
更新⻚表:将虚拟地址对应的⻚⾯映射到物理内存中的新位置,并更新⻚表。
恢复上下⽂:恢复被中断进程的上下⽂信息,并继续执⾏被中断的指令。
4.线程的控制及其周边问题
Linux没有线程的概念,其仅有LWP,线程是⽤LWP模拟的,所以Linux不会提供线程接⼝,仅提供创建LWP的接⼝vfork
LWP系统调⽤函数vfork()
#include <unistd.h>
pid_t vfork(void);
作⽤:创建⼦进程,和⽗进程共享地址空ᳵ
参数:不接受任何参数
返回值
成功时,vfork()在⽗进程中返回⼦进程的PID。
在⼦进程中返回0.这⼀点和fork函数⼀致
出Კ时,返回-1,并设置 errno 以指示错误类型。
fork和vfork是对clone函数的封装
所以vfork创建出来的是⽤户级线程
只有Windows有真正意义上的PCB和TCB
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...);
clone函数会根据参数flags接受到的选项来定夺
创建拷⻉空间⼦进程还是共享空间LWP
线程创建函数pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_t *thread:这是⼀个指向 pthread_t 类型变量的指针
可看作主线程对新线程操作时使⽤的对象名
const pthread_attr_t *attr:
这是⼀个指向线程属性对象的指ᰒ,可以传递 NULL,此时使⽤默认属性
void *(*start_routine) (void *):也可看作是新线程执⾏任务的具体⽅式
回调函数,这是⼀个指向函数的指ᰒ,该函数是新线程的起始执⾏点
void *arg:
这是传递给 start_routine 函数的参数。它的类型和内容由程序员定义
如果成功,pthread_create返回0
如果失败,它返回⼀个错误代码(⾮零值)
pthread_create的使⽤
5.多线程
线程异常
线程出现指针之类的Კ误会崩溃
随之进程也会崩溃 此进程下的其它线程也崩溃
调⽤syscall触发软中断系统回收进程资源
同⼀进程下 ⼀线程崩溃所有线程都崩溃
异常的本质就是触发软中断 向⽬标进程发信号
线程等待和回收
线程创建之后也是需要等待和回收的
a.线程被创建后不等待会出现类似僵⼫进程的问题
ps 内存泄漏
b.为了知新线程的执⾏结果
使⽤pthread_join函数对⼦线程阻塞等待
#include <pthread.h>
#include <void.h>
int pthread_join(pthread_t thread, void **retval);
作⽤:主线程等待新线程 和wait作⽤⼀样 回收新线程占⽤的资源
thread:要等待的线程的标识符(ID),常是通过 pthread_create 函数获得的。
retval:⼀个指向指针的指针,⽤于接收⽬标线程的返回值。
如果不需要接收返回值,可以将其设置为NULL。
detach新线程后 若主线程退出 谁来维护新线程?
detach后 新线程与主线程的关联取消
新线程会成为后台线程或守护线程
成为后台线程时将由运⾏时库接管
PCB退出时也⼀并由系统内核释放
如何理解pthread_create的第三参数与第四参数
void意思是类型不完整 ⼀般⼤⼩为0字节 但是void*⼤⼩为8字节
函数返回类型是void时不需要任何返回值 但是如果是void*就需要返回值nullptr
线程创建是异步的
pthread_create 函数调⽤是异步的
这意味着主线程不会等待新线程开始执⾏才继续执⾏下⼀条指令
相反 pthread_create 会在为新线程分必要的资源并设置其执⾏函数后⽴即返回新线程的执⾏是独⽴于主线程的
并且其执⾏顺序与 pthread_create 的调⽤顺序没有必然的联系
如何理解pthread_join第⼆参数
作⽤:⽤于存储pthread_create中回调函数routine的返回值
多线程资源访问权限
线程退出pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
作⽤:单⼀进程退出
retval:新线程返回值,由pthread_join函数填写
线程取消pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);
取消⼀个线程的前提是⽬标进程已启动
作⽤:由主控线程去取消其他线程 不推荐使⽤
发送⼀个请求使线程状态变为待处理 但不⽴即终⽌线程执⾏
thread:这是要发送取消请求的线程标识符
线程分离detach:主线程不等待新线程
和⽗进程与⼦进程类似
当主线程有⾃⼰的任务需要执⾏时,主线程可以不等待新线程
可以将⽬标线程设置为分离状态
joined:线程需要被join(default)
detach:线程分离(主线程不等待新线程)
多执⾏流情况下,保证主执⾏流最后退出
#include <pthread.h>
int pthread_detach(pthread_t thread);
thread:要分离的线程的标识符,该标识符是pthread_create函数调⽤时返回的线程ID
返回值:如果成功,函数返回0;如果失败,函数将返回⼀个⾮零错误码
作⽤
pthread_detach函数的主要作⽤是将线程从可结合(joinable)状态转换为可分离状态
在可分离状态下,线程在终⽌时会⾃动释放其占⽤的系统资源
⽽⽆需其他线程调⽤pthread_join函数来回收这些资源
//如果pthread_detach和pthread_join共存,那么主线程依旧会等待新线程
//如果只有pthread_join,那么主线程也会依旧等待新线程
//如果只有pthread_detach,那么主线程会忽视新线程,任由新线程执⾏⾃⼰的任务
进程内可以创建线程 线程内可以创建进程 只要fork和pthread相关函数使⽤合理即可
十七.线程库
1.⽤户级线程库成结构简析
2.C++中对POSIX内核级线程库接⼝封装逻辑理解
对POSIX内核级线程库接⼝封装᭦辑理解 假设有⼀个Thread 先⽤class描述 再组织
线程状态枚举与成员
线程创建
ps:据规定,
⾃定义函数Routine参数列表有且仅有⼀个void*类型变量
⽽class中的所有函数参数列表默认⾃带*this
所以 使⽤static来取消this
构造函数
线程停⽌
线程等待资源回收
线程分离
线程互斥
临界资源 多执⾏流被保护的资源
临界区(代码) 每个线程内存访问临界资源的代码 仅⼀个线程可访问
互斥 任何时候,保证有且仅有⼀个执⾏流进⼊临界区
原⼦性 不会被任何调度机制打断的操作,有且仅有完成和未完成两态
互斥变量Mutex
⽤于保证共享资源被⼀个进程或线程访问的变量
未加保护的共享资源 线程并发问题演示
有关执⾏结果为何会有负数的分析
为什么ticketnum最后会变负数
因为if判断不具有原⼦性
多个线程在⼀个cpu上同时运⾏,它们同时将ticketnum读取到了cpu上并进
⾏--操作就导致了ticketnum被多减了好几遍(多执⾏流)
让线程合理切换可以较好地解决此ᳯ题,线程⼀般什么时候切换
a.线程时间⽚耗尽
b.来了优先级更⾼的进程(OS线程是基于优先级抢占的)
c.通过usleep从内核返回⽤户时,会进⾏时ᳵ⽚是否耗尽的判断,进⽽切换
数据从内存搬到cpu寄存器本质是将数据从共享变为线程私有 寄存器内容叫做执⾏流硬件上下⽂、
原⼦性性状有且仅有执⾏前和执⾏后两态 不存在执⾏中此态
使⽤互斥锁Mutex对临界区保护解决上述问题
临界区与⾮临界区
所有对临界资源的保护᮷是对临界代码的保护 因为只有通过临界代码才能访问临界资源
临界资源:⼀次仅允许⼀个执⾏流访ᳯ的共享资源 共享性:多个执⾏流可以访问
互斥性:任何时刻仅允许⼀个执⾏流访问
有关lock的相关接⼝操作简介
#include <pthread.h>
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER; 声明⼀个lock并初始化
int pthread_mutex_lock(pthread_mutex_t *lock);//加lock
如果当前lock被其他线程占有,就让此线程阻塞式等待int pthread_mutex_trylock(pthread_mutex_t *mutex);//加lock
如果当前lock被其他线程占有,就让此线程⽴刻返回⽽不是阻塞式等待int pthread_mutex_unlock(pthread_mutex_t *lock);//解lockint pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
作⽤:⽤于初始化 POSIX 线程(pthread)互斥lock(mutex)的函数
pthread_mutex_t *mutex:互斥lock的地址或指针
const pthread_mutexattr_t *attr:指向互斥lock属性的指针
如果传递 NULL,则使⽤默认属性
返回01int pthread_mutex_destroy(pthread_mutex_t *mutex);
作⽤:对上述初始化的1lock销毁
全局lock(互斥)的使⽤
lock实现原理
也可以依靠硬件实现加lock
通过屏蔽中断达到延时间⽚的效果
(每执行一条指令以后都要去检测是否有中断,如果有中断那么这个中断就完全有可能是其他的执行流,现在当我正在执行某个方法中的所有指令时屏蔽所有中断以遏制这一情况的发生)
这样指定线程就不会被切换
代码执⾏结束后再开启时中断
C++11⻛格的lock
对系统调⽤lock接⼝封装
线程同步
互斥保证安全性 但未必合理或⾼效 同步是在安全的前提下保证更加合理
条件变量(同步)
条件变ᰁ是⼀个⽤来进⾏进程同步的特性 内᮱要维护线程队列
为了保证⼀个线程在临界区内频繁申请mutex从⽽导致不执⾏临界资源ᳯ题
我们便将此mutex设置为条件变量确保临界区内的线程⼀直去执⾏任务
条件变量允许⼀个或多个线程在条件不满⾜时进⼊等待状态
直到另⼀个进程修改了此条件
修改后它会通过pthread_cond_signal或pthread_cond_broadcast通知等待线程
当条件变量被触发时,⼀或多个等待条件变量的线程就会被唤醒
线程被唤醒后会去⾃动获取之前被释放的lock
获取lock后会再次检查条件变量所依赖的条件,查看条件变量是否满⾜
满⾜则线程访问临界资源
反之继续等待返回等待队列继续等待
条件变量操作所涉及到的⼀些必要POSIX接⼝
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
作⽤:⽤于初始化⼀个条件变量。条件变量⽤于线程ᳵ的同步,允许⼀个或多个线程在某个条件满⾜时被唤醒。
cond:指向需要初始化的条件变量的指ᰒ。
attr:指向条件变量属性的指针,常可以传递 NULL 以使⽤默认属性。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
作⽤:使当前线程进⼊等待队列,让线程等待条件变量的变化,通常和mutex⼀起使⽤,以保证操作的原⼦性
cond:指向条件变量的指针。
mutex:指向互斥锁的指针,调⽤线程在调⽤ pthread_cond_wait 前必须已经持有该lock
函数会⾃动释放锁并在条件变量被触发后新获取锁。
int pthread_cond_signal(pthread_cond_t *cond);
作⽤:⽤于⼀个等待某个条件变量的线程,如果有多个线程在等待该条件变ᰁ,则选择其中⼀个唤ᯯ
cond:指向条件变量的指针
int pthread_cond_broadcast(pthread_cond_t *cond);
作⽤:⼀次唤醒所有线程 ⼆者作⽤是唤醒线程去看看能不能执⾏任务,如果线程发现不能执⾏任务就会被pthread_cond_wait阻挡
⼦线程
⼀次唤醒⼀个线程
⼀次唤醒所有线程
十八.生产者 消费者模型简要概述
基于⽣产消费模型的POSIX计数信号ᰁ理解控制使⽤与封装
信号量本质上是⼀个计数器 ⽤于记录⼀个thread对临界资源的与定数 记录是否有thread在访ᳯ临界资源
信号量和mutex⼀样 其本身具有临界性与原⼦性
所以说mutex也是信号量的⼀种特殊情况
使⽤时 我们只需记好信号量⼀⽅⾯是⽤于表示队列中的空间和队列中的有效数据的的变量
另⼀⽅⾯信号量下的控制下线程的获取和释放操作具有原⼦性 保证了其操作不会被其他线程打断
信号量定义与类型
信号量是⼀种计数器,⽤于控制对共享资源的访问
它本质上是⼀个整型变量,可以通过特定的操作进⾏访问和修改
信号量主要分为两种类型:
计数信号量(Counting Semaphore):其值可以表示多个可⽤资源的数量,
⽤于控制多个进程或线程对多个资源的并发访问。
⼆进制信号量(Binary Semaphore),⼜称互斥量(Mutex):其值只能为0
或1,⽤于实现互斥访问即同⼀时间内只允许⼀个进程或线程访问某个资源
信号ᰁ常⽤POSIX接⼝介绍
对sem封装
有关构᭜函数的参数列表和初始化列表以及函数体是哪些变量的初始化领域
相同点就是三者是对成员变量进⾏初始化
不同点是
参数列表⼀般是需要user主动传参的,或者是某个成员变量需要user⼿动传参且共享同⼀参数
初始化列表则⼀般接受来⾃参数列表变量的初始化或者user既定的默认值
且user使⽤此类类型进⾏对象创建时只能对其构᭜函数的参数列表中的成员变量进⾏初始化
构᭜函数体内⼀般是某个成员变量初始化需要其他函数对其进⾏初始化的变量的初始化区域