linux系统知识 - 进程&线程

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

参考链接

http://www.cnblogs.com/vamei/archive/2012/09/20/2694466.html

http://www.cnblogs.com/vamei/archive/2012/10/09/2715393.html

背景知识

指令:计算机能做的事情其实非常简单,比如计算两个数之和、寻找到内存中的某个地址。这些最基础的计算机动作称为指令。

程序:一系列指令所构成的集合。通过程序,我们可以让计算机完成复杂的动作。程序大部分时候被存储为可执行文件。

进程:进程是程序的一个具体实现,是正在执行的程序。

进程创建

计算机开机时,内核只创建了一个init进程。剩下的所有进程都是init进程通过fork机制(新进程通过老进程复制自身得到)建立的。

进程存活于内存中,每个进程在内存中都有自己的一片空间

fork

fork是一个系统调用。

当进程fork时,linux在内存中开辟出新的一片内存空间给新的进程,并将老的进程空间的内容复制到新的进程空间中,此后两个进程同时运行。

fork通常作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。

通常在调用fork函数之后,程序会设计一个if选择结构。当PID等于0时,说明该进程为子进程,那么让它执行某些指令,比如说使用exec库函数(library function)读取另一个程序文件,并在当前的进程空间执行 (这实际上是我们使用fork的一大目的:为某一程序创建进程);而当PID为一个正整数时,说明为父进程,则执行另外一些指令。由此,就可以在子进程建立之后,让它执行与父进程不同的功能。

进程运行

进程内存使用(内存地址由高到低)

Stack(堆栈)

以帧(stack frame)为单位。

帧中存储当前激活函数的参数和局部变量,以及该函数的返回地址。

当程序调用函数时,stack向下增长一帧。

当激活函数返回时,会从栈中弹出(pop,读取并从栈中删除)该帧,并根据帧中记录的返回地址,经控制权交给返回地址所指向的指令

Unused Area

Heap(堆)

Global Data

从低到高存放常量、已初始化的全局/静态变量、未初始化的全局/静态变量

Text(instruction codes)

存放指令(也就是代码段)

图示

  

注意点:

1.最下方的帧和Global Data一起,构成了当前的环境。当前激活的函数可以从环境中调取需要的变量

2.Text和Global data在进程一开始的时候就确定了,并在整个进程中保持固定大小。

进程附加信息

除了进程自身内存中的内容,每个进程还要包括一些附加信息,包括PID、PPID、PGID等,用来说明进程身份、进程关系以及其他统计信息。

这些信息保存在内核的内存空间中,内核为每个进程在内核的内存空间中保存一个变量(task_struct结构体)以保存上述信息。

内核可以通过查看自己空间中各个进程的附加信息就能知道进程的情况,而不用进入到进程自身的空间。

每个进程的附加信息中有位置专门用于保存接收到的信号。

进程运行的过程中,通过调用和返回函数,控制权不断在函数间转移。

进程在调用函数的时候,原函数的帧保留有离开原函数时的状态,并为新的函数开辟所需的帧空间。

在调用函数返回时,该函数的帧所占据的空间随着帧pop而清空。进程再次回到原函数的帧中所保留的状态,并根据返回地址所指向的指令继续执行。

上面的过程不断继续,栈不断增长或减小直到main()函数返回,栈完全清空,进程结束。

当程序使用malloc时,堆会向上增长,增长的部分就是malloc从内存中申请的空间。

malloc申请的空间会一直存在,直到使用free系统调用来释放,或者进程结束。

内存泄漏 - 没有释放不再使用的堆空间,导致堆不断增大,可用内存不断减少。

栈溢出 Stack overflow

栈和堆的大小会随着进程的运行增大或者变小,当栈顶和堆相遇时,该进程再无可用内存。则进程栈溢出,进程中止。

如果清理及时,依然栈溢出,则需要增加物理内存。

进程组

每个进程属于一个进程组,每个进程组可以包含多个进程。

进程组会有一个进程组领导进程(process group id),领导进程的PID成为进程组的ID,即PGID。

领导进程可以先终结,此时进程组依然存在,并持有相同的PGID,知道进程组中最后一个进程终结

进程组的重要作用是可以发信号给进程组。进程组的所有进程都会收到该信号

会话session

多个进程组构成一个会话。

会话是由其中的进程建立的,该进程叫做会话的领导进程(session leader),领导进程的PID成为识别会话的SID(session id)

会话中的每一个进程组称为一个工作(job)。

会话可以有一个进程组成为会话的前台工作,其他进程组是后台工作。

每个会话可以连接一个控制终端。

当控制终端有输入输出时,或者由终端产生的信号(ctrl+z/ctrl+c),都会传递给会话的前台工作。

一个命令可以末尾加上&让它后台运行。

可以通过fg从后台拿出工作,使其变为前台工作

bash支持工作控制,sh不支持。

前台工作

独占stdin、(独占命令行窗口,只有运行完了或者手动终止,才能执行其他命令)

可以stdout、stderr

后台工作

不继承stdin(无法输入,如果需要读取输入,会halt暂停)

继承stdout、stderr(后台任务的输出会同步在命令行下显示)

