1.Linux内存机制
Linux虚拟内存的实现需要6种机制的支持:
地址映射机制、内存分配回收机制、缓存和刷新机制、请求页机制、交换机制和内存共享机制;
内存管理程序通过映射机制把用户程序的逻辑地址映射到物理地址。
当用户程序运行时,如果发现程序中要用的虚地址没有对应的物理内存,就发出了请求页要求。
如果有空闲的内存可供分配,就请求分配内存(于是用到了内存的分配和回收),
并把正在使用的物理页记录在缓存中(使用了缓存机制)。如果没有足够的内存可供分配,
那么就调用交换机制;腾出一部分内存。
另外,在地址映射中要通过TLB(翻译后援存储器)来寻找物理页;
交换机制中也要用到交换缓存,并且把物理页内容交换到交换文件中,
也要修改页表来映射文件地址。
2.死锁必要条件及避免算法
(1)资源不能共享,只能由一个进程使用。
(2)请求与保持(Hold andwait):已经得到资源的进程可以再次申请新的资源。
(3)不可剥夺(Nopre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
(4)循环等待:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
处理死锁的策略:
1.忽略该问题。例如鸵鸟算法,该算法可以应用在极少发生死锁的的情况下。
为什么叫鸵鸟算法呢,因为传说中鸵鸟看到危险就把头埋在地底下,
可能鸵鸟觉得看不到危险也就没危险了吧。跟掩耳盗铃有点像。
2.检测死锁并且恢复。
3.仔细地对资源进行动态分配,以避免死锁。
4.通过破除死锁四个必要条件之一,来防止死锁产生。
3.系统如何将一个信号通知到进程
信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,
也有人称作软中断。
进程之间可以互相通过系统调用kill发送软中断信号。
信号机制是异步的;当一个进程接收到一个信号时,它会立刻处理这个信号,
而不会等待当前函数甚至当前一行代码结束运行。信号有几十种,分别代表着不同的意义。
信号之间依靠它们的值来区分,但是通常在程序中使用信号的名字来表示一个信号。
Linux系统中,这些信号和以其名称命名的常量均定义在/usr/include/bits/signum.h中。
(通常程序中不需要直接包含这个头文件,而应该包含<signal.h>。)
信号事件的发生有两个来源:
硬件来源(比如我们按下了键盘或者其它硬件故障);
软件来源,最常用发送信号的系统函数是kill, raise, alarm和setitimer及sigqueue函数,
软件来源还包括一些非法运算等操作。
发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()及abort()。
进程可以通过三种方式来响应一个信号:(1)忽略信号,即对信号不做任何处理,
其中,有两个信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定义信号处理函数,
当信号发生时,执行相应的处理函数;(3)执行缺省操作。
4. Linux系统中各类同步/异步机制
如何实现守护进程:守护进程最重要的特性是后台运行。
(1) 在后台运行。
为避免挂起控制终端将Daemon放入后台执行。
方法是在进程中调用fork使父进程终止,让 Daemon在子进程中后台执行。
if(pid=fork())
exit(0); //是父进程,结束父进程,子进程继续
(2)脱离控制终端,登录会话和进程组
有必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:
进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。
登录会话可以包含多个进程组。这些进程组共享一个控制终端。
这个控制终端通常是创建进程的登录终端。控制终端,
登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,
使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长。
(当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。)
setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话
和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
(3) 禁止进程重新打开控制终端
if(pid=fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话组长)
(4)关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,
造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);
(5) 改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。
对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/")
(6) 重设文件创建掩模
进程从创建它的父进程那里继承了文件创建掩模。
它可能修改守护进程所创建的文件的存取位。
为防止这一点,将文件创建掩模清除:umask(0);
(7)处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,
特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,
子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,
将增加父进程的负担,影响服务器进程的并发性能。
在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);这样,内核在子进程结束时不会产生僵尸进程。
这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。
5.标准库函数和系统调用
(1)系统调用
系统调用提供的函数如open, close, read, write, ioctl等,需包含头文件unistd.h。
以write为例:
其函数原型为 size_t write(int fd, const void *buf, size_t nbytes),
其操作对象为文件描述符或文件句柄fd(file descriptor),要想写一个文件,
必须先以可写权限用open系统调用打开一个文件,获得所打开文件的fd,
例如fd=open(/"/dev/video/", O_RDWR)。fd是一个整型值,每新打开一个文件,
所获得的fd为当前最大fd加1。Linux系统默认分配了3个文件描述符值:
0-standard input,1-standard output,2-standard error。
系统调用通常用于底层文件访问(low-level file access),
例如在驱动程序中对设备文件的直接访问。
系统调用是操作系统相关的,因此一般没有跨操作系统的可移植性。
系统调用发生在内核空间,因此如果在用户空间的一般应用程序中使用系统调用来进行文件操作,
会有用户空间到内核空间切换的开销。事实上,即使在用户空间使用库函数来对文件进行操作,
因为文件总是存在于存储介质上,因此不管是读写操作,都是对硬件(存储器)的操作,
都必然会引起系统调用。也就是说,库函数对文件的操作实际上是通过系统调用来实现的。
例如C库函数fwrite()就是通过write()系统调用来实现的。这样的话,
使用库函数也有系统调用的开销,为什么不直接使用系统调用呢?这是因为,
读写文件通常是大量的数据(这种大量是相对于底层驱动的系统调用所实现的数据操作单位而言),
这时,使用库函数就可以大大减少系统调用的次数。这一结果又缘于缓冲区技术。
在用户空间和内核空间,对文件操作都使用了缓冲区,
例用fwrite写文件,都是先将内容写到用户空间缓冲区,当用户空间缓冲区满或者写操作结束时,
才将用户缓冲区的内容写到内核缓冲区,同样的道理,
当内核缓冲区满或写结束时才将内核缓冲区内容写到文件对应的硬件媒介。
(2)库函数调用
标准C库函数提供的文件操作函数如fopen, fread, fwrite, fclose,fflush, fseek等,
需包含头文件stdio.h。以fwrite为例,其函数原型为
size_t fwrite(const void *buffer,size_t size, size_t item_num, FILE *pf),
其操作对象为文件指针FILE *pf,要想写一个文件,
必须先以可写权限用fopen函数打开一个文件,
获得所打开文件的FILE结构指针pf,例如pf=fopen(/"~/proj/filename/",/"w/")。
实际上,
由于库函数对文件的操作最终是通过系统调用实现的,因此,
每打开一个文件所获得的FILE结构指针都有一个内核空间的文件描述符fd与之对应。
同样有相应的预定义的FILE指针:
stdin-standard input,stdout-standard output,stderr-standard error。
库函数调用通常用于应用程序中对一般文件的访问。
库函数调用是系统无关的,因此可移植性好。
由于库函数调用是基于C库的,因此也就不可能用于内核空间的驱动程序中对设备的操作。
6.块设备和字符设备、用户态和内核态
(1) 字符设备:提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。
相反,此类设备支持按字节/字符来读写数据。举例来说,调制解调器是典型的字符设备。
(2) 块设备:应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。
硬盘是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。
此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,
块设备并不支持基于字符的寻址。
两种设备本身并没用严格的区分,主要是字符设备和块设备驱动程序提供的访问
接口(file I/O API)是不一样的。本文主要就数据接口、
访问接口和设备注册方法对两种设备进行比较。
用户态和内核态:
虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,
即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,
在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,
7.进程间通信
消息队列:存放在内核中,是链表的形式。
匿名管道:CreatePipe(); 只能本地使用。管道是半双工的。只能是父子进程之间通信
命名管道:也是半双工,但是可在无亲缘关系的进程之间通信。可用于网络通信,
可以通过名称引用;支持多客户端链接,双向通信;
共享内存(内存映射文件):CreateFileMapping .创建内存映射文件,
是多个进程对同一块物理内存的映射。
(因为是共享内存的方式,读写之间有冲突)
socket: 可以跨越机器。
共享内存的效率是最高的,因为共享一块都能看见的内存,不用多份拷贝,
而且访问共享内存相当于内存中区域一样,不需要通过系统调用或者切入内核来完成。
但是需要字节提供同步措施。一般用信号量解决读写冲突。
8.多线程和多进程
进程数据是分开的:共享复杂,需要用IPC,同步简单;多线程共享进程数据:共享简单,同步复杂。
进程创建销毁、切换复杂,速度慢 ;线程创建销毁、切换简单,速度快。
进程占用内存多, CPU利用率低;线程占用内存少, CPU利用率高。
进程编程简单,调试简单;线程 编程复杂,调试复杂。
进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉。
进程适应于多核、多机分布;线程适用于多核。
线程所私有的:
线程id、寄存器的值、栈、线程的优先级和调度策略、
线程的私有数据、信号屏蔽字、errno变量。
多线程锁的种类:a.互斥锁(mutex)b.递归锁 c.自旋锁 d.读写锁。
9.几种锁机制
线程之间的锁:互斥锁(sleep-waiting)、条件锁、自旋锁(busy-waiting,
eg,当发生阻塞时,互斥锁可让CPU去处理其它任务,
而自旋锁让CPU一直不断循环请求获取这个锁)、读写锁。
网友评论