操作系统基本知识整理

本基本知识整理及代码源于牛客网C++面试宝典导读,

  网址https://www.nowcoder.com/tutorial/93/156e55e0579d4a678e857b34d572c278

进程与线程

  基本概念:

  进程是对运行中程序的封装,是系统进行资源调度和分配的基本单位,实现了操作系统的并发;

  线程是进程的子任务,是CPU调度和分配的基本单位,实现了进程内部的并发;

  线程是操作系统可识别的最小执行和调度单位。每个线程都肚子占用一个虚拟处理器:独自的寄存器组,指令计数器和CPU状态。

  每个线程完成不同任务,但共享同一地址空间(堆区,全局区(静态区),文字常量区,映射文件,目标代码),打开的文件队列和其他内核资源。

  区别:

  1.一个线程只能属于一个进程,而一个进程可以有多个线程且至少一个,线程依赖于进程存在。

  2.进程有独立的内存单元,多个线程共享进程内存(代码段(代码和常量),数据段(全局/静态变量),扩展段(堆存储))。每个线程栈段独立(局部变量和临时变量)

  3.进程是资源分配最小单位,线程时CPU调度最小单位。

  4.系统开销:创建和撤销进程时,系统要分配和回收资源,所以创建和撤销开销大于线程的。进行进程切换时,设计到当前进程CPU环境保存以及新被调度运行进程CPU环境设置。线程切换只需保存设置少量寄存器内存,不涉及存储器。

  5.通信:进程间通信IPC,线程可以直接读写数据段来通信(需要同步和互斥)

  6.进程间不会互相影响,线程则是一个线程挂掉导致整个进程挂掉

  7.进程适应于多核、多机分布;线程适用于多核。

  

  进程通信:

  管道、系统IPC、套接字socket

  1.管道:管道通信分为无名管道和有名管道:无名管道只能用于有亲缘关系间进程之间通信,有名管道除此之外可以与其他进程通信,pipe(int fd[2]),fd[0]读,fd[1]写。

  无名管道PIPE:1) 半双工,固定读端和写端

           2)只能用于具有亲缘关系的进程间通信

          3)linux系统将其当做一种特殊文件,读写使用普通的read和writre函数,但不属于其他任何文件系统,只存在

          于内核的内存空间中。

  有名管道FIFO: 1) 可以在无关进程之间交换数据,同样半双工。

         2) FIFO有路径名与之关联,是一种特殊设备文件在文件系统中。(需要更多了解)

  2.系统IPC

  消息队列:参考https://www.jianshu.com/p/7598e5ed5200

  存放在内核中,一个消息队列有一个标识符(队列ID)标记。(消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息;

  特点:1)消息队列面向记录,其中消息具有特定格式和特定优先级,不一定先进先出。

       2)独立于进程,进程终止时消息队列及内容不会被删除。

       3)消息队列可以随机查询,可以按照消息类型读取

  信号量

  信号量是一个计数器,主要用于控制进程互斥和同步

  特点:1)信号量用于进程互斥同步,结合共享内存使用。

     2)信号量基于系统的PV操作,都是原子操作。

        3)信号量PV操作可以加减任意正整数。

       4)信号量可以成组。

 

  信号

  信号用于通知接受进程某个事件已经发生。

 

  共享内存:

  创建一块内存,多个进程可以共同访问,需要依靠信号量和互斥锁等同步。

  特点:

  1)速度最快的IPC,直接对内存存取。

  2)需要进行同步

  3)信号量+共享内存通常一起使用。

 

  3.套接字SOCKET:

  socket可以用于不同主机之间进程通信。

 

  线程间通信:

  1.临界区:通过多线程串行化访问公共资源或代码。

  2.互斥量:采用互斥对象机制,只有拥有互斥对象的线程才能访问公共资源。

  3.信号量:同上

  4.事件(信号):通过通知操作来保持多线程同步,可以方便的实现多线程优先级比较操作。

 

  Linux虚拟地址空间

  防止不同进程同一时刻在物理内存中运行对物理内存争夺。

  Linux虚拟地址空间范围0-4G,高1G字节为内存使用的内核空间,低3G供各进程使用。

  每个进程都有自己的虚拟地址空间,将需要的虚拟内存映射并存储到物理内存上。在运行时,进程加载只建立好虚拟内存与磁盘文件的映射,运行到相应位置时通过缺页异常来拷贝数据。

  虚拟内存好处:

  1. 扩大地址空间

  2. 内存保护:各进程运行在各自虚拟内存空间中不干扰其他进程。虚拟内存对特定内存地址提供写保护,防止代码和数据被恶意篡改。

  3.公平内存分配:每个内存分配相当于同样大小的虚存

  4.进程通信时,可以通过虚拟内存共享。

  5.不同进程使用相同代码,只需将虚存映射到物理内存上。

  6.提高系统并发度:内存中有许多程序片段,在等待从虚存映射到磁盘上读取时可以执行其他进程。

  7.需要连续内存空间时,只需在虚拟内存分配连续空间,不需要实际物理内存的连续空间。

  代价:

  1. 虚存管理需要建立大量数据结构,占用额外内存。

  2.虚拟地址到物理地址转换增加指令执行时间。

  3.页面换入换出需要磁盘IO耗时。

 

