一、操作系统概述
1.功能和目标
(1)进程管理:进程控制、进程同步、进程通信和进程调度
(2)内存管理:内存分配、内存保护、地址映射、内存扩充
(3)设备管理:管理所有的外围设备,包括完成用户的IO请求,为用户进程分配IO设备,提高IO设备的利用率和IO速度等;
(4)文件管理:管理用户文件和系统文件,方便使用的同时保证安全性。包括磁盘存储空间的管理,目录管理、文件读写管理以及文件共享和保护;
(5)提供用户接口:程序接口(API)和用户接口(GUI)
2.四大特性(并共虚异)
(1)并发:计算机系统中同时存在着多个运行着的程序,这些程序微观上是交替进行的,但是宏观上看起来就像是同时进行的一样;
(2)共享:系统中的资源可供内存中多个并发执行的进程共同使用,两种资源共享方式:互斥和同时(这里的同时使指的宏观上的);
(3)虚拟:是指把一个物理上的实体(实际存在的)变为若干个逻辑上的对应物(用户感受到的);
空分复用技术(如虚拟存储器技术)
时分复用技术(如虚拟处理器)
没有并发性,就谈不上虚拟性;
(4)异步:在多道程序的环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停的,以不可预知的速度向前推进,这就是进程的异步性;
3.操作系统的分类
(1) 批处理操作系统:成批处理、系统吞吐量高、资源利用率高、用户不能干预作业的执行;
(2)分时操作系统:多路性、独立性、及时性、交互性;
(3) 实时操作系统:及时响应、快速处理、高可靠性和安全性、不要求系统资源利用率;
4.动态链接库和静态链接库的区别
静态链接库是.lib格式的文件,一般在工程的设置界面加入工程中。程序编译时,会把lib文件代码加入到程序中,因此会增加代码大小。不能手动移除lib代码。
动态链接库是程序运行时动态装入内存的模块,格式为.dll,在程序运行是可以随意加载和移除,节省内存空间。
5.用户态和核心态
(1)运行在用户态下的程序,只能受限地访问内存,不允许访问外围设备。占用CPU的能力被剥夺,CPU资源可以被其他程序获取。运行在内核态下的程序,可以访问内存所有数据,包括外围设备,并且所占用的处理机是不允许被抢占的。 (2)用户态切换到内核态的三种方式:
【1】系统调用:用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用,申请使用操作系统提供的服务程序,以完成工作。比如fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
【2】异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,会触发由当前运行进程切换到处理此异常的内核相关程序中。因此也就转到了内核态,比如缺页异常。
【3】外围设备终端:外围设备完成用户请求的操作后,会像CPU发送相应的中断信号。此时,CPU会暂停执行下一条即将要执行的指令,转而执行与中断信号对应的处理程序。如果先前执行的指令是用户态的程序,那么转换的过程自然就发生了由用户态到内核态的转换。
6.中断和系统调用和函数调用
(1)中断
所谓的中断就是在计算机执行程序的过程中,由于出现了某些特殊事情,使得CPU暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。
中断一般分为三类:
【1】由计算机硬件异常或故障引起的中断,称为内部异常中断;
【2】由程序中执行了引起中断的指令而造成的中断,称为软中断(这也是和我们将要说明的系统调用相关的中断);
【3】由外部设备请求引起的中断,称为外部中断。简单来说,对中断的理解就是对一些特殊事情的处理。
与中断紧密相连的一个概念就是中断处理程序了。当中断发生的时候,系统需要去对中断进行处理,对这些中断的处理是由操作系统内核中的特定函数进行的,这些处理中断的特定的函数就是我们所说的中断处理程序了。另一个与中断紧密相连的概念就是中断的优先级。中断的优先级说明的是当一个中断正在被处理的时候,处理器能接受的中断的级别。中断的优先级也表明了中断需要被处理的紧急程度。每个中断都有一个对应的优先级,当处理器在处理某一中断的时候,只有比这个中断优先级高的中断可以被处理器接受并且被处理。优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略。
典型的中断优先级如下所示:
机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断
(2)系统调用和函数调用的区别
【1】
系统调用 ① 操作系统提供给用户程序调用的一组特殊的接口。用户程序可以通过这组特殊接口来获得操作系统内核提供的服务;
② 系统调用可以用来控制硬件;设置系统状态或读取内核数据;进程管理,系统调用接口用来保证系统中进程能以多任务在虚拟环境下运行;
③ Linux中实现系统调用利用了0x86体系结构中的软件中断;
【2】 函数调用
① 函数调用运行在用户空间;
② 它主要是通过压栈操作来进行函数调用;
【3】区别
(3)执行一个系统调用时,OS发生的过程:
【1】执行用户程序(如fork)
【2】根据glibc中的函数实现,取得系统调用号并执行int $0x80产生中断;
【3】 进行地址空间的转换和堆栈的切换,执行SAVE_ALL。(进行内核模式)
【4】进行中断处理,根据系统调用表调用内核函数。
【5】执行内核函数
【6】 执行RESTORE_ALL并返回用户模式
7.中断与轮询
中断指的是,在计算机执行期间,系统内发生任何非寻常或非预期的急需处理时间,使得CPU中断当前正在执行的程序,而转去执行相应的事件处理程序。处理完毕后,处理器又返回原来被中断的地方,继续执行或调度新的进程。轮询指的是,系统定时对各种设备轮流询问一遍是否有处理要求。有要求的,则加以处理。轮询占据了CPU相当一部分处理时间,是一种效率较低的 方式。 中断:容易遗漏一些问题,CPU 利用率高
轮询:效率低,等待时间长,CPU利用率低
8.中断的实现与作用
(1)中断的实现
① 关中断,进入不可再次响应中断的状态,由硬件实现。
② 保存断点,为了在
中断处理结束后能正确返回到中断点。由硬件实现。
④ 保护现场、置屏蔽字、开中断,即保护CPU中某些寄存器的内容、设置
中断处理次序、允许更高级的
中断请求得到响应,实现
中断嵌套。由软件实现。
⑤ 设备服务,实际上有效的中断处理工作是在此程序段中实现的。由软件程序实现
⑥ 退出中断。在退出时,又应进入不可中断状态,即关中断、恢复屏蔽字、恢复现场、开中断、中断返回。由软件实现。
(2)中断的作用
① 可以使CPU和外设同时工作,使系统可以及时地响应外部事件;
② 可以允许多个外设同时工作,大大提高了CPU的利用率;
③ 可以使CPU及时处理各种软硬件故障。
9.IO多路复用
(1)定义和使用场景:
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
-
当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
-
当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
-
如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
-
如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
-
如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
-
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
(2)名词解释
【1】
同步 就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。*也就是必须一件一件事做*,等前一件做完了才能做下一件事。就是我调用一个功能,该功能没有结束前,我死等结果。
【2】异步
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知
【3】阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。 就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
【4】非阻塞
指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。就是调用我(函数),我(函数)立即返回,通过select通知调用者。
(3)IO模型
1) 阻塞I/O
应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好,一直等待….数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。
2) 非阻塞I/O
我们把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用CPU的时间。
3) I/O复用
I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这三个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。
4) 信号驱动I/O
首先我们允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
5) 异步I/O
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入输出操作。
10.并发和并行
(1)并发:在同一时间多个程序都启动运行在同一个处理机中;
(2)并行:多个进程分别由不同的CPU管理执行,在同一时间多个程序并行运行在不同的处理机中;
二、进程和线程
1.进程和线程的关系
(1)进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位;
(2)线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程是操作系统可识别的最小的执行和调度单位;
(3) 一个进程可以有多个线程并发运行;一个线程只能属于一个进程;
(4)线程基本不拥有系统资源,只拥有一些在运行中必不可少的资源,比如程序计数器、寄存器和栈;但是一个线程可以和其他线程共享进程的全部资源(代码段---代码和常量;数据段---全局变量和静态变量;扩展段---堆内存);
(5)线程可以创建和撤销另一个线程;
(6)进程和线程都是一个时间段的描述,是CPU工作时间段的描述,只不过颗粒大小不同;
2.进程和线程的区别
如果以多进程的方式运行,那么允许多个任务同时运行;
如果以多线程的方式运行,那么允许将单个任务分成不同的部分运行;
为了防止进程/线程之间产生冲突和允许进程之间的共享资源,需要提供一种协调机制 --- 进程/线程的同步机制;
(1)进程有自己的独立地址空间,线程没有;
(2)进程是资源分配的最小单位,线程是CPU调度的最小单位;
(3)进程和线程通信方式不同;
【1】线程之间的通信比较方便,同一进程下的线程是共享资源数据的,通过这些数据来通信不仅快捷而且方便,但是如何处理好这些访问的同步与互斥正是多线程编程的首要考虑的点;
【2】进程之间的通信则只能通过进程通信的方式进行;
(4)进程的上下文切换开销大,线程开销小;
(5)一个进程挂掉了不会影响其他的进程,但是一个线程挂掉了会影响其他线程
3.线程比进程具有哪些优势?如何提升多线程的效率?
优势:
(1) 线程在程序中是独立的,并发的执行流,但是,进程中的线程之间的隔离程度要小;
(2)线程比进程更具有更高的性能,这是由于同一个进程中的线程都有共性:多个线程将共享同一个进程虚拟空间;
(3)当操作系统创建一个进程时,必须为进程分配独立的内存空间,并分配大量相关资源;
如何提升多线程的效率:
(1)尽量使用池化技术,也就是线程池,从而不用频繁的创建,销毁线程
(2)减少线程之间的同步和通信
(3)通过Huge Page的方式避免产生大量的缺页异常
(4)避免需要频繁共享写的数据
4.什么时候使用多进程?什么时候使用多线程?
(1)需要频繁创建销毁的优先用线程;
(2)需要进行大量计算的优先使用线程;
(3)强相关的处理用线程,弱相关的处理用进程;
(4)可能要扩展到多机分布的用进程,多核分布的用线程;
5.为什么进程的上下文切换比线程上下文切换代价高?
(1)上下文切换
对于单核单线程CPU而言,某一时刻只能执行一条CPU命令。上下文切换(context switch)是一种将CPU资源从一个进程分配给另一个进程的机制。在用户的角度看来,计算机能够并行的运行多个进程,这恰恰就是操作系统通过快速的上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行下一个进程;
(2)进程切换分两步:
【1】 切换页目以使用新的地址空间;
【2】切换内核栈和硬件上下文;
对于linux来说,线程和进程的最大区别就是在于地址空间,对于线程的切换,第一步是不需要的,但是第二步是两者都要进行的;
(3)性能分析:
【1】线程的上下文切换和进程上下文切换一个最主要的区别就是线程的切换,虚拟内存空间仍旧是相同的,而进程则不同;内核的这种切换过程是伴随着十分显著的性能损耗的;
【2】另外一个隐形的损耗是上下文切换回扰乱处理器的缓存机制,一旦发上下文切换,处理器中所有已经缓存的内存地址一瞬间都作废了。并且,当我们改变虚拟内存空间的时候,处理的页表缓冲等会被全部刷新,这会导致内存的访问在一段时间内都十分低效,但是在线程切换中,就不会出现上述的问题;
6.进程的组成
7. 进程的组织
8.进程的常见状态
(1)就绪态:进程已处于准备好运行的状态,即进程已分配到除CPU外的所有必要资源后,只要再获得CPU,便可立即执行。
(2)运行态:进程已经获得CPU,程序正在执行状态。
(3)阻塞态:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态。
进程的状态转换有四种:
(1)就绪—运行,如:当前运行进程阻塞,调度程序选一个优先级最高的进程占有处理机
(2)运行—就绪,如:当前运行进程时间片用完
(3)运行—阻塞,如:当前运行进程等待键盘输入
(4)阻塞—就绪,如:I/O操作完成,被中断处理程序唤醒
9.线程的状态
10.进程同步与互斥
(1)进程同步的主要任务:是对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作,从而使程序的执行具有可再现性。简单的来讲,由于进程并发导致的异步性,是的各个并发执行的进程以不可预知的速度向前推进,操作系统需要提供“进程同步机制”来实现一个直接制约关系,协调它们的执行次序。进程的互斥,互斥,也称之为间接制约关系,进程互斥指的是当一个进程访问某临界资源的时候,另外一个进程必须等待其访问结束并释放该资源后,才可以去访问;
同步机制遵循的原则:
【1】空闲让进;
【2】忙则等待(保证对临界区的互斥访问);
【3】有限等待(有限代表有限的时间,避免死等);
【4】让权等待,(当进程不能进入自己的临界区时,应该释放处理机,以免陷入忙等状态)。
(2)进程的互斥:互斥,也称之为间接制约关系,进程互斥指的是当一个进程访问某临界资源的时候,另外一个进程必须等待其访问结束并释放该资源后,才可以去访问;
(3)进程的同步机制:
(4)信号量机制实现进程间的同步、互斥和前驱关系
(5)管程方式实现的同步和互斥
(6)经典同步问题:生产者和消费者问题、哲学家进餐问题、读者写者问题
11.线程同步与多线程锁
(1)信号量
允许统一时刻多个线程访问同一个资源,但需要控制统一时刻访问此资源的最大线程数量
(2)互斥量
实际上是信号量的一种特殊情况,允许统一时刻只有一个线程访问同一个资源
(3)信号,也叫事件
通过通知操作的方式来保证多线程同步,还可以方便实现多线程优先级的比较操作
多线程锁与线程同步
1) 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。 2) 读写锁从广义的逻辑上讲,也可以认为是一种共享版的互斥锁。如果对一个临界区大部分是读操作而只有少量的写操作,读写锁在一定程度上能够降低线程互斥产生的代价。
3) Mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。
12.进程的通信方式

