3.1、操作系统(作业、进程、线程、内存管理、垃圾回收)

来源:互联网 发布:淘宝开内衣店店铺简介 编辑:程序博客网 时间:2024/05/20 18:49

操作系统
进程、线程、内存管理、垃圾回收、缓存[1][2]

1、进程

1.1、基本概念

作业:用户在一次解决或是一个事务处理过程中要求计算机系统所做的工作的集合,它包括用户程序、所需要的数据集控制命令等。作业是由一系列有序的步骤组成的。作业的完成要经过作业提交、作业收容、作业执行和作业完成4个阶段。在执行一个作业可能会运行多个不同的进程。

进程:程序在一个数据集上的一次运行过程。是操作系统资源分配的基本单位。

在Windows下,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位.  进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。

线程:是进程中的一个实体,是被操作系统独立调度和执行的基本单位。一个进程包含一个或多个线程。

线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。主线程将执行运行时宿主, 而运行时宿主会负责载入CLR。

简单总结:

作业是向计算机提交任务的任务实体,

而进程是执行实体,是资源分配的基本单位

线程是处理机调度的基本单位

1.2、进程的状态

运行中的进程可能具有以下三种基本状态。

  1)就绪状态(Ready):

  进程已获得除处理器外的所需资源,只要分配了处理器进程就可执行。

就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。

  2)运行状态(Running):

  进程获得处理器资源,其程序正在处理器上执行。

处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

  3)阻塞状态(Blocked):

  正在执行的进程,由于等待某种条件(如I/O操作或进程同步)而无法继续执行,便放弃处理器而处于阻塞状态。

该事件发生前即使把处理机分配给该进程,也无法运行。

             

   

进程状态的转换

进程状态五态模型:
运行状态(Running)当一个进程正在处理机上运行时。
就绪状态(Ready)一个进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行.
等待状态又称阻塞状态(Blocked)一个进程正在等待某一事件而暂停运行时。如等待某资源,等待输入/输出完成。
创建状态(NEW)一个进程正在被创建,还没被转到就绪状态之前的状态。
结束状态(Exit)一个进程正在从系统中消失时的状态,这是因为进程结束或由于其他原因所导致。

状态变化图


1.3、进程间通信实现

windows的进程间的通信方式有:

1.文件映射;2. 共享内存(是文件映射的一种特殊情况);3.邮件槽(mailslot)(点对点消息队列); 4.匿名管道;5;命名管道; 6. 剪贴板;7.动态数据交换;8.对象链接与嵌入;9.远程过程调用;10.动态链接库;11.socket;12.WM_COPYDATA .
windows和linux共有的进程间通信方式:1. 消息(linux中叫做信号) 2. 共享内存  3. 邮槽  4. 管道   5.socket

linux进程间通信的方式有:1.管道 2.信号量 3.共享内存 4.消息队列 5.套接字 6.信号

      


2、线程

进程和线程的区别:

1、进程间是独立的,在内存空间、上下文环境上。而线程是运行在进程空间内的,同一进程所产生的线程共享同一内存空间。

2、进程间是可以并发执行的,线程之间也可以并发执行。但同一进程中的两段代码只有在引入线程的情况下才能并发执行。

3、线程是属于进程的,当进程退出时,该进程所产生的线程都会被强制退出并清除。

4、线程占用的资源要少于进程占用的资源,线程间的切换速度比进程间的切换快的多。

动态链接库与静态链接库的优缺点

    目前以lib为后缀的库有两种:静态链接库和动态链接库的倒入库。静态链接库本身就包含了实际执行代码、地址符号等等,相对于导入库而言,其实际的执行代码位于动态库中,倒入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。
    静态链接库是一个或多个obj文件的打包,当应用工程在使用静态连接库的时候,静态连接库要参与编译,在生成执行文件之前的链接过程中,将静态链接库的全部指令直接链接到可执行文件中,故而,生成可执行文件后,静态链接库*.lib文件即可弃之不用。
    动态链接库是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个*.dll文件中,该dll包含一个或多个已编译、链接并与使用它们的进程分开存储的函数。dll还有助于共享数据和资源。多个应用程序可同时访问内存中单个dll副本的内容。

