操作系统总结




上图是进程的虚拟地址空间示意图。

堆栈段:

  1. 为函数内部的局部变量提供存储空间。

  2. 进行函数调用时,存储“过程活动记录”。

  3. 用作暂时存储区。如计算一个很长的算术表达式时,可以将部分计算结果压入堆栈。

数据段(静态存储区):

  包括BSS段的数据段,BSS段存储未初始化的全局变量、静态变量。数据段存储经过初始化的全局和静态变量。

代码段:

  又称为文本段。存储可执行文件的指令。

堆:

  就像堆栈段能够根据需要自动增长一样,数据段也有一个对象,用于完成这项工作,这就是堆(heap)。堆区域用来动态分配的存储,也就是用 malloc 函数活的的内存。calloc和realloc和malloc类似。前者返回指针的之前把分配好的内存内容都清空为零。后者改变一个指针所指向的内存块的大小,可以扩大和缩小,他经常把内存拷贝到别的地方然后将新地址返回。

 

1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 

2、堆区(heap):由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 

3、全局区(静态区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。 

4、文字常量区:常量字符串就是放在这里的。 程序结束后由系统释放。 

 

 

5、程序代码区:存放函数体的二进制代码。



 

[cpp]  view plain  copy
  1. #include<stdio.h>  
  2. #include<string.h>  
  3. #include<stdlib.h>  
  4. #include<unistd.h>  
  5.   
  6. void main()  
  7. {  
  8.     char str[6]="hello";  
  9.   
  10.     pid_t pid=fork();  
  11.   
  12.     if(pid==0)  
  13.     {  
  14.         str[0]='b';  
  15.         printf("子进程中str=%s\n",str);  
  16.         printf("子进程中str指向的首地址:%x\n",(unsigned int)str);  
  17.     }  
  18.     else  
  19.     {  
  20.         sleep(1);  
  21.         printf("父进程中str=%s\n",str);  
  22.         printf("父进程中str指向的首地址:%x\n",(unsigned int)str);  
  23.     }  
  24. }  
子进程中str=bello
子进程中str指向的首地址:bfdbfc06
父进程中str=hello

父进程中str指向的首地址:bfdbfc06

这里就涉及到物理地址和逻辑地址(或称虚拟地址)的概念。

为了执行,程序应被调入内存并放在进程内。在磁盘上等待调入内存以便执行的进程形成了输入队列

用户程序在运行之前需要经历若干步骤。在这些步骤中,地址可能有不同的表示形式
    符号(源程序中)
    可重定位的地址(目标模块)
    绝对地址(内存映像)



将指令与数据捆绑到内存地址可以在以下步骤的任何一步中执行:
    (1)编译时:MS-DOS的COM格式程序
    (2)加载时:编译器生成可重定位代码
    (3)执行时:进程在执行时可以从一个内存段移到另一内存段,那么捆绑必须延迟到执行时才进行。

内存空间:  是由存储单元(字节或字)组成的一维连续的地址空间; 内存空间用来存放当前正在运行程序的代码及数据, 是程序中指令本身地址所指的存储器、亦即程序计数器所指的存储器。

被绑定到物理地址空间的逻辑地址空间概念是内存管理的中心。

逻辑地址:用户程序经过编译之后的每个目标模块都是以0为基址开始顺序编址的,由CPU生成,这种地址称为相对地址逻辑地址

物理地址内存中存储单元的地址, 可直接寻址。

逻辑地址空间由程序中逻辑地址组成的地址范围叫做逻辑地址空间,或简称为地址空间

内存空间/物理空间/绝对空:由内存中一系列存储单元所限定的地址范围

内存管理单元(MMU):运行时从虚拟地址映射到物理地址的硬件设备称为内存管理单元
                                    用户进程所生成的地址在送交内存之前,都将加上重定位寄存器的值。
                                    用户程序处理的是逻辑地址,它永远不会看到真实的物理地址。

重定位:程序和数据装入内存时需对目标程序中的地址进行修改,这种把逻辑地址转变为内存物理地址的过程叫做重定位

静态重定位:当用户程序被装入内存时,一次性实现逻辑地址到物理地址的转换,以后不再转换(一般在装入内存时由软件完成)。
动态重定位 在程序运行过程中要访问数据时再进行地址变换(在逐条指令执行时完成地址映射; 为了提高效率, 此工作一般由硬件地址映射机制来完成;  硬件支持,软硬件结合完成),  硬件上需要一对寄存器的支持

虚拟存储器:用户能作为可编址内存对待的虚拟存储空间,它使得、用户逻辑存储器和物理存储器分离,是操作系统给用户提供的一个比真实内存空间大得多的地址空间
注:局部性原理
       应用程序在运行之前没有必要将之全部装入内存,而仅须将那些当前要运行的少数页面或段先装入内存,便可运行,其余部分暂留在盘上。当用户看到自己的程序能在系统中正常运行时,他会认为该系统具有的内存容量一定会比自己的程序大,或者说用户所感觉到的内存容量比实际内存容量大=》虚的

       用户程序看不见真正的物理地址。用户只生成逻辑地址,且认为进程的地址空间为0到max。物理地址范围从R+0到R+max,R为基地址,地址映射-将程序地址空间中使用的逻辑地址变换成内存中的物理地址的过程。由内存管理单元(MMU)来完成。

       fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。

       每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同没什么奇怪。
具体过程是这样的:
       fork子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。

      这就是所谓的“写时复制”。正因为fork采用了这种写时复制的机制,所以fork出来子进程之后,父子进程哪个先调度呢?内核一般会先调度子进程,因为很多情况下子进程是要马上执行exec,会清空栈、堆。。这些和父进程共享的空间,加载新的代码段。。。,这就避免了“写时复制”拷贝共享页面的机会。如果父进程先调度很可能写共享页面,会产生“写时复制”的无用功。所以,一般是子进程先调度滴。

假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所对应的 物理地址是不一样的。所以两个进城中的这两个地址相互之间没有任何关系。

(注1:在理解时,你可以认为fork后,这两个相同的虚拟地址指向的是不同的物理地址,这样方便理解父子进程之间的独立性)
(注2:但实际上,Linux为了提高 fork 的效率,采用了 copy-on-write 技术,fork后,这两个虚拟地址实际上指向相同的物理地址(内存页),只有任何一个进程试图修改这个虚拟地址里的内容前,两个虚拟地址才会指向不同的物理地址(新的物理地址的内容从原物理地址中复制得到))


子进程父进程文件描述符共享:http://keren.blog.51cto.com/720558/170822
父子进程各有各的全局变量。

2 exec家族

exec家族一共有六个函数,分别是:

[cpp]  view plain  copy
  1. <span style="font-family:Microsoft YaHei;font-size:12px;">(1)int execl(const char *path, const char *arg, ......);  
  2. (2)int execle(const char *path, const char *arg, ...... , char * const envp[]);  
  3. (3)int execv(const char *path, char *const argv[]);  
  4. (4)int execve(const char *filename, char *const argv[], char *const envp[]);  
  5. (5)int execvp(const char *file, char * const argv[]);  
  6. (6)int execlp(const char *file, const char *arg, ......);</span>  

其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。

    exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。

fork之后,子进程从父进程那继承了什么

  • 子进程继承父进程
    • 用户号UIDs和用户组号GIDs
    • 环境Environment
    • 堆栈
    • 共享内存
    • 打开文件的描述符
    • 执行时关闭(Close-on-exec)标志
    • 信号(Signal)控制设定
    • 进程组号
    • 当前工作目录
    • 根目录
    • 文件方式创建屏蔽字
    • 资源限制
    • 控制终端
  • 子进程独有

    • 进程号PID
    • 不同的父进程号
    • 自己的文件描述符和目录流的拷贝
    • 子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
    • 不继承异步输入和输出
  • 父进程和子进程拥有独立的地址空间和PID参数。

  • 子进程从父进程继承了用户号和用户组号,用户信息,目录信息,环境(表),打开的文件描述符,堆栈,(共享)内存等。
  • 经过fork()以后,父进程和子进程拥有相同内容的代码段、数据段和用户堆栈,就像父进程把自己克隆了一遍。事实上,父进程只复制了自己的PCB块。而代码段,数据段和用户堆栈内存空间并没有复制一份,而是与子进程共享。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。由于父进程的PCB和子进程的一样,所以在PCB中断中所记录的父进程占有的资源,也是与子进程共享使用的。这里的“共享”一词意味着“竞争”     

2、进程、线程的区别:http://blog.csdn.net/pmt123456/article/details/57424068

多进程 & 多线程的区别与适用场景

      进程是具有一定功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源调度和分配的一个独立单位。
      线程是指进程内的一个执行单元,也是进程内的可调度实体,是CPU调度和分派的基本单位,与进程的区别:

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以发并发执行

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源

(4)系统开销:在创建和撤销进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建撤销线程的开销。


注:子进程产生时会拷贝父进程的变量的值,然后生成自己的一份
虽然两个指针的值相同,但他们是不同进程空间的,所以会映射到不同的物理内存。
子进程复制了父进程的数据之后,两者就完全没有关系了。
多进程地址空间是独立的 要共享数据需通过进程间通信 
也可考虑用多线程来解决 Linux多线程间地址空间是共享的


相关题目

有一个变量int a=0;两个线程同时进行+1操作,每个线程加100次,不加锁,最后a的值是

单核 100~200,100每次同时+1

多核2~200

i++不是原子操作,也就是说,它不是单独一条指令,而是3条指令:
1、从内存中把i的值取出来放到CPU的寄存器中
2、CPU寄存器的值+1
3、把CPU寄存器的值写回内存
如果是单线程操作,i++毫无问题;但是在多核处理器上,用多线程来做i++会有什么问题呢?
我再仔细地重复一遍问题:进程有一个全局变量i,还有有两个线程。每个线程的功能,就是循环100次,执行i++。问线程代码全部执行完毕后,i的值是否一定是200?如果不是,它的最大最小值是多少?
=========分析=======
i++是由3条指令构成的运算操作,两个线程在i变量上共计需要执行100(次循环)*3(条指令)*2(个线程)=600条指令,这600条指令在某种排列下会导致最终i的值仅为2。
(下面是我复制过来的)
假设两个线程的执行步骤如下: 
 1. 线程A执行第一次i++,取出内存中的i,值为0,存放到寄存器后执行加1,此时CPU1的寄存器中值为1,内存中为0;
 2. 线程B执行第一次i++,取出内存中的i,值为0,存放到寄存器后执行加1,此时CPU2的寄存器中值为1,内存中为0;
 3. 线程A继续执行完成第99次i++,并把值放回内存,此时CPU1中寄存器的值为99,内存中为99;
 4. 线程B继续执行第一次i++,将其值放回内存,此时CPU2中的寄存器值为1,内存中为1;
 5. 线程A执行第100次i++,将内存中的值取回CPU1的寄存器,并执行加1,此时CPU1的寄存器中的值为2,内存中为1;
 6. 线程B执行完所有操作,并将其放回内存,此时CPU2的寄存器值为100,内存中为100; 
 7. 线程A执行100次操作的最后一部分,将CPU1中的寄存器值放回内存,内存中值为2;


3、进程、线程间通信http://blog.csdn.net/pmt123456/article/details/56479737

(1)线程同步的方式有哪些?
         互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
         信号量:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。 
         事件(信号):通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。

4、进程间传递文件描述符

http://blog.csdn.net/y396397735/article/details/50684558

5、send、recv阻塞和非阻塞

6、内存共享、内存映射

mmp进行内存映射的原理:
(1)在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成)
(2)如图,左边描述的是一个进程地址空间,右边是通过描述符fd访问的文件。加入vm_area_struct描述的是一个文件映射的虚寸空间,成员vm_file便指向被映射的file结构,vm_pgoff就是该虚寸的其实地址在vm_file文件里的文件偏移,单位为物理页面。mmap系统调用的工作就是准备这样一段虚存空间,并建立vm_area_file结构体,将其传给具体的设备驱动程序。
(3)建立虚拟地址空间和文件或设备的物理地址之间的映射