操作系统中程序内存结构

  

 

 

   代码区:该内存区域只读,存放代码及只读常数变量

  已初始化数据区(数据段):已初始化全局变量及静态变量。

  BSS段:存放未初始化的全局变量和静态变量。

  代码段和数据段编译时分配内存,bss由链接器在运行时获取内存,运行前置为0。

  栈区:由编译区释放,存放函数参数及局部变量。栈区从高地址位向低地址位增长,是一块连续的内存区域。申请超过界限时会提示溢出,栈区空间较小。

  堆区:用于动态分配内存,由程序员申请分配和释放,从低地址向高地址增长,链式存储结构。

 

  操作系统的缺页中断

  使用malloc()和mmap()(将文件映射到虚拟内存中,对内存进行修改也将对文件进行修改)等内存分配函数,分配时知识建立了进程虚拟地址空间,并没有分配对应内存,当进程访问时则会触发缺页异常。

  缺页中断:在请求分页系统中,可以通过查询页表中状态为来确定所访问页面是否存在,若不在则会发生缺页中断,根据页表中的外存地址将其调入内存。

  中断处理步骤:

  1. 保护CPU现场

  2.分析中断原因

  3.执行中断处理程序

  4.恢复线程继续执行

  缺页中断与普通中断区别:

  1.在指令执行期间产生和处理缺页中断信号

  2.一条智能可能出现多次缺页中断

  3.缺页中断时返回继续执行,一般中断发挥执行下一条指令。

 

  fork和vfork区别

  基础知识:

  fork:

  调用fork会创建一个新进程,子进程返回0, 父进程返回pid,如果出错返回负值。

  常见使用fork创建进程后使用exec()载入二进制映像替换当前进程,子进程则会执行新的二进制映像(即其他程序)

  早期Unix系统使用fork会将所有内部数据结构复制一份,复制进程页表项,并将父进程地址空间内容按页复制到子进程地址空间中。现代Unix系统则选择写时复制而不是整体复制。

  vfork;

  在调用后子进程必须立刻执行一次exec系统调用或者调用_exit()退出,其他与fork一致。vfork为了防止早期调用fork按页复制后调用exec又载入其他映像,vfork()只进行复制内部的内核数据结构。

  写时复制:

  采用写时复制减少fork时父进程空间进程整体复制带来的开销。

  如果进程不需要修改资源,则只需要保存一个指向该资源的指针。只有需要修改时才复制资源并修改。

  使用虚拟内存时,写时复制时以页为基础进行,所以只要不修改全部地址空间就不需要全部复制。调用fork结束后,父进程和子进程共享父进程原始页。

  

  区别:

  1.fork()子进程拷贝父进程数据段和代码段;vfork()子进程与父进程共享数据段。

  2.fork()父子进程执行次序不确定;vfork()保证子进程先运行,调用exec或exit前与父进程数据共享,调用后父进程才继续执行。

  3.vfork()子进程先运行,若在调用exec或exit之前子进程依赖父进程下一步动作则会死锁。

  

  如何修改文件最大句柄数

  linux默认最大为1024个,有两种方法修改

  1. ulimit -n <可以同时打开的文件数>,只对当前进程有效。 

  2. 对所有进程都有效的方法,修改Linux系统参数

  vi /etc/security/limits.conf 添加

  *  soft  nofile  65536

  *  hard  nofile  65536

  将最大句柄数改为65536

 

  并发和并行

  并发:只多个程序同时运行,不同程序的指令交织执行。

  并行:指在CPU上同时运行,多核CPU不同程序运行在不同核上互不影响。

   

  MySQL的端口号是多少,如何修改这个端口号

  使用 show global variables like ‘port’查看,mysql默认端口3306。(sqlsever为1433)

  修改端口号:

  编辑/etc/my.cnf文件,早期版本有可能是my.conf文件名,增加端口参数,并且设定端口,注意该端口未被使用,保存退出。

  

  操作系统中的页表寻址

  页式内存管理,内存分成固定长度的页片。页表时操作系统为每个进程维护一个从虚拟地址到物理地址的映射关系的数据结构,页表内容就是映射。页表每一项都记录了这个页的基地址,由逻辑高位部分找到逻辑地址的页基地址,再由页基地址偏移一定长度就得到最后物理地址,偏移的长度由逻辑地址低位决定。

 

  操作系统中的中断

  中断指CPU对系统发生的某个时间做出的反应,CPU暂停正在执行的程序,保存现场后自动去执行响应的处理程序,处理完后返回继续执行。

  3种中断:CPU外部引起:时钟中断,I/O中断。

      CPU内部事件或程序执行引起:程序非法操作,地址越界,浮点溢出。

      使用系统调用。

  中断响应由硬件实施,中断反应由软件实施(中断处理例程)。

 

  共享内存相关api

  Linux允许不同进程访问同一个逻辑内存,提供了一组API,头文件在sys/shm.h中。

  1)新建共享内存shmget

  int shmget(key_t key,size_t size,int shmflg);

  key:共享内存键值,可以理解为共享内存的唯一性标记。

  size:共享内存大小

  shmflag:创建进程和其他进程的读写权限标识。

  返回值:相应的共享内存标识符,失败返回-1

  2)连接共享内存到当前进程的地址空间shmat

  void *shmat(int shm_id,const void *shm_addr,int shmflg);

  shm_id:共享内存标识符

  shm_addr:指定共享内存连接到当前进程的地址,通常为0,表示由系统来选择。

  shmflg:标志位

  返回值:指向共享内存第一个字节的指针,失败返回-1

  3)当前进程分离共享内存shmdt

  int shmdt(const void *shmaddr);

  4)控制共享内存shmctl

  和信号量的semctl函数类似,控制共享内存

  int shmctl(int shm_id,int command,struct shmid_ds *buf);

  shm_id:共享内存标识符

  command: 有三个值

  IPC_STAT:获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中。

  IPC_SET:设置共享内存的状态,把buf复制到共享内存的shmid_ds结构。

  IPC_RMID:删除共享内存

  buf:共享内存管理结构体。

 

  死锁发生的条件以及如何解决死锁

  死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下:

互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;

请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源

不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放

环路等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链

解决死锁的方法即破坏上述四个条件之一,主要方法如下:

资源一次性分配,从而剥夺请求和保持条件

可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件

资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

  

  C++/C的内存分配

  

32bitCPU可寻址4G线性空间,每个进程都有各自独立的4G逻辑地址,其中0~3G是用户态空间,3~4G是内核空间,不同进程相同的逻辑地址会映射到不同的物理地址中。其逻辑地址其划分如下:

各个段说明如下:

3G用户空间和1G内核空间

静态区域:

text segment(代码段):包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。

data segment(数据段):存储程序中已初始化的全局变量和静态变量

bss segment:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量,对于未初始化的全局变量和静态变量,程序运行main之前时会统一清零。即未初始化的全局变量编译器会初始化为0

动态区域:

heap(堆): 当进程未调用malloc时是没有堆段的,只有调用malloc时采用分配一个堆,并且在程序运行过程中可以动态增加堆大小(移动break指针),从低地址向高地址增长。分配小内存时使用该区域。  堆的起始地址由mm_struct 结构体中的start_brk标识,结束地址由brk标识。

memory mapping segment(映射区):存储动态链接库等文件映射、申请大内存(malloc时调用mmap函数)

stack(栈):使用栈空间存储函数的返回地址、参数、局部变量、返回值,从高地址向低地址增长。在创建进程时会有一个最大栈大小,Linux可以通过ulimit命令指定

posted @ 2019-09-01 17:17  我是好人007  阅读(562)  评论(0编辑  收藏  举报