静态链接库的优点 

 (1) 代码装载速度快,执行速度略比动态链接库快; 

 (2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。 

动态链接库的优点 

 (1) 更加节省内存,减少页面交换,节省磁盘空间;

 (2) 更易于升级;

    DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性。

 (3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;

 (4)适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。

不足之处

 (1) 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;

 (2) 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入是动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行是动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统死掉。这在早期Windows中很常见。

多线程编程步骤

a、编写线程函数
    DWORD WINAPI YourThreadFunc(LPVOID lpThreadParm);
    该函数输入一个LPVOID型的参数,可以是一个DWORD型的整数,也可以是一个指向一个缓冲区的指针, 返回一个DWORD型的值。象WinMain函数一样,这个函数并不由操作系统调用, 操作系统调用包含在KERNEL32.DLL中的非C运行时的一个内部函数,如StartOfThread,然后由StartOfThread函数建立起一个异常处理框架后,调用我们的函数。

    以下代码为两个线程函数:

void FunCount(PVOID arg)
{
  while(1)
  {
    printf("funcout is executing.....\n");
    Sleep(10000);
  }
}

void Function1(PVOID arg)
{
  while(1)
  {
    printf("function1 is executing.....\n");
    Sleep(20000);
  }
}

b、创建一个线程
    一个进程的主线程是由操作系统自动生成,若需要一个主线程创建额外的线程,调用CreateThread完成。
    HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpsa,
DWORD cbstack,
LPTHREAD_START_ROUTINE lpStartAddr, 
LPVOID lpvThreadParm,
DWORD fdwCreate,
LPDWORD lpIDThread);

    其中lpsa参数为一个指向SECURITY_ATTRIBUTES结构的指针。如果想让对象为缺省安全属性的话,可以传一个NULL,如果想让任一个子进程都可继承一个该线程对象句柄,必须指定一个SECURITY_ATTRIBUTES结构,其中bInheritHandle成员初始化为TRUE。参数cbstack表示线程为自己所用堆栈分配的地址空间大小,0表示采用系统缺省值。

  参数lpStartAddr用来表示新线程开始执行时代码所在函数的地址,即为线程函数。lpvThreadParm为传入线程函数的参数,fdwCreate参数指定控制线程创建的附加标志,可以取两种值。如果该参数为0,线程就会立即开始执行,如果该参数为CREATE_SUSPENDED,则系统产生线程后,初始化CPU,登记CONTEXT结构的成员,准备好执行该线程函数中的第一条指令,但并不马上执行,而是挂起该线程。最后一个参数lpIDThread 是一个DWORD类型地址,返回赋给该新线程的ID值。

    此外,还可以使用_beginthread等函数来创建线程。

    handle=(HANDLE)_beginthread(FunCount,0, NULL);
    handle1=(HANDLE)_beginthread(Function1,0,NULL);


c、终止线程
    如果某线程调用了ExitThread 函数,就可以终止自己。

    VOID ExitThread(UINT fuExitCode);

  这个函数为调用该函数的线程设置了退出码fuExitCode后, 就终止该线程。

    调用TerminateThread函数亦可终止线程。

    BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

  该函数用来结束由hThread参数指定的线程, 并把dwExitCode设成该线程的退出码。当某个线程不在响应时,我们可以用其他线程调用该函数来终止这个不响应的线程。

d、设定线程的相对优先级

    当一个线程被首次创建时,它的优先级等同于它所属进程的优先级。在单个进程内可以通过调用SetThreadPriority函数改变线程的相对优先级。一个线程的优先级是相对于其所属的进程的优先级而言的。

    BOOL SetThreadPriority(HANDLE hThread,int nPriority);

  其中参数hThread是指向待修改 优先级线程的句柄,nPriority可以是以下的值:

  THREAD_PRIORITY_LOWEST, 
  THREAD_PRIORITY_BELOW_NORMAL, 
  THREAD_PRIORITY_NORMAL, 
  THREAD_PRIORITY_ABOVE_NORMAL, 
  THREAD_PRIORITY_HIGHEST

e、挂起及恢复线程

  创建挂起状态的线程(通过传递CREATE_SUSPENDED标志给函数CreateThread来实现),系统创建指定线程的核心对象,创建线程的栈,在CONTEXT结构中初始化线程CPU注册成员。然而,线程对象被分配了一个初始挂起计数值1,这表明了系统将不再分配CPU去执行线程。要开始执行一个线程,另一个线程必须调用ResumeThread并传递给它调用CreateThread时返回的线程句柄。

    DWORD ResumeThread(HANDLEhThread);

  一个线程可以被挂起多次。如果一个线程被挂起3次, 则该线程在它被分配CPU之前必须被恢复3次。除了在创建线程时使用CREATE_SUSPENDED标志,还可以用SuspendThread函数挂起线程。

    DWORD SuspendThread(HANDLE hThread);

实例:
#include <iostream>   #include <windows.h>   using namespace std;   //这是2个线程模拟买火车票的小程序DWORD WINAPI Fun1Proc(LPVOID lpParameter);//thread dataDWORD WINAPI Fun2Proc(LPVOID lpParameter);//thread dataint index = 0;int tickets = 10;HANDLE hMutex;int main(){      HANDLE hThread1, hThread2;//创建线程hThread1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);CloseHandle(hThread1);CloseHandle(hThread2);//创建互斥现象//只允许一个线程拥有对共享资源的独占//创造了一个名为tickets并且归属于创建它的进程的资源。hMutex = CreateMutex(NULL,TRUE,_T("tickets"));if (hMutex){if (ERROR_ALREADY_EXISTS == GetLastError()){cout << "only one instance can run !" << endl;return 0;}}//第一个参数指定所申请的资源的句柄,//第二个参数一般指定为INFINITE,表示如果没有申请到资源就一直等待该资源,//如果指定为0,表示一旦得不到资源就返回,//也可以具体地指定等待多久才返回,单位是千分之一秒。WaitForSingleObject(hMutex,INFINITE);//该函数用于释放一个独占资源,进程一旦释放该资源,该资源就不再属于它了,//如果还要用到,需要重新申请得到该资源。ReleaseMutex(hMutex);ReleaseMutex(hMutex);Sleep(400000);return 0;}//线程1的入口函数DWORD WINAPI Fun1Proc(LPVOID lpParameter){while (true){ReleaseMutex(hMutex);WaitForSingleObject(hMutex, INFINITE);if (tickets > 0){Sleep(1);cout << "thread1 sell ticket:" << tickets-- << endl;}elsebreak;ReleaseMutex(hMutex);}return 0;}//线程2 的入口函数DWORD WINAPI Fun2Proc(LPVOID lpParameter){while (true){ReleaseMutex(hMutex);WaitForSingleObject(hMutex, INFINITE);if (tickets > 0){Sleep(1);cout << "thread2 sell ticket:" << tickets-- << endl;}elsebreak;ReleaseMutex(hMutex);}return 0;}


3、内存管理

内存管理方式和优缺点

   主要分为:页式管理、段式管理、段页式管理。

   页式管理基本原理是将各进程的虚拟空间划分为若干个长度相等的页;页式管理把内存空间按照页的大小划分成片或者页面,然后把页式虚拟地址与内存地址建立一一对应的页表;并用相应的硬件地址变换机构来解决离散地址变换问题。页式管理采用请求调页或预调页技术来实现内外存存储器的统一管理。

    优点是没有外碎片,每个内碎片不超过页的大小。

    缺点是,程序全部装入内存,要求有相应的硬件支持。例如地址变换机构缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。这增加了机器成本,增加了系统开销。

    段式管理基本思想是把程序按照内容或过程函数关系分段,每段都有自己的名字。一个用户作业或进程所包括的段对应一个二维线形虚拟空间,也就是一个二维虚拟存储器。段式管理程序以段为单位分配内存,然后通过地址映射机构把段式虚拟地址转换为实际内存物理地址。

    优点是可以分别编写和编译,可以针对不同类型的段采用不同的保护,可以按段为单位来进行共享,包括通过动态链接进行代码共享。

    缺点是会产生碎片。

    段页式管理:为了实现段页式管理,系统必须为每个作业或进程建立一张段表以管理内存分配与释放、缺段处理等。另外由于一个段又被划分成了若干个页。每个段必须建立一张页表以把段中的虚页变换成内存中的实际页面。

    显然与页式管理时相同,页表中也要有相应的实现缺页中断处理和页面保护等功能的表项。段页式管理的段式管理与页式管理方案结合而成的所以具有他们两者的优点。

    但反过来说,由于管理软件的增加,复杂性和开销也就随之增加了。另外需要的硬件以及占用的内存也有所增加。使得速度降下来。

X64和X86区别

    Intel曾用8086、80286、80386等作为其PC用CPU的型号表示法

    x64 是64位的操作系统
    x86 是32位的操作系统,X86_64的缩写;纯64位计算机架构用IA64表示;32位兼容的64位架构用amd64表示,有时也把amd64架构的CPU成为X86_64;
    32位系统:
    cpu的位是指一次性可处理的数据量是多少,1字节=8位,32位处理器可以一次性处理4个字节的数据量,依次类推。32位操作系统针对的32位的CPU设计。
    64位系统:

    指特别为64位架构计算机系统而设计的操作系统。64位操作系统的优点,在于能够利用64位处理器的优势,在处理多媒体内容时能够有更佳的表现。
    32位系统与64位系统的区别:
    64位系统是32位系统的更高级版本,32位系统可识别内存到4G,64位系统可识别内存超过4G以上内存。