操作系统知识梳理.docx
《操作系统知识梳理.docx》由会员分享,可在线阅读,更多相关《操作系统知识梳理.docx(23页珍藏版)》请在冰豆网上搜索。
![操作系统知识梳理.docx](https://file1.bdocx.com/fileroot1/2023-1/9/39ba8b03-757b-48f5-a283-dd5810bf5dea/39ba8b03-757b-48f5-a283-dd5810bf5dea1.gif)
操作系统知识梳理
操作系统基础
程序与进程
程序
程序是一个存储在磁盘上某个目录中的可执行文件
进程和进程ID
程序的执行实例被称为进程
计算机启动过程
通电,BIOS刷入内存,
硬件自检,BIOS程序首先检查,计算机硬件能否满足运行的基本条件
按照启动顺序,BIOS转移控制权,读取设备,如果设备不可以用于启动,则读取下一个设备,直到读取到MBR(主引导记录)
根据MBR,将控制权转移给操作系统所在分区,读取磁盘分区,
加载操作系统。
系统调用和库函数的差别:
1、系统调用是运行于内核状态;而库函数由用户调用,运行于用户态。
2、系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。
3、系统调用是为了方便使用操作系统的接口,而库函数则是为了人们编程的方便。
慢系统调用:
在调用过程中可能会永久阻塞的调用,包括以下情况:
1.调用read()时数据不出现;调用write()时数据不能被立刻接收;
2.打开一个文件将阻塞直至出现某条件;
3.读写一个具有强制锁的文件;
4.某些ioctl()操作;
5.某些进程间通信。
存储器分配
三个用于存储空间动态分配的函数:
void*malloc(size_tsize);
void*calloc(size_tnmemb,size_tsize);
void*realloc(void*ptr,size_tsize);
1.malloc:
分配指定字节数的存储区。
此存储区中的初始值不确定。
2.calloc:
为指定数量具指定长度的对象分配存储空间。
该空间中的每一位都初始化为0。
3.realloc:
更改以前分配区的长度(增加或减少)。
当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。
三个函数的返回值:
若成功则返回非空指针,若出错则返回NULL.
C程序使用malloc函数族在堆上分配和释放内存。
这些函数具备以下特点:
●属于C语言标准的一部分
●更易于在多线程程序中使用
●接口简单,允许分配小块内存。
●允许随意释放内存块,它们被维护于一张空闲内存列表中,在后续内存分配调用时循环使用。
malloc()函数在堆上分配参数size字节大小的内存,并返回指向新分配内存起始位置处的指针,其分配的内存未经初始化。
#include
void*malloc(size_tsize);
Returnpointertoallocatedmemoryonsuccess,orNULLonerror
由于malloc()的返回类型为void*,因而可以将其赋给任意类型的C指针。
malloc()返回内存块所采用的字节对齐方式,总是适宜于高效访问任何类型的C语言数据结构。
在大多数硬件架构上,这实际意味着malloc是基于8字节或16字节边界来分配内存的。
若无法分配内存,则malloc()返回NULL,并设置errno以返回错误信息。
虽然分配内存失败的可能性很小,但所有对malloc()以及后续提及的相关函数的调用都应对返回值进行错误检查。
free()函数释放ptr参数所指向的内存块,该参数一般是之前由malloc()或其他堆内存分配函数返回的地址。
#include
voidfree(void*ptr);
free()函数会将释放的这块内存添加到空闲内存列表中,供后续的malloc()函数循环使用。
如果传给free()的是一个空指针,那么函数将什么都不做。
(换句话说,给free()传入一个空指针并不是错误代码。
)
在调用free()后对参数ptr的任何使用,例如将其再次传递给free(),将产生错误,并可能导致不可预知的后果。
所以应对参数做检查。
CPU时间与墙钟时间
关于时间的一个重要应用是测量程序的运行时间。
UNIX系统是多道分时系统,它可以同时运行多道程序,因此,程序的运行时间有CPU时间与墙钟时间之分。
进程的CPU时间是进程占用处理机的运行时间,它包括进程执行自己的指令以及系统为进程服务所用时间,但不包括等待I/O或其他进程运行所占时间,它是一个相对固定的值。
进程的墙钟时间是进程从开始执行到结束期间墙钟实际走动的时间。
由于分时的原因,进程的墙钟时间可能包含了其他进程的运行时间,因此对于一个进程的每一次运行而言,它可能是一个不固定的值。
每一个进程自创建开始,系统便统计它所使用的CPU时间。
进程的CPU时间进一步又分为用户时间和系统时间。
用户时间是进程执行自己的指令所花的时间,系统时间是操作系统为进程服务(如执行系统调用)所用的时间。
进程
进程组
进程组是一个或多个进程的集合,通常他们与同一作业相关联,可以接收来自同一终端的各种信号,组长进程的标识是进程组ID等于其自身的进程ID。
一个进程只能为它自己或它的子进程设置进程组ID,在它的子进程调用了exec函数之后就不能再改变该子进程的进程组id。
Pid_tgetpgrp(void);
Pid_tgetpgid(pid_tpid);若pid为0,则返回调用的进程组ID
Intsetpgid(pid_tpid,pid_tpgid);将pid进程的进程组ID设置为pgid.如果这两个参数相等,pid指定的进程变成进程组组长,为0,则使用调用者的进程ID.pgid为0,pid指定的进程id将用作进程组id。
会话
会话是一个或多个进程组的集合。
pid_t setsid(void); //若成功则返回进程组ID,出错则返回-1.
1.如果调用此函数的进程不是一个进程组组长,则此函数就会创建一个新会话,结果将发生下面三件事:
a.该进程变成新会话首进程(sessionleader)。
(会话首进程是创建该会话的进程)此时,该进程是新会话中的唯一进程。
b.该进程称为一个新进程组的组长进程,新进程组ID是该调用进程的进程ID。
c.该进程没有控制终端,如果在调用setsid之前该进程有一个控制终端,那么这种联系也会被中断。
2.如果该调用进程已经是一个进程组的组长,则此函数返回出错。
为了保证不会发生这种情况,通常先调用fork,然后使其父进程终止,则子进程则继续。
因为子进程继承了父进程的进程组ID,而其进程ID是新分配的,两者不可能相等,保证了子进程不会是一个进程组的组长。
控制终端:
1.一个会话可以有一个控制终端(controllingterminal),通常会话的第一个进程打开一个终端(终端设备或伪终端设备)后,该终端就成为该会话的控制终端。
2.建立与控制终端连接的会话首进程被称为控制进程。
(controllingprocess)
3.一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。
4.如果一个会话有一个控制终端,则它有一个前台进程组(foregroundprocessgroup),会话中的其他进程组则为后台进程组。
5.无论何时进入终端的中断键(ctrl+c)或推出键(ctrl+\),就会将中断信号发送给前台进程组的所有进程。
6.如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送给控制进程。
作业控制
作业控制允许在一个终端上启动多个作业(进程组),它控制哪一个作业可以访问终端,哪些作业在后台运行。
作业控制是伯克利在1980年左右加到UNIX的一个新特性。
它允许在一个终端上起动多个作业(进程组),控制哪一个作业可以存取该终端,以及哪些作业在后台运行。
作业控制要求三种形式的支持:
(1)支持作业控制的shell。
(2)内核中的终端驱动程序必须支持作业控制。
(3)必须提供对某些作业控制信号的支持。
进程的状态转换
僵死进程
子进程终止时,由于需要保存它得终止状态以备父进程调用wait(),因此子进程在进程表中的登记项不会立刻释放,仍然保持与父进程的连接直到父进程正常终止或者调用wait()为止。
即尽管子进程不在活跃,但仍在系统中,此时它就是所谓的僵死进程。
为了避免僵死进程遗留在系统中,通常需要调用wait()来等待子进程,或者通过调用两次fork()。
守护进程
守护进程也是精灵进程(daemon),是一种生存期较长的进程,常常在系统自举时候启动,仅仅在系统关闭时终止。
因为没有控制终端所有在后台运行。
常见的守护进程有下面这些:
∙init.系统守护进程,负责启动各个运行层次的特定系统服务。
∙keventd.为内核中运行计划执行的函数提供进程上下文。
∙kampd.对高级电源管理提供支持。
∙kswapd.页面调度守护进程。
∙bdflush.当可用内存到达某个下限的时候,将脏缓冲区从缓冲池(buffercache)冲洗到磁盘上。
∙kupdated.将脏页面冲洗到磁盘上,以便在系统失效时减少丢失的数据。
∙portmap.将rpc程序号映射为网络端口号。
∙syslogd.系统消息日志服务器。
∙xinted.inted守护进程。
∙nfsd,lockd,rpciod.支持NFS的一组守护进程。
∙crond.在指定的日期和时间执行特定的命令。
∙cupds.打印假脱机进程,处理对系统提出的所有打印请求。
∙daemonize
产生一个daemon程序需要一系列的操作,步骤如下:
∙umask(0).因为我们从shell创建的话,那么继承了shell的umask.这样导致守护进程创建文件会屏蔽某些权限。
∙fork然后使得父进程退出。
一方面shell认为父进程执行完毕,另外一方面子进程获得新的pid肯定不为进程组组长,这是setsid前提。
∙setsid来创建新的会话。
这时候进程称为会话首进程,称为第一个进程组组长进程同时失去了控制终端。
∙最好在这里再次fork。
这样子进程不是会话首进程,那么永远没有机会获得控制终端。
如果这里不fork的话那么会话首进程依然可能打开控制终端。
∙将当前工作目录更改为根目录。
父进程继承过来的当前目录可能mount在一个文件系统上。
如果不切换到根目录,那么这个文件系统不允许unmount.
∙关闭不需要的文件描述符。
可以通过_SC_OPEN_MAX来判断最高文件描述符(不是很必须).
∙然后打开/dev/null复制到0,1,2(不是很必须).当一个进程正常或者是异常终止的时候,内核就会向父进程发送一个SIGCHLD信号,父进程可以对这个信号进行处理或者是忽略,默认情况下面是忽略。
如果父进程需要处理的话,那么就可以调用wait/waitpid来得到子进程终止状态。
进程终止的方式和途径:
1正常终止
(1)从main()内执行return
(2)直接调用exit()函数
(3)调用_eixt()函数
2异常终止
(1)调用abort()
(2)当进程收到某种信号时
C程序的存储空间布局
(1)正文段。
这是由CPU执行的机器指令部分。
通常正文段是可共享的,所以即使是频繁执行的程序(入文本编辑器、C编译器和shell等)在存储器中也只需有一个副本,另外正文段常常是只读的,以防止程序由于意外而修改其指令。
(2)初始化数据段。
通常将此段称为数据段,它包含了程序中需明确的赋值的变量。
例如C程序中任何函数之外的声明:
intmaxcount=99;使此变量以其初值存放在初始化数据段中。
(3)为初始化数据段。
通常将此段称为bss段,这一名称来源于早期汇编程序一个操作服,意思是“由符号开始的块”(blockstartedbysymbol),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。
函数外的声明:
longsum[1000]使此变量存放在非初始化数据段中。
(4)栈。
自动变量以及每次函数调用时所需保存的信息都存放在此段中。
(5)堆。
通常在堆中进行动态存储分配。
由于历史上形成的惯例,堆位于为初始化数据段和栈之间。
IPC机制
按发送路径来看,可分为直接通信和间接通信。
1.直接通信
(1)进程必须正确的命名对方
send(P,message)–发送信息到进程P
receive(Q,message)–从进程Q接受消息
(2)通信链路的属性
自动建立链路
一条链路恰好对应一对通信进程
每对进程之间只有一个链接存在
链接可以是单向的,但通常为双向的
2.间接通信
(1)通过操作系统维护的消息队列实现进程间的消息接收和发送
每个消息队列都有一个唯一的标识
只有共享了相同消息队列的进程,才能够通信
(2)通信链路的属性
只有共享了相同消息队列的进程,才建立连接
连接可以是单向或双向
消息队列可以与多个进程相关联
每对进程可以共享多个消息队列
(3)通信流程
创建一个新的消息队列
通过消息队列发送和接收消息
销毁消息队列
3.进程通信可划分为阻塞(同步)或非阻塞(异步)
(1)阻塞通信
阻塞发送:
发送者在发送消息后进入等待,直到接收者成功收到
阻塞接收:
接收者在请求接收消息后进入等待,直到成功收到一个消息
(2)非阻塞通信
非阻塞发送:
发送者在消息发送后,可立即进行其他操作
非阻塞接收:
没有消息发送时,接收者在请求接收消息后,接收不到任何消息
4.信号
进程间的软件中断通知和处理机制。
5.管道(间接通信)
进程间基于内存文件的通信机制。
6.消息队列(间接通信)
是由操作系统维护的以字节序列为基本单位的间接通信机制。
7.共享内存(直接通信)
把同一段物理机制映射到多个进程的内存地址空间的通信机制。
每个进程的内存地址空间需要明确设置共享内存段,同一个进程的线程共享地址空间。
(速度快,但是没有同步)
共享存储
共享存储允许两个或多个进程共享一个给定的存储区。
因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。
使用共享存储时要掌握的唯一窍门是,在多个进程之间同步访问一个给定的存储区。
若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应当去取这些数据.通常,信号量用于同步共享存储访问。
数据库函数访问模型
当有多个进程访问同一数据库时,有两种方法可实现库函数。
(1)集中式。
由一个进程作为数据库管理者,所有的数据库访问工作由此进程完成。
其他进程通过IPC机制与此中心进程进行联系。
(2)非集中式。
每个库函数使用要求的并发控制(加锁),然后发起自己的I/O函数调用。
使用这两种技术的数据库系统都有.如果有适当的加锁例程,因为避免了使用IPC,那么非集中式方法一般要快一些。
fork()函数
头文件:
#include
函数定义:
intfork(void);
返回值:
子进程中返回0,父进程中返回子进程ID,出错返回-1
函数说明:
一个现有进程可以调用fork函数创建一个新进程。
由fork创建的新进程被称为子进程(childprocess)。
fork函数被调用一次但返回两次。
两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间,它们之间共享的存储空间只有代码段。
Vfork()函数,(建立一个新的进程)
相关函数wait,execve
头文件#include
定义函数pid_tfork(void)
函数说明:
vfork()会产生一个新的子进程,其子进程会复制父进程的数据与堆栈空间,并继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限制等。
返回值:
如果vfork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回0。
如果vfork失败则直接返回-1,失败原因存于errno中。
vfork与fork主要有三点区别:
fork():
子进程拷贝父进程的数据段,堆栈段
2.vfork():
子进程与父进程共享数据段
3.fork()父子进程的执行次序不确定,vfork保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
vfork()保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行.如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
1)fork()系统调用是创建一个新进程的首选方式,fork的返回值要么是0,要么是非0,父进程与子进程的根本区别在于fork函数的返回值.
2)vfork()系统调用除了能保证用户空间内存不会被复制之外,它与fork几乎是完全相同的.vfork存在的问题是它要求子进程立即调用exec,而不用修改任何内存,这在真正实现的时候要困难的多,尤其是考虑到exec调用有可能失败.
3)vfork()的出现是为了解决当初fork()浪费用户空间内存的问题,因为在fork()后,很有可能去执行exec(),vfork()的思想就是取消这种复制.
4)现在的所有unix变量都使用一种写拷贝的技术(copyonwrite),它使得一个普通的fork调用非常类似于vfork.因此vfork变得没有必要.
Fork工作机制
#include//返回值==0表示子进程,>0表示父进程(表示子进程pid)
pid_tfork();
pid_tvfork();
我们使用fork/vfork可以开辟子进程:
fork之后,子进程和父进程各自执行自己的逻辑。
刚分开的时候,两者的内存映像是相同的。
系统在实现的时候,并没有完全进行复制,而是使用COW(copyonwrite)的技术来解决的。
如果父子进程任意一个试图修改这些内存的话,那么会对修改页创建一个副本。
对于POSIX线程来说,fork的子进程之后包含了该fork出来的线程,而不是拥有所有线程的副本。
fork失败的原因通常有下面两种:
系统中已经存在太多的进程。
实际用户ID的进程总数已经超过了系统限制,CHILD_MAX.
fork出的子进程继承了父进程下面这些属性:
uid,gid,euid,egid
附加组id,进程组id,会话id
设置用户id标记和设置组id标记
控制终端
当前工作目录/根目录
文件模式创建mask
文件描述符的文件标志(close-on-exec)
信号屏蔽和安排
存储映射
资源限制
下面是不同的部分:
pid不同
进程时间被清空
文件锁没有继承
未处理信号被清空
fork通常一种使用方法就是之后执行exec程序,因为大部分时候做一个COW内存映像也是没有必要的。
vfork相对于fork就是这样一个差别,vfork子进程和父进程占用同一个内存映像,在子进程修改会影响父进程。
同时只有在子进程执行exec/exit之后才会运行父进程。
实际上子进程占用的栈空间就是父进程的栈空间,所以需要非常小心。
如果vfork的子进程并没有exec或者是exit的话,那么子进程就会执行父进程直到程序退出之后,父进程才开始执行。
而这个时候父进程的内存已经完全被写坏:
[dirlt@localhost.localdomain]$./main
parentarewaiting...
6616584
Segmentationfault
文件I/O
链接
关于文件链接分为硬链接和软链接,其中软链接也称为符号链接。
硬链接:
创建一个硬链接效果就是,选择一个文件名然后选择一个已经使用的inode编号存放在目录下面。
一旦创建硬链接之后,那么被链接的文件的属性里面就会将链接数目+1。
链接数目对应于structstat结构里面的st_nlink字段。
硬链接有如下限制:
硬链接通常要求链接和文件位于同一文件系统中
(因为硬链接是使用inode节点来操作的,所以硬链接不可以跨越文件系统)
只有超级用户才能创建指向目录的硬链接
(因为硬链接可能会造成文件系统中形成循环,而大多数程序无法处理这种情况而且很容易搞乱文件系统。
)
---------------------------------------------------------------------
符号链接:
符号链接是对一个文件的间接指针,它与硬链接有所不同,硬链接直接指向文件的i节点。
引入符号链接的目的是为了避开硬链接上述的一些限制。
对符号链接以及它指向何种对象,并无任何文件系统限制,任何用户都可以创建指向目录的符号链接。
符号链接一般用于将一个文件或整个目录结构移到系统中另一个位置。
当使用以名字引用文件的函数时,应当了解该函数是否处理符号链接。
也就是该函数是否跟随符号链接到达它所链接的文件。
如若该函数具有处理符号链接的功能,则其路径名参数引用由符号链接指向的文件。
否则,一个路径名参数引用链接本身,而不是由该链接指向的文件:
函数
不跟随链接
跟随链接
access
Y
chdir
Y
chmod
Y
chown
Y
creat
Y
exec
Y
lchown
Y
link
Y
lstat
Y
open
Y
opendir
Y
pathconf
Y
readlink
Y
remove
Y
rename
Y
stat
Y
truncate
Y
unlink
Y
文件访问权限
所有文件类型(目录、字符特别文件等)都有访问权限。
st_mode值包含了对文件的访问权限位。
每个文件有9个访问权限位,可将它们分为3类,如图
st_mode屏蔽
含义
S_IRUSR
S_IWUSR
S_IXUSR
用户读
用户写
用户执行
S_IRGRP
S_IWGRP
S_IXGRP
组读
组写
组执行
S_IROTH
S_IWOTH
S_IXOTH
其他读
其他写
其他执行
Vnode&&inode
传统的Unix既有v节点(vnode)也有i节点(inode),vnode的数据结构中包含了inode信息。
但在Linux中没有使用vnode,而使用了通用inode。
“实现虽不同,但在概念上是一样的。
”
vnode(“virtualnode”)仅在文件打开的时候,才出现的;而inode定位文件在磁盘的位置,它的信息本身是存储在磁盘等上的,当打开文件的时候从磁盘上读入内存。
Inode包含了文件方方面面的信息。
打开文件
内核使用3种数据结构表示打开的文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程的影响。
(1)每个进程在进程表中都有一个纪录项,纪录项中包含一张打开文件描述符表,每个文件描述符各占一项,与每个文件描述符相关的是
a.文件描述符标志
b.指向一个文件表项的指针
(2)内核为所有打开文件维护一张文件表项,每个文件表项包含:
a.文件状态(读写同步非阻塞等)
b.当前文件偏移量
c.指向改文件V节点(i节点)的指针
(3)每打开一个文件或设备,都有一个V节点结构,V节点包含了文件类型和对此文件进行操作函数的指针,对于大多数文件,v节点还