linux系统知识 - 进程&线程

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

参考连接

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

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

背景知识

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

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

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

进程建立

计算机开机时,内核只建立了一个init进程。剩下的全部进程都是init进程经过fork机制(新进程经过老进程复制自身获得)创建的。函数

进程存活于内存中,每一个进程在内存中都有本身的一片空间spa

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)进程积累时,内存空间会被挤占。

相关文章
相关标签/搜索