7、可重入、线程安全、异步信号安全

8、什么是缓冲区溢出?有什么危害?其原因是什么?
缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。
危害有以下两点:
       程序崩溃,导致拒绝额服务
       跳转并且执行一段恶意代码
造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入。 

9、死锁
两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
 产生死锁的原因主要是
       1) 因为系统资源不足。
       2) 进程运行推进的顺序不合适。
       3) 资源分配不当等
死锁发生的必要条件(Coffman条件)
       (1)资源独占(mutual exclusion),即一个资源在同一时刻只能分配给一个进程。
            如果进程申请某一资源, 而该资源正被另一进程占用, 则申请者需等待, 直到占有者释放该资源。
       (2)不可剥夺(non-preemption),即资源申请者不能从资源占有者手中抢夺资源。
       (3)保持申请(hold-while-applying),进程已经保持申请了至少一个资源,但又提出了新资源的请求,而新资源又被其他进程占有,此时进程阻塞,但对自己已获得资源保持不放。
       (4)循环等待(circular wait),即存在一个进程等待序列{p1,p2,…,pn}, 其中p1等待p2占用的某一资源, p2等待p3占用的某一资源,…,pn等待p1占用的某一资源。
        仅当四个条件同时满足死锁才会发生。
注意:
        1.只要破坏一个条件, 死锁就不会发生.
        2.每类资源只有一个时, 为充要条件.