(1)管道
分为三种:普通管道(匿名管道)PIPE、命名管道NAME_PIPE、流管道S_PIPE;管道通信系统即:连接读写进程以实现他们之间通信的共享文件,类似于先进先出的队列,由一个进程写,另一个进程读。注意:各个进程要互斥的访问管道,并且数据以字符流的形式写入管道,当管道写满的时候,写进程的write()系统调用将被阻塞,等待读进程将数据取走,当读进程将数据全部取走后,管道变空,这个时候读进程的read()系统调用将被阻塞。如果没写满,就不允许读,如果没读空,就不允许写;数据一旦被读出,就从管道中被抛弃了,这就意味着读进程最多只能有一个,否则会出现读错数据的情况。
【1】普通管道为半双工,只能单向传输,只能在父子进程间使用
【2】流管道去除了第一种限制,可以双向传输
【3】命名管道去除了第二种限制,可以在许多并不相关的进程之间进行通讯
(2)系统IPC(包括消息队列、信号量、共享内存)
【1】信号量是一个计数器,用来控制多个进程对资源的访问,通常作为一种锁机制
【2】消息队列是消息的链表,存放在内核中并由消息队列标识符标识
【3】信号(也叫事件)是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生
【4】共享内存是映射一段能被其他进程访问的内存,这段共享内存由一个进程创建,但是多个进程可以访问。共享内存是最快的IPC方式;两个进程对共享内存的访问必须是互斥的,互斥访问则是通过操作系统提供的互斥工具实现的(P、V操作);
(3)SOCKET
套接口也是一种进程间通信机制,它可用于不同主机间的进程通信
13.多线程之前共享的数据
进程代码段
进程的公有数据
进程打开的文件描述符
信号的处理器
进程的当前目录
进程用户ID与进程组ID
14.进程调度
(1)调度种类
[1]高级调度:(High-Level Scheduling)又称为作业调度,它决定把后备作业调入内存运行;外存(辅存)和内存之间的调度,每个作业只调入一次、调出一次。作业调入的时候会建立相应的PCB( Process Control Block,进程管理/控制块),作业调出时撤销PCB 。高级调度主要指的是调入的问题,因为只有调入的时机需要操作系统确定,调出的时机则是作业运行结束就会被调出的。
[2]低级调度:(Low-Level Scheduling)又称为进程调度,是操作系统中最基本的一种调度,按照某种方法和策略从就绪队列中选取一个进程,将处理机分配给它;
[3]中级调度:(Intermediate-Level Scheduling)又称为内存调度,在内、外存对换区进行进程对换。暂时被调到外存等待的进程状态为挂起状态,值得注意的是,PCB并不会一起被调到外存,而是会常驻内存,被挂起进程的PCB将会被添加到挂起队列中。
(2)非抢占式调度与抢占式调度
[1]非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
[2]抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
(3)调度策略的设计
[1]响应时间: 从用户输入到产生反应的时间
[2]周转时间: 从任务开始到任务结束的时间
CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待的时间。
(4)调度算法:先来先服务调度算法、短作业优先调度算法、非抢占式优先级调度算法、抢占式优先级调度算法、高响应比优先调度算法、时间片轮转法调度算法;
<1>FIFO或First Come, First Served (FCFS)先来先服务
调度的顺序就是任务到达就绪队列的顺序。
公平、简单(FIFO队列)、非抢占、不适合交互式。
未考虑任务特性,平均等待时间可以缩短。
<2>Shortest Job First (SJF)
最短的作业(CPU区间长度最小)最先调度。
SJF可以保证最小的平均等待时间。
<3>Shortest Remaining Job First (SRJF)
SJF的可抢占版本,比SJF更有优势。
SJF(SRJF): 如何知道下一CPU区间大小?根据历史进行预测: 指数平均法。
<4>优先权调度
每个任务关联一个优先权,调度优先权最高的任务。
注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象。
<5>Round-Robin(RR)轮转调度算法
设置一个时间片,按时间片来轮转调度(“轮叫”算法)
优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多;
时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
<6>多级队列调度
按照一定的规则建立多个进程队列
不同的队列有固定的优先级(高优先级有抢占权)
不同的队列可以给不同的时间片和采用不同的调度方法
存在问题1:没法区分I/O bound和CPU bound;
存在问题2:也存在一定程度的“饥饿”现象;
<7>多级反馈队列
在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。
可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。
最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。
15.线程分类
(1)用户级线程(user level thread):对于这类线程,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。在应用程序启动后,操作系统分配给该程序一个进程号,以及其对应的内存空间等资源。应用程序通常先在一个线程中运行,该线程被成为主线程。在其运行的某个时刻,可以通过调用线程库中的函数创建一个在相同进程中运行的新线程。用户级线程的好处是非常高效,不需要进入内核空间,但并发效率不高。
(2) 内核级线程(kernel level thread):对于这类线程,有关线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只能调用内核线程的接口。内核维护进程及其内部的每个线程,调度也由内核基于线程架构完成。内核级线程的好处是,内核可以将不同线程更好地分配到不同的CPU,以实现真正的并行计算;
事实上,在现代操作系统中,往往使用组合方式实现多线程,即线程创建完全在用户空间中完成,并且一个应用程序中的多个用户级线程被映射到一些内核级线程上,相当于是一种折中方案。
16.线程的同步和互斥
当有多个线程的时候,经常需要去同步(注:同步不是同时刻)这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
(1)所谓同步,是指在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
(2)所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
17.多线程同步和互斥有几种实现方法
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
(1)用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
(2)内核模式下的方法有:事件,信号量,互斥量。
【1】临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
【2】互斥量:为协调共同对一个共享资源的单独访问而设计的。
【3】信号量:为控制一个具有有限数量用户资源而设计。
【4】事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
18.临界资源
在操作系统中,进程是占有资源的最小单位(线程可以访问其所在进程内的所有资源,但线程本身并不占有资源或仅仅占有一点必须资源)。但对于某些资源来说,其在同一时间只能被一个进程所占用。这些一次只能被一个进程所占用的资源就是所谓的临界资源。典型的临界资源比如物理上的打印机,或是存在硬盘或内存中被多个进程所共享的一些变量和数据等(如果这类资源不被看成临界资源加以保护,那么很有可能造成丢数据的问题)。
对于临界资源的访问,必须是互斥进行。也就是当临界资源被占用时,另一个申请临界资源的进程会被阻塞,直到其所申请的临界资源被释放。而进程内访问临界资源的代码被成为临界区。
19.进程和作业(程序)的区别
(1)进程是程序的一次动态执行,属于动态概念;而程序是一个静态概念;
(2)一个进程可以执行一个或几个程序,同一个程序也可由几个进程执行;
(3)程序可以作为一种软件资源长期保留,而进程是程序的一次执行;
(4)进程具有并发性,能与其他进程并发执行;程序没有并行特征;
(5)进程是一个独立的运行单位;
20.协程
线程:抢占式调度
协程:协同式调度,避免了无意义的调度,可以提高性能,但程序员必须要自己承担调度的责任。协程也失去了标准线程使用多CPU的能力。
(1) 是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程;协程不是被操作系统内核管理,而完全是由程序所控制。
(2) 协程的开销远远小于线程;
(3) 协程拥有自己寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈。
(4) 每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源。
(5) 跨平台、跨体系架构、无需线程上下文切换的开销、方便切换控制流,简化编程模型;
(6) 协程又称为微线程,协程的完成主要靠yeild关键字,协程执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行;
(7) 协程极高的执行效率,和多线程相比,线程数量越多,协程的性能优势就越明显;
(8) 不需要多线程的锁机制;
21.死锁
(1)死锁、饥饿、死循环
(2) 死锁产生的必要条件
【1】互斥条件
【2】不可剥夺条件
【3】请求和保持条件
【4】循环等待条件
对不可剥夺资源的不合理分配,可能导致死锁;
(3) 死锁的处理策略
【1】预防死锁:破坏四个必要条件的一个或几个;
<1>破坏互斥条件:SPOOLing技术,比如将打印机改造成共享设备;
<2>破坏循环等待条件:顺序资源分配法,首先给系统中的资源编号,规定每个进程必须按照编号递增的顺序请求资源。
【2】避免死锁:用某种方法防止系统进入不安全状态,从而避免死锁,如银行家算法;
【3】检测和解除死锁:允许死锁的发送,不过操作系统会负责检测出死锁的发生,然后采用某种措施(选择一个牺牲品,回滚、饥饿)来解除死锁;
(4) 鸵鸟策略
假设的前提是,这样的问题出现的概率很低。比如,在操作系统中,为应对死锁问题,可以采用这样的一种办法。当系统发生死锁时不会对用户造成多大影响,或系统很少发生死锁的场合采用允许死锁发生的鸵鸟算法,这样一来可能开销比不允许发生死锁及检测和解除死锁的小。如果死锁很长时间才发生一次,而系统每周都会因硬件故障、编译器错误或操作系统错误而崩溃一次,那么大多数工程师不会以性能损失或者易用性损失的代价来设计较为复杂的死锁解决策略,来消除死锁。鸵鸟策略的实质:出现死锁的概率很小,并且出现之后处理死锁会花费很大的代价,还不如不做处理,OS中这种置之不理的策略称之为鸵鸟策略(也叫鸵鸟算法)。
22.守护、僵尸、孤儿进程
(1) 守护进程: 运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务。
【1】特点
1) 守护进程最重要的特性是后台运行。
2) 守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的 3) 守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(shell)执行。
【2】实现
1) 在父进程中执行fork并exit推出;
2) 在子进程中调用setsid函数创建新的会话;
3) 在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;
4) 在子进程中调用umask函数,设置进程的umask为0;
5) 在子进程中关闭任何不需要的文件描述符
(2) 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程。(孤儿进程将由 init 进程收养并对它们完成状态收集工作)
一般情况下,子进程是由父进程创建,而子进程和父进程的退出是无顺序的,两者之间都不知道谁先退出。正常情况下父进程先结束会调用 wait 或者 waitpid 函数等待子进程完成再退出,而一旦父进程不等待直接退出,则剩下的子进程会被init(pid=1)进程接收,成会孤儿进程。(进程树中除了init都会有父进程)。
(3) 僵尸进程:一个进程 fork 子进程,子进程退出,而父进程没有wait/waitpid子进程,那么子进程的进程描述符仍保存在系统中,这样的进程称为僵尸进程。
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。
23.线程安全
(1) 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 (3) 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。 (4) 对于线程不安全的对象我们可以通过如下方法来实现线程安全:
① 加锁 利用Synchronized或者ReenTrantLock来对不安全对象进行加锁,来实现线程执行的串行化,从而保证多线程同时操作对象的安全性,一个是语法层面的互斥锁,一个是API层面的互斥锁.
② 非阻塞同步来实现线程安全。原理就是:通俗点讲,就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生冲突,那就再采取其他措施(最常见的措施就是不断地重试,知道成功为止)。这种方法需要硬件的支持,因为我们需要操作和冲突检测这两个步骤具备原子性。通常这种指令包括CAS SC,FAI TAS等。
③ 线程本地化,一种无同步的方案,就是利用Threadlocal来为每一个线程创造一个共享变量的副本来(副本之间是无关的)避免几个线程同时操作一个对象时发生线程安全问题。
(5)i++ ,++i是否是原子操作,是否线程安全?
i++的操作分三步:
(1)栈中取出i
(2)i自增1
(3)将i存到栈
所以i++不是原子操作,上面的三个步骤中任何一个步骤同时操作,都可能导致i的值不正确自增
++i在多核的机器上,cpu在读取内存i时也会可能发生同时读取到同一值,这就导致两次自增,实际只增加了一次。综上,我认为i++和++i都不是原子操作。
24.线程共享资源和独占资源
一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。 用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。
堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
25.进程的创建过程?需要哪些函数?需要哪些数据结构?
(1)fork()函数创造的子进程是父进程的完整副本,赋值了父亲进程的资源,包括内存的内容task_struck内容;
(2)vfork创建的子进程与父进程共享数据段,而且vfork创建的子进程将先于父进程运行;
(3)linux上创建线程一般使用的是pthread库,实际上linux也给我们提供了创建线程的系统调用,那就是clone;
26.进程创建子进程,fork详解
(1)函数原型
pid_t fork(void); //void代表没有任何形式参数;
(2)除了0号进程(系统创建的)之外,linux系统中都是由其他进程创建的。创建新进程的进程,即调用fork函数的进程为父进程,被创建的进程为子进程;
(3)fork函数不需要任何参数,对于返回值有三种情况:
【1】对于父进程,fork函数返回新建子进程的pid
【2】对于子进程,fork函数返回0;
【3】如果出错,则返回-1;
int pid=fork();
if(pid < 0){
//失败,一般是该用户的进程数达到限制或者内存被用光了
........
}
else if(pid == 0){
//子进程执行的代码
......
}
else{
//父进程执行的代码
.........
}
27.父进程和子进程之间的通信
(1)在Linux系统中实现父子进程的通信可以采用pipe()和fork()函数进行实现;
(2)对于父子进程,在程序运行时首先进入的是父进程,其次是子进程,在此我个人认为,在创建父子进程的时候程序是先运行创建的程序,其次在复制父进程创建子进程。fork()函数主要是以父进程为蓝本复制一个进程,其ID号和父进程的ID号不同。对于结果fork出来的子进程的父进程ID号是执行fork()函数的进程的ID号。
(3)管道:是指用于连接一个读进程和一个写进程,以实现它们之间通信的共享文件,又称pipe文件。
(4)写进程在管道的尾端写入数据,读进程在管道的首端读出数据。
28.内存池、进程池、线程池
首先介绍一个概念“池化技术 ”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。内存池相关的内容,建议看看Apache、Nginx等开源web服务器的内存池实现。
由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
线程池:线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
进程池与线程池同理。
内存池:内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。
三、内存管理
1.概念
2.一个程序从开始运行到结束的完整过程
(1) 预处理,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏替换,添加行号。经过预处理指令后生成一个.i文件; (2) 编译,编译过程所进行的是对预处理后的文件进行语法分析、词法分析、符号汇总,然后生成汇编代码。生成.s文件;
(3) 汇编,将汇编文件转换成二进制文件,二进制文件就可以让机器来读取。生成.o文件;
(4) 链接,由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。将其和所需的库函数链接在一起,形成一个完整的装入模块;.exe
(5)装入(装载),由装入程序将装入模块装入内存中运行;

