OS2 进程与线程

当硬件介入的时候,用数量考虑时间复杂度很明显不合适。


基本概念

进程也叫作业:a program in execution.

线程是在进程中对于进程概念的推广。



进程是资源的基本分配单位,注意这里的程序计数器、寄存器都是内存空间用于存放状态的,并不是实际硬件,包括:

  1. program counter
  2. registers
  3. text section
  4. data section
  5. stack heap

实际上在单线程阶段,进程就分为分配的虚拟地址和控制线程,因此线程是cpu调用的基本单位;除了基本的东西,线程的其他基本原则是共享,这些基本的东西是:

  1. stack
  2. register
  3. program counter
  4. thread id and other info

线程的一些特点:1 更轻量级 2 cpu调度的基本单位,线程的切换在现代OS中基本上ns级的  3 线程之间由于可以共享因此一个线程能修改另一个线程的stack等,保护其实由于本身就是共享因此是不必要的。



进程和线程由于所处环境的复杂性面对调用算法并发的复杂性,执行时间都是不确定的,也不要做任何时间假设。



在unix系统中,进程是有层次关系的:init----》产生登录进程----》口令正确-----》产生shell进程----》启动用户需要的进程,很明显的进程树;unix中用pstree来查看

在win系统中,虽然父进程创建子进程有一个句柄返回,但这个句柄是可以给其他进程操作的,因此树形层次结构基本没有;

而对于线程,不管啥系统都和win类似。



创建进程:unix中用fork+execve;win32中用createProcess    

杀死进程:unix中用kill;win32中用terminateProcess

正常退出:unix中用exit;win32中用exitProcess

对于unix系统来说,fork通过copy_on_write+页表实现;父子进程的页表中通过指向共同的物理块实现fork而不是马上复制。当修改某个部分时复制一个page修改。
线程操作:pthread_create、pthread_exit、pthread_join、pthread_yield



进程的状态与转换

进程包括 new、ready、running、waiting、terminate这几个状态;

ready和waiting的区别是:waiting主动释放cpu、ready被剥夺cpu

实际上在主动释放:1 running--》terminate 2 running---》waiting 或者被动释放: running---》ready 或者waiting--》ready 时都会要执行cpu调度算法;scheduling algorithm还会在时钟中断时调用。

进程的内存表达方式

进程在内存中通过PCB的方式保存,PCB保存所有状态信息,PCB与PCB之间是用数组实现的,这样满足局部性原理,不用反复更新寄存器等;然后其他的队列:比如I/O队列、就绪队列都是通过一排指针指向PCB。

线程的方式

线程的实现方式有两种:

  1. 用户空间:在用户空间生成线程表并管理。两个主要问题与解决:a 阻塞一个线程对于OS来说等于阻塞一个进程,因此线程调用毫无意义,解决方法是用一个wrapper:select系统调用检测直到I/O数据有才进行调用而不是选择阻塞等待I/O;b线程内部没有时钟中断因此可以一直执行,解决方法是主动pthread_yield
  2. 内核空间:在内核中生成线程表并管理。慢,但很有效
调度算法

评价指标:cpu利用率;吞吐量;周转时间;等待时间

调度可能发生在:

  1. running---->waiting
  2. running----->ready
  3. running----->terminate
  4. waiting------>ready

还可能发生在时钟中断时,根据时钟中断时是否发生调度进行进程切换,分为抢占式和非抢占式。

现在一般通过round robin+优先级调度实现,round robin的时间片选择很重要,一般取20到50ms,如果太快,进程切换的时间相对执行时间太大,效率过低(进程切换要保存上下文,切换到内存态,调度选择进程,刷新存储,切换成用户态执行);时间片太长,则排队后面的进程得到的响应太晚。优先级调用在批处理系统中有FCFS,SJF,SRTF等等。现代操作系统一般用多级反馈队列实现,进程完全用完时间片则扔到更低一级的队列中,没用完再拉到高处,基本思想是尽量让I/O密集型进程优先级高,因为I/O较慢,充分利用I/O;防止饿死,可以将队列之间用时间片轮转;注意linux中实现有不同,会有两个队列,用完扔到另一处,直到第一个没有进程为止。再互换再次执行。多级反馈队列在图形上看像邻接表。。。。。。。。。。。

多cpu情况

一般每个cpu都有自己的队列,各干各的,减少cache的刷新;亲和性和负载均衡有时也是矛盾

亲和性:

  1. processor affinity
  2. soft affinity
  3. hard affinity
进程和线程的通信
1 共享内存

会带来critical section的问题

临界区指的是访问共享内存的程序片段,好的解决原则:mutual exclusion;progress;bounded waiting

解决方法有以下几个:

  1. 关中断:很明显不大合适,用户控制了中断行为,对于cpu来说是执行了disable指令,但多cpu时很明显有问题
  2. 锁变量:思想很好,但锁的同步怎么办?
  3. peteson 解法(对于两个进程):核心代码是:
    while(interested[1-process]==true&&turn==process);

    当对面没有兴趣即interested[1-process]==false时不用循环等待,否则当自己执行了turn的赋值必须等待对方将turn赋了自己的值才会继续往下,虽然有延迟,但还是先来先执行了。
  4. TSL:将锁变量用硬件实现了,TSL得到值然后赋一个true返回得到的值,TSL在执行的时候会关闭内存总线防止其他CPU访问,和关闭中断不同。
  5. 信号量:包含所有的一切,信号量就是一个可以计数的整数,定义了原子P、V操作,这个可以用类似TSL的方式实现;信号量有两种形式:一个不用计数的简化版本:互斥量,一个条件变量
  6. 管程:语言级实现方法:每次只允许一个线程调用管程方法,管程的实现也参照了信号量,一个互斥量一个自我停止的wait和唤醒notify(java中还有notifyAll)。

几个注意点

这里的互斥量也好条件变量也好,都要一个共同的访问变量,这个共同的访问变量可以通过存放在内核中的变量通过系统调用实现,也可以通过进程间共享内存实现(这个很微妙的模糊了进程和线程的区别);

对于单cpu来说,线程的忙等很明显不合适,没有yield不会让出cpu不会进行线程切换更别说将任务往下执行了,解决方法是挂起阻塞变成waiting,因此对于用户级别的实现的线程很明显是错的,因为阻塞了整个进程,解决方式是通过yield;

对于网络来说,这里的所有的方法通过同一片内存中的东西实现,很明显不合适,解决方法是消息传递;

忙等不是好主意,当得不到共享区域的锁时,通过挂起变成waiting更合适;

posix接口中:

  1. 互斥量:pthread_mutex_init、pthread_mutex_destroy、pthread_mutex_lock、pthread_mutex_unlock
  2. 条件变量:pthread_cond_init、pthread_cond_destroy、pthread_cond_wait、pthread_cond_signal
2消息传递

网络中就等待传递,在同一个进程中通过内核中介传递。

屏障实现进程组的同步
常用的例子




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值