处理死锁的基本方法:
        (1)预防死锁:预防策略,事先预防,破坏产生死锁的必要条件
                                   资源一次性分配:(破坏请求和保持条件)
                                   可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
                                   资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
                                   
        (2)避免死锁:预防策略,实现不采取各种限制措施破坏死锁产生的必要条件,而是利用动态资源分配策略,使每次资源分配都安全,避免死锁
        (3)检测死锁:化简资源分配图
                                   资源分配图的简化过程:(1)在图中找到一个进程顶点Pi,Pi没有请求边或请求变均能立即满足;(2)若找到这样的Pi,则将与Pi相连的边全部删去,转(1);否则化简结束
                                   如果化简后所有的进程定点都成了孤立点,则称该图可以完全化简;否则,则称该图是不可完全化简的。
        (4)死锁恢复:允许死锁发生,检测出后将发生的死锁清除掉。
                                   
                                   剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
                                         撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

预防死锁
            a、摈弃“请求和保持条件”
                  静态资源分配法,所有进程在开始之前,都必须一次性申请所需全部资源,充足则分配,如果一种资源不满足,即时其他资源都空闲,也不分配给该进程,摈弃保持条件
            b、摈弃“不可剥夺”条件
                  当进程需猪哥的提出对各个资源的请求,如果一个已经获取,再提出另一种资源请求而不能立即满足时,必须释放它已经保持了的所有资源,以后重新申请。这意味着某一进程已经占有的资源,在运行过程中会被暂时释放,摈弃了“不剥夺”
            c、摈弃“环路等待”条件