3.逻辑地址vs物理地址vs线性地址vs虚拟内存
(1)逻辑地址与物理地址
所谓的逻辑地址(相对地址),是指计算机用户(例如程序开发者),看到的地址。例如,当创建一个长度为100的整型数组时,操作系统返回一个逻辑上的连续空间:指针指向数组第一个元素的内存地址。由于整型元素的大小为4个字节,故第二个元素的地址时起始地址加4,以此类推。事实上,逻辑地址并不一定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置,又称为绝对地址),并非是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思维。
(2)线性地址
线性地址是逻辑地址到物理地址的中间层。我们编写的代码会存在一个逻辑地址或者是段中的偏移地址,通过相应的计算(加上基地址)生成线性地址。此时如果采用了分页机制,那么吸纳行地址再经过变换即产生物理地址。在Intelk 80386中地址空间容量为4G,各个进程地址空间隔离,意味着每个进程独享4G线性空间。多个进程难免出现进程之间的切换,线性空间随之切换。基于分页机制,对于4GB的线性地址一部分会被映射到物理内存,一部分映射到磁盘作为交换文件,一部分没有映射,通过下面加深一下印象
(2)虚拟内存
另一个重要概念是虚拟内存。操作系统读写内存的速度可以比读写磁盘的速度快几个量级。但是,内存价格也相对较高,不能大规模扩展。于是,操作系统可以通过将部分不太常用的数据移出内存,“存放到价格相对较低的磁盘缓存,以实现内存扩展。操作系统还可以通过算法预测哪部分存储到磁盘缓存的数据需要进行读写,提前把这部分数据读回内存。虚拟内存空间相对磁盘而言要小很多,因此,即使搜索虚拟内存空间也比直接搜索磁盘要快。唯一慢于磁盘的可能是,内存、虚拟内存中都没有所需要的数据,最终还需要从硬盘中直接读取。这就是为什么内存和虚拟内存中需要存储会被重复读写的数据,否则就失去了缓存的意义。现代计算机中有一个专门的转译缓冲区(Translation Lookaside Buffer,TLB),用来实现虚拟地址到物理地址的快速转换。
(3)Resident Set , Thrashing
由于resident set包含预期要用的数据,理想情况下,进程运行过程中用到的数据都会逐步加载进resident set。但事实往往并非如此:每当需要的内存页面(page)不在resident set中时,操作系统必须从虚拟内存或硬盘中读数据,这个过程被称为内存页面错误(page faults)。当操作系统需要花费大量时间去处理页面错误的情况就是thrashing。
4.内存管理的方式
(1)虚拟内存:适用于管理大型对象和结构
(2)内存映射文件:适用于管理大型数据流以及在单个计算机上运行多个进程之间的共享数据
(3)内存堆栈,适用于管理大量小对象
5.内存分配的方式
(1)内存连续分配(动态分区分配)
【1】首次适应
空闲分区以地址递增次序链接,分配内存时顺序查找,找到大小能满足要求的第一个空闲分区;
【2】最佳适应
空闲分区按容量递增的次序链接,找到第一个能满足要求的空闲分区
【3】最坏适应
空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区,也就是挑选最大的分区
【4】领近适应
(2)内存的非连续分配 --- 分页存储、分段存储、段页存储
【1】分页和分段的区别
段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段,如代码段、数据段、堆栈段。每个进程有一个二维地址空间,相互独立,互不打扰。段式存储管理的优点是:没有内碎片,因为段大小可变,可以通过改变段的大小而消除内碎片。但会产生外碎片,比如4K的段换5K的段,会产生1K的外碎片;
页式存储管理是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页,而物理内存划分为同样大小的帧。程序加载时,可将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片,因为页的大小固定,但会产生内碎片,因为一个页可能填充不满
两者的不同点:目大地信内目的不同:
<1>分页是由于系统管理的需要,是信息的物理单位。分段是由于用户的需要,是信息的逻辑单位。
<2>大小不同:页的大小固定,由系统决定。段的大小不固定,由其完成的功能决定
<3>地址空间不同:页向用户提供一维地址空间,段向用户提供二维地址空间
<4>信息共享:页的保护和共享收到限制,段利于存储保护和信息共享
<5>内存碎片:分页没有外碎片,但有内碎片;分段没有内碎片,但有外碎片
【2】基本分页存储管理方式
页表用于记录逻辑地址和实际存储地址之间的映射关系,以实现从页号到物理块号的映射。访问分页系统中内存数据需要两次的内存访问。
<1>从内存中访问页表,找到指定的物理块号,加上页内偏移,得到实际物理地址
<2>根据第一次访问得到的物理地址,访问内存,存取数据
【3】基本分段存储管理
分段内存管理中,地址是二维的,其中一维为段号,另一维是段内地址。每个段的长度不一样,每个段内部都从0开始编址。段内部为连续内存分配,但段与段之间离散分配,因此有了段表机制,以从一个逻辑地址映射到一个物理地址。

