进程
1.基本描述
- 进程是处于执行期的程序以及相关的资源总称。
- 相关的资源:打开的文件,挂起的信号,内核内部数据,处理器的状态,一个或是多个具有内存映射的内存地址空间及一个或多个执行线程,存放全局变量的数据段等
- 内核调度的对象是线程,资源分配的对象是进程
- 每个线程都拥有独立的程序计数器、进程栈和一组进程寄存器。
- 进程提供两种虚拟机制:虚拟处理器和虚拟内存,让每个进程都自己觉得好像是独占处理器,独占内存一样。
- 内核把进程的列表存放在叫做任务队列(task list)的双向循环链表中,链表每一项都是task_struct(进程描述符)的结构,大约1.7KB。包含{打开的文件,进程的地址空间,挂起的信号,进程的状态}
- 分配进程描述符:linux是通过slab机制分配task_struct的,可实现对象复用和缓存着色(cache coloring)
- 如图,利用slab分配器动态生成task_struct,在栈底创建一个新的数据结构struct thread_info,每个任务的thread_info结构在它的内核栈的尾部分配,结构中task域中存放的是指向该任务实际task_struct的指针。
- 内核通过一个唯一的进程标识值(process identification value)或是PID表示每个进程
- PID的最大默认值为32768(short int),可以通过修改/proc/sys/kernel/pid_max来提高上限。
- 如何通过current宏查找当前正在运行进程的进程描述符:1)对于寄存器充足的体系结构,有一个专门的寄存器存放当前进程的task_struct的指针。2)x86体系在内核栈的尾部创建thread_info结构,通过计算偏移间接地查找task_struct结构。
2.状态转移
process.jpglinux上进程有5种状态:
- 运行(正在运行或在运行队列中等待)
- 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)
- 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)
- 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)
- 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)
ps工具标识进程的5种状态码:
- D 不可中断 uninterruptible sleep (usually IO)
- R 运行 runnable (on run queue)
- S 中断 sleeping
- T 停止 traced or stopped
- Z 僵死 a defunct (”zombie”) process
3.进程树(pstree)
- 再次注意进程在任务列表中,是以循环链表连接的。
- 进程树的树根是0号进程idle,是所有进程的祖先。
- 0号进程创建1号进程(创建时是内核线程),1号进程负责内核部分的初始化工作及系统配置,创建高速缓存和虚拟内存管理的内核线程
- 1号进程调⽤execve()运⾏磁盘上的init可执⾏程序,并演变为⽤户态1号进程,init进程
- init进程按照配置⽂件/etc/initab的要求,完成系统启动⼯作,创建编号为1号,2号,……的Getty进程(初始化终端)
- getty进程监控到终端连接信号时,通过调用execve()执行login登录程序
- 如果登录成功过,login程序通过execve()函数调用shell
- Shell进程接收getty进程的pid,取代原来的getty进程。
- 0号进程idle
- idle进程只能从静态地填写thread_inf o和task_struct
- idle进程让CPU陷入空闲循环,空闲运行
- 多处理器上刚启动只有一个CPU能运行,只有CPU0上的idle进程完成初始化后才激活其它CPU,并通过copy_process()创建其他CPU的idle进程
4.进程的创建与撤销
- 在Linux系统中,系统通过fork()函数复制一个现有的进程创建一个新进程。接着,调用exec()函数创建新进程的地址空间,并把新程序载入其中。最终,程序通过调用exit()系统调用退出运行。进程退出后被设置为僵死状态,直到父进程检查之后调用wait(),才删除进程描述符资源。
- frok,vfork,clone函数(重点)
Linux中fork,vfork和clone详解(区别与联系)
- fork:fork创建的子进程是父进程的完整副本,==复制==父进程的全部资源,包括内存的task_struct内容。复制父进程的页表(虚拟地址相同)
- vfork:vfork创建的子进程与父进程==共享==所有的内存,而且由vfork创建的子进程先于父进程运行。(exit()退出),子进程在vfork()返回后直接运行在父进程的栈空间,并使用父进程的内存和数据。这意味着子进程可能破坏父进程的数据结构或栈,造成失败。
- clone:linux提供的创建线程的系统调用。可以传入很多参数,选择性的继承父进程的资源(clone_vm//clone_vfork),不再复制父进程的栈空间,而是自己创建一个新的。(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值)
- copy-on-write:
- 底层do_fork函数的实现
- 各种进程:
- 前台进程:它会独占命令行窗口,只有运行完了或者手动中止,才能执行其他命令。
- 后台进程:&
1.继承当前 session(对话)的标准输出(stdout)和标准错误(stderr)。因此,后台任务的所有输出依然会同步地在命令行下显示。
2.不再继承当前 session的标准输入(stdin)。你无法向这个任务输入指令了。如果它试图读取标准输入,就会暂停执行(halt)。
- 守护进程:特殊的后台进程,运行在后台,不受任何终端控制(syslogd、login、crond、at系统守护进程)
1.创建子进程,终止父进程
2.在子进程中创建新会话
3.改变工作目录
4.重设文件创建的掩码
5.关闭当前文件描述符
- 孤儿进程:父进程先于子进程结束,子进程没了父亲,会交付给init进程,处理回收工作。
- 僵尸进程:子进程结束,并未被父进程调用wait函数回收,
进程与线程
- 区别:
- 进程是资源分配的基本单位,线程是调度的基本单位。
- 实体间(进程间,线程间,进线程间)通信方式的不同:
进程间通信:共享内存、消息队列、信号量、有名管道、匿名管道、socket、信号、文件。
线程间通信:线程间的通信方式可沿用上述进程间的方式,且还有独特的几种方式:互斥量、自旋锁、条件变量、读写锁、线程信号、全局变量。
- 进程有⽗⼦关系,线程只有⼀个⽗线程,其他都为⼦线程
- 多线程优势:
- ==空间的花费==:它是一种非常"节俭"的多任务操作方式,在Linux系统下,启动一个新的进程必须分配给它独自的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
- ==线程间方便的通信机制==。对不同进程来说,它们具有独自的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于****同一进程下的线程之间共享数据空间****,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
- 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数时,不同的线程运行于不同的CPU上。
网友评论