避免死锁银行家算法


10、进程有哪几种状态
就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源
运行状态:占用处理机资源运行,处于此状态的进程数小于等于CPU数
阻塞状态: 进程等待某种条件,在条件满足之前无法执行 
 



11、 分页和分段有什么区别
虚拟内存将内存抽象成一个巨大的、统一的存储数组,进而将用户看到的逻辑内存与物理内存分开。
       只要部分程序需要放在内存中就能使程序执行
       逻辑地址空间可以比物理地址空间大。
       允许地址空间被多个进程共享
       允许更多进程被创建
段是信息的逻辑单位,它是根据用户的需要划分的,因此段对用户是可见的 ;页是信息的物理单位,是为了管理主存的方便而划分的,对用户是透明的。
段的大小不固定,决定于用户所编写的程序;页大大小固定,由系统决定
段向用户提供二维地址空间,程序员在标识一个地址时,既需给出段名(比如数据段、代码段和堆栈段等),又需给出段内地址;页向用户提供的是一维地址空间,即单一的线性空间
段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制:段有读、写和执行三种权限;而页只有读和写两种权限

分段存储方式的优缺点
     分页对程序员而言是不可见的,而分段通常对程序员而言是可见的,因而分段为组织程序和数据提供了方便。与页式虚拟存储器相比,段式虚拟存储器有许多优点:
(1)    段的逻辑独立性使其易于编译、管理、修改和保护,也便于多道程序共享。
(2)    段长可以根据需要动态改变,允许自由调度,以便有效利用主存空间。
(3)    方便编程,分段共享,分段保护,动态链接,动态增长
 因为段的长度不固定,段式虚拟存储器也有一些缺点:
(1)    主存空间分配比较麻烦。
(2)    容易在段间留下许多碎片,造成存储空间利用率降低。
(3)    由于段长不一定是2的整数次幂,因而不能简单地像分页方式那样用虚拟地址和实存地址的最低若干二进制位作为段内地址,并与段号进行直接拼接,必须用加法操作通过段起址与段内地址的求和运算得到物理地址。因此,段式存储管理比页式存储管理方式需要更多的硬件支持。

12、 用户态和核心态的区别

内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
为什么要有用户态和内核态
       由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态 和 内核态当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。
       当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。


指令划分:
特权指令:只能由操作系统使用、用户程序不能使用的指令。  举例:启动I/O   内存清零  修改程序状态字  设置时钟    允许/禁止终端   停机
非特权指令:用户程序可以使用的指令。  举例:控制转移  算数运算  取数指令   访管指令(使用户程序从用户态陷入内核态)
3.特权级别:


特权环:R0、R1、R2和R3
R0相当于内核态,R3相当于用户态;
不同级别能够运行不同的指令集合;


CPU状态之间的转换
用户态--->内核态:唯一途径是通过中断、异常、陷入机制(访管指令)
内核态--->用户态:设置程序状态字PSW

内核态与用户态的区别
1)内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;
2)当程序运行在0级特权级上时,就可以称之为运行在内核态。
3)运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。   
4)这两种状态的主要差别是:
处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ;
而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。

用户态切换到内核态的3种方式
      a. 系统调用
          这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
      b. 异常
          当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
      c. 外围设备的中断
          当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

用户态与内核态的切换
     所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作. 这时需要一个这样的机制: 用户态程序切换到内核态, 但是不能控制在内核态中执行的指令
     这种机制叫 系统调用 , 在CPU中的实现称之为 陷阱指令 (Trap Instruction)。 他们的工作流程如下:
  1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.
  2. 用户态程序执行陷阱指令
  3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
  4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
  5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果
printf执行过程
13、内存池、进程池、线程池。
        首先介绍一个概念“池化技术 ”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。内存池相关的内容,建议看看Apache、Nginx等开源web服务器的内存池实现。
       由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。
      线程池:线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
     进程池与线程池同理。
     内存池:内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。
15、系统调用与库函数的区别
        系统调用:操作系统为用户程序与硬件设备进行交互提供的一组接口,发生在内核地址空间。
        库函数:把一些常用的函数编写完放到一个文件里,编写应用程序时调用(include),这是由第三方提供的,发生在用户地址空间。
        在移植性方面,不同操作系统的系统调用一般是不同的,移植性差;而库函数一般具有较好平台移植性,通过库文件(静态库或动态库)向程序员提供功能性调用。程序员无需关心平台差异,由库来屏蔽平台差异性。
        在调用开销方面,系统调用需要在用户空间和内核环境间切换,开销较大;而库函数调用属于“过程调用”,开销较小。
16、 守护、僵尸、孤儿进程的概念
僵尸进程:一个进程 fork 子进程,子进程退出,而父进程没有 wait/waitpid子进程,那么子进程的进程描述符仍保存在系统中,这样的进程称为僵尸进程。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程。(孤儿进程将由 init 进程收养并对它们完成状态收集工作)

17、进程调度算法
        (1)先来先服务算法:按照进程进入就绪队列的先后次序进行选择
                                             属于非剥夺调度方式,看似公平,但对后进入的短进程,I/O型进程不公平
         (2)短进程优先:从进程的就绪队列中选择所需运行时间最短的进程占用处理机
                                        属于非剥夺方式,不适合于分/实时系统(难以预测,长进程饥饿)
         (3)时间片轮转算法:系统将所有就绪进程按到达时间的先后顺序排成一个队列,进程调度程序总是按FCFS选择队首的进程执行,并规定执行完一个时间片时,就将它送至就绪队列的队尾,再把处理机分配给就绪队列的队首进程。
                                       属于剥夺调度方式,分时系统的典型调度算法。    
          (4)优先级调度算法:每次从就绪队列汇总选择优先级最高的进程占用处理



        消费者与生产者UNP2
        银行家算法







22、 内存泄漏
       内存泄漏检测工具
       任务管理器

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值