聊聊操作系统-进程与线程

来源:互联网 发布:php课程简介 编辑:程序博客网 时间:2024/06/14 04:00

工作也有两年了,在研究很多项目时发现很多问题追根溯源都会到计算机底层的知识,也是越来越发现编程语言只是一层外壳,这个外壳需要去和操作系统协商使用后者管理的的计算机资源,包括存储资源和计算资源。如果计算机底层知识不牢靠,遇到一些问题还真是不好分析,也很容易成为职业上升的瓶颈。现在回想起大学时学习这么课时是比较抽象的,那时没有太多编程经验,知识很难落地,造成了只知其然不知其所以然的情况,所以现在重新梳理一下计算机操作系统的一些底层知识,在这里记录一下。

我们说操作系统是运行在内核态的一套软件,它的主要作用是隐藏硬件,呈现给程序或者程序员良好的、优雅的、一致的计算机抽象模型,并且负责将计算机的存储资源和计算资源有序分配给应用程序。


这篇文章我先总结一下计算机的进程和线程相关知识。

(一)进程相关

(1)什么是进程

我个人觉得理解进程需要从两个角度来看,第一从动态角度讲“进程”是一个正在运行程序的实例,它包括程序计数器、寄存器和变量的当前值。

第二从静态角度讲“进程”是一个容器,该容器用于聚集相关的计算机资源,线程,打开的文件等。


在单CPU系统中,CPU在多个进程之间快速来运行的,这是一种伪并行模式。所以在对进程编程时绝不能对时序做任何确定的假设。

(2)什么时候进程被创建

  • 系统初始化:必须linux系统启动时init进程会被创建,所有其他进程都是这个进程的子进程
  • 正在运行的进程通过系统调用创建另一个进程:比如我在这篇博客Redis持久化机制原理分析中提到的Redis主进程通过系统调用fork创建一个子进程来执行RDB
  • 用户请求创建一个进程:如用户在Window系统中双击桌面上的word快捷方式

在linux系统中进程的创建时通过系统调用fork进行创建,调用fork的进程成为父进程,被创建的进程成为子进程。父进程和子进程都有私有的内存地址空间,如果父进程修改了属于自己的变量,这个修改对子进程是不可见的。

pid = fork(); //创建成功pid>0,实际为子进程的pidif(pid < 0){hand_error();  // pid <0 创建进程失败} else if (pid > 0){// 父进程逻辑} else {// 子进程逻辑}

系统调用fork调用一次返回两次,在父进程中返回子进程的pid,在子进程中返回0,可以通过这个返回值来判断执行父进程代码还是子进程代码,系统调用fork创建一个完全与父进程相同的子进程,包括相同的文件描述符,相同的寄存器内容和其他所有内容,当然现代操作系统使用了 写时复制Copy on write的方式来优化fork的性能,fork刚创建的子进程采用了共享的方式,只用指针指向了父进程的物理资源。当子进程真正要对某些物理资源写操作时,才会真正的复制一块物理资源来供子进程使用。这样就极大的优化了fork的性能

如果子进程想获得自己的pid可以通过getpid系统调用。

(3)什么时候进程退出

  • 正常运行结束程序退出(正常情况):在linux系统中程序运行结束后调用exit退出
  • 出现错误程序退出(正常情况):编译一个找不到的文件等
  • 严重错误(非正常情况):引用不存在的内存、除数为0等情况
  • 被其他用户杀死(非正常情况):在linux系统中用户kill命令杀死进程


(4)进程层次结构

在unix系统中当一个进程创建了另一个进程之后,我们称前者为父进程后者为子进程,一个父进程有0个或多个子进程,这些进程构成了一个进程树。

在Windows系统中进程没有父子进程的层次结构的概念。


(二)线程相关

(1)什么是线程

我们常说线程是cpu调用的最小单位,每个进程中至少有一个线程,当然这只是从cpu调度的角度来看线程;如果从存储资源的角度来看线程的话,

线程解决的最大问题就是它可以很简单地表示共享资源的问题,这里说的资源指的是存储器资源,资源最后都会加载到物理内存,一个进程的所有线程都是共享这个进程的同一个虚拟地址空间的,也就是说从线程的角度来说,它们看到的物理资源都是一样的,这样就可以通过共享变量的方式来表示共享资源,也就是直接共享内存的方式解决了线程通信的问题。

理论上说Linux内核是没有线程这个概念的,只有内核调度实体(Kernal Scheduling Entry, KSE)这个概念。linux的线程本质上是一种轻量级的进程,是通过clone系统调用来创建的。

(2)线程的创建

在文章上面我们提到通过系统调用fork创建进程,如果说父进程是一个多线程运行模式,那么现在面临的一个问题是:是不是需要在新创建的子进程中创建父进程中的所有线程,如果全部创建假设父进程中一个线程正在等待输入而阻塞,那么等输入完成之后数据是传给父进程还是子进程的线程;如果不全创建,如果父进程中一个线程A持有锁,线程B等待锁,那么子进程中如果没有创建线程A,那么线程B在子进程中将永远阻塞。

为了解决这个问题,Linux系统引入了一个强大的系统调用clone

pid=clone(function,stack_ptr,sharing_flags,args)

function:为新建线程的执行入口

args:为新创建线程执行参数

stack_ptr:为新创建线程的私有堆栈指针

sharing_flags:这个是一个位图不同的值可以指定,新创建线程是复制调用clone函数的线程数据结构,还是共享这部分数据结构。

具体参数值可以参考下图:



(3)线程没有层级结构而是对等的关系

进程采用父子结构,init进程是最顶端的父进程,其他进程都是从init进程派生出来的。这样就很容易理解进程是如何共享内核的代码和数据的了。

而线程采用对等结构,即线程没有父子的概念,所有线程都属于同一个线程组,线程组的组号等于第一个线程的线程号。



原创粉丝点击