SIGHUP信号

用户退出session时,系统向该session发送SIGHUP信号

session将SIGHUP信号发送给所有子进程

子进程收到SIGHUP信号后,自动退出

前台工作肯定会收到SIGHUP信号

后台工作是否会收到SIGHUP信号,决定于huponexit参数($shopt | grep hupon),这个参数决定session退出时SIGHUP信号是否会发送给后台工作

disown

disown可以将工作移出后台工作列表,从而即使huponexit参数打开,在session结束时,系统也不会向不在后台任务列表中的任务发送SIGHUP信号

标准I/O

标准I/O继承于session,如果session结束,被移出的后台任务有需要使用I/O,就会报错终止执行

因此需要将目标任务的标准I/O进行重定向。

nohup

no hang up - 不挂起

nohup的进程不再接受SIGHUP信号

nohup的进程关闭了stdin,即使在前台。

nohup的进程将stdout、stderr重定向到nohup.out

screen和tmux

作用:终端复用器,可以在同一个终端里面,管理多个session

不做深入。

多线程原理

程序运行过程中只有一个控制权存在,称为单线程

程序运行过程中有多个控制权存在,称为多线程

单CPU的计算机,可以通过不停在不同线程的指令间切换,造成类似多线程的效果

由于一个栈只有栈顶帧可被读写,相应的,只有栈顶帧对应的函数处于运行中(激活状态)

多进程的程序通过在一个进程内存空间中创建多个栈(每个栈之间以一定空白区域隔开,以备栈的增长),从而绕开栈的限制

多个栈共享进程内存中的text、heap、global data区域。

由于同一个进程空间存在多个栈,任何一个空白区域被填满,都会导致stack overflow的问题

多线程并发

多线程相当于一个并发系统,并发系统一般同时执行多个任务

如果多个任务对共享资源同时有操作,则会导致并发问题

并发问题解决是将原先的两个指令构成一个不可分隔的原子操作

多线程同步

同步是指一定的时间内只允许某一个线程访问某个资源。

可以通过互斥锁、条件变量和读写锁来实现同步资源

互斥锁(mutex):把某一段程序代码加锁,即表示某一时间段内只允许一个线程去访问这一段代码,其他的线程只能等待该线程释放互斥锁,才可以访问该代码段

条件变量:一般与互斥锁相结合,解决每个线程需要不断尝试获得互斥锁并检查条件是否发生时出现的资源浪费情况。

读写锁:

一个unlock的RW lock可以被某个线程获取R锁或者W锁;

如果被一个线程获得R锁,RW lock可以被其它线程继续获得R锁,而不必等待该线程释放R锁。但是,如果此时有其它线程想要获得W锁,它必须等到所有持有共享读取锁的线程释放掉各自的R锁。

如果一个锁被一个线程获得W锁,那么其它线程,无论是想要获取R锁还是W锁,都必须等待该线程释放W锁。

进程间通信

IPC( interprocess communication )

方式:文本、信号、管道、传统IPC

进程通信 - 文本

一个进程将信息写入到文本中,另一个进程去读取

由于是位于磁盘,所以效率非常低

进程通信 - 信号

可以以整数的形式传递非常少的信息

进程通信 - 管道

可以在两个进程之间建立通信渠道,分为匿名管道PIPE和命名管道FIFO

进程通信 - 传统IPC

主要指消息队列(message queue)、信号量(semaphore)、共享内存(shared memory)。

这些IPC的特点是允许多个进程之间共享资源。但是由于多进程任务具有并发性,所以也需要解决同步的问题。

传统IPC - 消息队列

与PIPE相似,也是建立一个队列,先放入队列的消息最先被取出。

不同点

消息队列允许多个进程放入/取出消息。

每个消息可以携带一个整数识别符(message_type),可以通过识别符对消息分类

进程从消息队列中取出消息时,按照先进先出的顺序取出,也可以只取出某种类型的消息(也是先进先出的顺序)。

消息队列不使用文件API(即调用文件+参数)

消息队列不会自动消失,他会一直存在于内核中,直到某个进程删除该队列

传统IPC - semaphore

与mutex类似,是一个进程计数锁,允许被N个进程取到,有更多的进程申请时,会等待

一个semaphore会一直在内核中,直到某个进程删除它

传统IPC - 共享内存

一个进程可以将自己内存空间中的一部分拿出来,允许其他进程读写。

在使用共享内存的时候,同样要注意同步的问题。

我们可以使用semaphore同步,也可以在共享内存中建立mutex或者其他的线程同步变量来同步

进程终结

当子进程终结时候,它会通知父进程,并清空自己所占据的内存,并在内核中留下自己的退出信息(exit code,0 - 正常退出,>0 - 异常退出),在这个信息里,会解释该进程为什么退出。

父进程得知子进程终结时,对该子进程使用wait系统调用,wait系统调用会取出子进程的退出信息,并清空该信息在内核中所占据的空间。

但是,如果父进程早于子进程终结时,子进程就成了一个孤儿(orphand)进程。孤儿进程会被过继给init进程。init进程会在该子进程终结时调用wait系统调用。