【4】段页式管理
6.页面置换算法
操作系统将内存按照页面进行管理,在需要的时候才把进程相应的部分调入内存。当产生缺页中断时,需要选择一个页面写入。如果要换出的页面在内存中被修改过,变成了“脏”页面,那就需要先写会到磁盘。页面置换算法,就是要选出最合适的一个页面,使得置换的效率最高 (1)最优页面置换算法
最理想的状态下,我们给页面做个标记,挑选一个最远才会被再次用到的页面调出。当然,这样的算法不可能实现,因为不确定一个页面在何时会被用到。
(2)先进先出页面置换算法(FIFO)及其改进
这种算法的思想和队列是一样的,该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予淘汰。实现:把一个进程已调入内存的页面按先后次序链接成一个队列,并且设置一个指针总是指向最老的页面。缺点:对于有些经常被访问的页面如含有全局变量、常用函数、例程等的页面,不能保证这些不被淘汰。
(3)LFU,last frequently used,最近使用次数算法,根据使用次数来判断;
(4)最近最少使用页面置换算法LRU(Least Recently Used)
根据页面调入内存后的使用情况做出决策。LRU置换算法是选择最近最久未使用的页面进行淘汰。
【1】为每个在内存中的页面配置一个移位寄存器。(P165)定时信号将每隔一段时间将寄存器右移一位。最小数值的寄存器对应页面就是最久未使用页面。
【2】利用一个特殊的栈保存当前使用的各个页面的页面号。每当进程访问某页面时,便将该页面的页面号从栈中移出,将它压入栈顶。因此,栈顶永远是最新被访问的页面号,栈底是最近最久未被访问的页面号。
7.内部碎片和外部碎片
在内存管理中,内部碎片是已经被分配出去的的内存空间大于请求所需的内存空间。
外部碎片是指还没有分配出去,但是由于大小太小而无法分配给申请空间的新进程的内存空间空闲块。
固定分区存在内部碎片,可变式分区分配会存在外部碎片;
页式虚拟存储系统存在内部碎片;段式虚拟存储系统,存在外部碎片
为了有效的利用内存,使内存产生更少的碎片,要对内存分页,内存以页为单位来使用,最后一页往往装不满,于是形成了内部碎片。
为了共享要分段,在段的换入换出时形成外部碎片,比如5K的段换出后,有一个4k的段进来放到原来5k的地方,于是形成1k的外部碎片。
8.磁盘调度算法
- FCFS:先进先出
- SSTF:Shortest Seek Time First,最短寻道时间优先。可能会出现饥饿现象
- Elevator:向一个方向寻道,寻完后再向另一个方向寻道
9.缓冲区溢出及其危害
缓冲区溢出指的是,计算机向缓冲区填充数据时,超出了缓冲区本身的容量,溢出的数据覆盖在了合法的数据上。其危害有:
(1)程序崩溃,导致拒绝服务跳转并且执行一段恶意代码
(2)造成缓冲区溢出的主要原因是因为程序中没有仔细检查用户输入。
造成缓冲区溢出的主要原因是因为程序中没有仔细检查用户输入。
四、linux相关
1.linux文件系统
(1)
层次分析 【1】用户层,日常使用的各种程序,需要的接口主要是文件的创建、删除、读、写、关闭等;
【2】VFS层,文件相关的操作都有对应的System Call函数接口,接口调用VFS对应的函数;
【3】文件系统层,用户的操作通过VFS转到各种文件系统。文件系统把文件读写命令转化为对磁盘LBA的操作,起了一个翻译和磁盘管理的工作;
【4】缓存层;
【5】块设备层,块设备接口Block Device是用来访问磁盘LBA的层级,读写命令组合之后插入到命令队列,磁盘的驱动从队列读命令执行;
【6】磁盘驱动层;
【7】磁盘物理层;
(2)读取文件过程
【1】根据文件所在目录的inode信息,找到目录文件对应数据块;
【2】根据文件名从数据块中找到对应的inode节点信息;
【3】从文件inode节点信息中找到文件内容所在数据块块号;
【4】读取数据块内容
(3)Linux文件基本属性
2.linux是如何避免内存碎片
(1)在固定式分区分配中, 为将一个用户作业装入内存, 内存分配程序从系统分区表中找出一个能满足作业要求的空闲分区分配给作业, 由于一个作业的大小并不一定与分区大小相等, 因此, 分区中有一部分存储空间浪费掉了. 由此可知, 固定式分区分配中存在
*内碎片*.
(2)在可变式分区分配中, 为把一个作业装入内存, 应按照一定的分配算法从系统中找出一个能满足作业需求的空闲分区分配给作业, 如果这个空闲分区的容量比作业申请的空间容量要大, 则将该分区一分为二, 一部分分配给作业, 剩下的部分仍然留作系统的空闲分区。由此可知,可变式分区分配中存在*外碎片*.
(3)伙伴系统
(4)据可移动性组织页避免内存碎片
3.伙伴系统
(1)伙伴系统是一种经典的内存管理方法。Linux伙伴系统的引入为内核提供了一种用于*分配一组连续的页*而建立的一种高效的分配策略,并有效的解决了外碎片问题。
(2)Linux中的内存管理的“页”大小为4KB。把所有的空闲页分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页块。最大可以申请1024个连续页,对应4MB大小的连续内存。每个页块的第一个页的物理地址是该块大小的整数倍。
(3)当向内核
*请求分配(2^(i-1)**,**2^i]*数目的页块时,按照2^i页块请求处理。如果对应的块链表中没有空闲页块,则在更大的页块链表中找。当分配的页块中有多余的页时,伙伴系统根据多余的页框大小插入到对应的空闲页块链表中。
【1】当*释放单页*的内存时,内核将其置于CPU高速缓存中,对很可能出现在cache的页,则放到“快表”的列表中。在此过程中,内核先判断CPU高速缓存中的页数是否超过一定“阈值”,如果是,则将一批内存页还给伙伴系统,然后将该页添加到CPU高速缓存中。
【2】当*释放多页*的块时,内核首先计算出该内存块的伙伴的地址。*内核将满足以下条件的三个块称为伙伴*:(1)两个块具有相同的大小,记作b。(2)它们的物理地址是*连续*的。(3)第一块的第一个页的*物理地址*是2*(2^b)的倍数。如果找到了该内存块的伙伴,确保该伙伴的所有页都是空闲的,以便进行合并。内存继续检查合并后页块的“伙伴”并检查是否可以合并,依次类推。
(4)内核将已分配页分为以下三种不同的类型:
【1】*不可移动页*:这些页在内存中有固定的位置,不能够移动。
【2】*可回收页*:这些页不能移动,但可以删除。内核在回收页占据了太多的内存时或者内存短缺时进行页面回收。
【3】*可移动页*:这些页可以任意移动,用户空间应用程序使用的页都属于该类别。它们是通过页表映射的。当它们移动到新的位置,页表项也会相应的更新。
4. Inode节点
1) Linux操作系统引进了一个非常重要的概念inode,中文名为索引结点,引进索引接点是为了在物理内存上找到文件块,所以inode中包含文件的相关基本信息,比如文件位置、文件创建者、创建日期、文件大小等待,输入stat指令可以查看某个文件的inode信息;
2) 硬盘格式化的时候,操作系统自动将硬盘分成两个区域,一个是数据区,一个是inode区,存放inode所包含的信息,查看每个硬盘分区的inode总数和已经使用的数量,可以用df命令;
3) 在linux系统中,系统内部并不是采用文件名查找文件,而是使用inode编号来识别文件。查找文件分为三个过程:系统找到这个文件名对应的inode号码,通过inode号码获得inode信息,根据inode信息找到文件数据所在的block读取数据;
4) 除了文件名之外的所有文件信息,都存储在inode之中。
5.Linux软连接、硬连接
1) 软链接可以看作是Windows中的快捷方式,可以让你快速链接到目标档案或目录。硬链接则透过文件系统的inode来产生新档名,而不是产生新档案。
2) 软链接(符号链接) ln -s source target
硬链接 (实体链接)ln source target
3) 硬链接(hard link):A是B的硬链接(A和B都是文件名),则A的目录项中的inode节点号与B的目录项中的inode节点号相同,即一个inode节点对应两个不同的文件名,两个文件名指向同一个文件,A和B对文件系统来说是完全平等的。如果删除了其中一个,对另外一个没有影响。每增加一个文件名,inode节点上的链接数增加一,每删除一个对应的文件名,inode节点上的链接数减一,直到为0,inode节点和对应的数据块被回收。注:文件和文件名是不同的东西,rm A删除的只是A这个文件名,而A对应的数据块(文件)只有在inode节点链接数减少为0的时候才会被系统回收。
4) 软链接(soft link):A是B的软链接(A和B都是文件名),A的目录项中的inode节点号与B的目录项中的inode节点号不相同,A和B指向的是两个不同的inode,继而指向两块不同的数据块。但是A的数据块中存放的只是B的路径名(可以根据这个找到B的目录项)。A和B之间是“主从”关系,如果B被删除了,A仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。
5) 硬链接
不能对目录创建硬链接;不能对不同的文件系统创建硬链接;不能对不存在的文件创建硬链接;
6) 软连接
可以对目录创建软连接;可以跨文件系统;可以对不存在的文件创建软连接;
7) 因为链接文件包含有原文件的路径信息,所以当原文件从一个目录下移到其他目录中,再访问链接文件,系统就找不到了,而硬链接就没有这个缺陷,你想怎么移就怎么移;还有它要系统分配额外的空间用于建立新的索引节点和保存原文件的路径。
6.Linux系统应用程序的内存空间是怎么分配的,用户空间多大、内核空间多大?
Linux内核将这4G字节的空间分为两部分。
将最高的 1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。
而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间“。
因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
7.Linux的共享内存如何实现
1) 管道只能在具有亲缘关系的进程间进行通信;通过文件共享,在处理效率上又差一些,而且访问文件描述符不如访问内存地址方便;
2) mmap内存共享映射,mmap本来是存储映射功能,它可以将一个文件映射到内存中,在程序里就可以直接使用内存地址对文件内容进行访问;Linux的mmap实现了一种可以在父子进程之间共享内存地址的方式;
3) XSI共享内存,XSI是X/Open组织对UNIX定义的一 套接口标准(X/Open System Interface)。XSI共享内存在Linux底层的实现实际上跟mmap没有什么本质不同,只是在使用方法上有所区别。
4) POSIX共享内存,Linux提供的POSIX共享内存,实际上就是在/dev/shm下创建一个文件,并将其mmap之后映射其内存地址即可。
8.文件处理命令grep、awk、sed
1) grep
grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。常用来在结果中搜索特定的内容。
2) awk
awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件(或其他方式的输入流, 如重定向输入)逐行的读入(看作一个记录集), 把每一行看作一条记录,以空格(或\t,或用户自己指定的分隔符)为默认分隔符将每行切片(类似字段),切开的部分再进行各种分析处理。
3) sed
sed更侧重对搜索文本的处理,如修改、删除、替换等等。sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
9.查询进程占用CPU的命令---top和ps
1) top
top命令可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。
2) ps
ps命令就是最基本进程查看命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵尸、哪些进程占用了过多的资源等等.总之大部分信息都是可以通过执行该命令得到。ps是显示瞬间进程的状态,并不动态连续;如果想对进程进行实时监控应该用top命令。
10.Linux和windows平台下栈空间的大小
windows是编译器决定栈的大小,记录在可执行文件中,默认是1M。linux是操作系统来决定的,在系统环境变量中设置, ulimit -s 字节数 命令查看修改,但是linux默认栈大小为10M;
vs编译器设置:属性—>设置à链接à输出à栈分配à重新设置;
11.Linux重定向
(1)重定向符号 :>
输出重定向到一个文件或设备 覆盖原来的文件 >! 输出重定向到一个文件或设备 强制覆盖原来的文件 >> 输出重定向到一个文件或设备 追加原来的文件 < 输入重定向到一个程序
(2)标准错误重定向符号:2>
将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 b-shell 2>> 将一个标准错误输出重定向到一个文件或设备 追加到原来的文件 2>&1 将一个标准错误输出重定向到标准输出 注释:1 可能就是代表 标准输出 >& 将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 c-shell |& 将一个标准错误 管道 输送 到另一个命令作为输入
(3)命令重导向示例
在 bash 命令执行的过程中,主要有三种输出入的状况,分别是: \1. 标准输入;代码为 0 ;或称为 stdin ;使用的方式为 < \2. 标准输出:代码为 1 ;或称为 stdout;使用的方式为 1> \3. 错误输出:代码为 2 ;或称为 stderr;使用的方式为 2>
12.Linux常用命令
1) ls命令,不仅可以查看linux文件包含的文件,而且可以查看文件权限;
2) cd命令,切换当前目录到dirName
3) pwd命令,查看当前工作目录路径;
4) mkdir命令,创建文件夹
5) rm命令,删除一个目录中的一个或多个文件或目录
6) rmdir命令,从一个目录中删除一个或多个子目录项,
7) mv命令,移动文件或修改文件名
8) cp命令,将源文件复制至目标文件,或将多个源文件复制至目标目录
9) cat命令,显示文件内容;
10) touch命令,创建一个文件
11) vim命令,
12) which命令查看可执行文件的位置,whereis查看文件的位置,find实际搜寻硬盘查询文件名称;
13) chmod命令,用于改变linux系统文件或目录的访问权限,421,ewr
14) tar命令,用来压缩和解压文件。tar本身不具有压缩功能,只具有打包功能,有关压缩及解压是调用其它的功能来完成。
15) chown命令,将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;
16) ln命令;
17) grep命令,强大的文本搜索命令,grep全局正则表达式搜素;
18) ps命令,用来查看当前运行的进程状态,一次性查看,如果需要动态连续结果使用top;
19) top命令,显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等;
20) kill命令,发送指定的信号到相应进程。不指定型号将发送SIGTERM(15)终止指定进程。