一个糟糕的程序也可能造成子进程的退出信息滞留在内核中的情况(父进程不对子进程调用wait函数,内核中滞留task_struct结构体),这样的情况下,子进程成为僵尸进程。当大量僵尸(Zombie)进程积累时,内存空间会被挤占。

时间: 2024-10-06 09:47:22

linux系统知识 - 进程&线程的相关文章

Linux系统开发9 线程同步

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> Linux系统编程8 线程同步 多线程共享资源,不加锁,同步互斥演示 多线程共享资源,加锁,同步互斥演示 读写锁:3个写线程,5个读线程,不加锁,并行处理 读写锁:3个写线程,5个读线程,加读写锁,串行处理 条件变量:生产消费者模型 信号量 进程间锁 文件锁: 习题 死锁,哲学家就餐 多线程共享资源,不加锁,同步互斥演示 [email protected]:~/linux_c/thread$ ca

Linux系统开发8 线程

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> Linux系统开发8 线程 线程概念 浏览器 火狐多线程,谷歌多进程比较: 查看某一个进程有哪些线程 线程间共享资源 线程间非共享资源 线程优缺点 安装完整的manpage文档 pthread_create()创建线程 pthread_self() 获取线程自己的ID 线程创建程序演示: 指定libpthread.so库编译链接 演示:进程结束,线程也会立即结束 pthread_exit() 调用

Linux系统中进程的创建

1.Linux中的进程 进程是程序执行的一个实例,也是系统资源调度的最小单位.如果同一个程序被多个用户同时运行,那么这个程序就有多个相对独立的进程,与此同时他们又共享相同的执行代码,在Linux系统中进程的概念类似于任务或者线程(task & threads). 进程是一个程序运行时候的一个实例实际上说的是它就是一个可以充分描述程序以达到了其可以运行状态的的一个数据和代码集合.一个进程会被产生并会复制出自己的子代,类似细胞分裂一样.从系统的角度来看进程的任务实际上就是担当承载系统资源的单位,系统

Linux系统编程@进程通信(一)

进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统的一个分支) POSIX进程间通信(POSIX:可移植操作系统接口,为了提高UNIX环境下应用程序的可移植性.很多其他系统也支持POSIX标准(如:DEC OpenVMS和Windows).) 现在Linux使用的进程间通信方式包括: 管道(pipe).有名管道(FIFO) 信号(signal) 消

asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二)

原文:asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二) 续上一篇文章:asp.net core2.0 部署centos7/linux系统 --安装部署(一),遗留的问题而来,对程序添加守护进程,使网站可以持续化的运行起来. ? 1.介绍supervisor ?? ?Supervisor(http://supervisord.org/)是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,

linux系统编程:线程原语

线程原语 线程概念 线程(thread),有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成.更多详细解释看百度百科:线程. 在Linux shell下通过命令 $ ps -Lf pid 查看指定pid号下的所有线程. 线程之间的共享与非共享 这里的线程是指同一进程下的线程. 共享: 1.文件描述符表   2.每种信号的处理方式   3.当前工作目录   4.用户ID和组ID   5

获得Gnu/Linux系统与进程信息

这里主要介绍/proc伪文件系统及uname()函数来获取系统或进程的一些信息. /proc文件系统介绍 在早期的UNIX发行版中,并不能很容易的分析内核的一些属性,并且很难回答以下问题: 系统有多少进程正在运行,并且谁拥有这些进程? 一个进程都打开了哪些文件? 哪些文件目前被锁住了,并且哪些进程拥有这些文件锁? 系统有哪些套接字正在使用? 一些早期的UNIX发行版解决该问题是允许有权限的程序进入内核的内存空间的数据结构中.这样的解决办法有诸多不便.然而,最蛋疼的是它需要程序员了解的内核中的数据

Linux系统编程——进程管理

引言: 在Linux的内核的五大组成模块中,进程管理模块时非常重要的一部分,它虽然不像内存管理.虚拟文件系统等模块那样复杂,也不像进程间通信模块那样条理化,但作为五大内核模块之一,进程管理对我们理解内核的运作.对于我们以后的编程非常重要.同时,作为五大组成模块中的核心模块,它与其他四个模块都有联系.下面就对进程模块进行想写的介绍,首先要了解进程及其相关的概念.其次介绍进程的创建.切换.撤销等基本操作.除此之外,还给出了Linux内核是如何对进程进行调度管理的.      一.进程及其相关概念 进

嵌入式Linux系统学习嵌入式Linux系统知识大纲梳理

想要学习嵌入式知识.嵌入linux,就需要学习嵌入式linux系统基础架构知识,按照计划学习,现在就让小编带大家熟悉嵌入式Linux系统基础概念.事物总有个核心,复杂的事物总可以模块化.层次化, 嵌入式Linux也如此.学习嵌入式Linux困难,主要因为涉及知识和概念过多,所以学习嵌入式Linux的就需要找到核心,需要模块化,需要进行层次划分. 嵌入式Linux系统做模块化处理就是可划分为Bootloader(引导程序),Kernel(内核),fs(文件系统),Shell(命令行界面),Gui(