设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

Linux的进程

2012-2-3 14:35| 发布者: 红黑魂| 查看: 3243| 评论: 0|来自: csdn

摘要: 1. Linux中的进程和线程·进程进程是出于执行期的程序以及它所包含的资源的总称。它有4个要素:一段可执行的程序;进程专用的系统堆栈空间;内核中有一个task_struct数据结构;有独立的存储空间,意味着拥专有的用户 ...

1.  Linux中的进程和线程

·进程

进程是出于执行期的程序以及它所包含的资源的总称。它有4个要素:

一段可执行的程序;

进程专用的系统堆栈空间;

内核中有一个task_struct数据结构;

有独立的存储空间,意味着拥专有的用户空间;

·线程

如果进程缺少第4个条件,则称为线程。

完全没有用户空间,称为内核线程;如果共享用户空间,称为用户线程。

对Linux来说,线程只是一种进程间共享资源的手段。

注意:此处的线程 与 系统中在用户空间的同一内实现的线程 的区别?(4要素)

Linux系统中,进程在诞生之初都与父进程共用一个存储空间,严格说此时还是线程;当其建立自己的存储空间,并与父进程分道扬镳后,成为真正意义上的进程。

Linux系统中进程process和任务task是同一个意思。

2. 进程描述符 process descriptor

在<linux/sched.h>中定义的task_struct。

Linux通过slab分配器分配task_struct,所以只需在系统堆栈栈底创建一个新结构thread_info,该结构中的task域存放指向该任务的task_struct指针。(参见:http://feizf.blogbus.com/logs/16835565.html)

内核通过PID来标识每个进程,PID存放在各自的进程描述符中。

内核中通过current宏查找当前正在运行进程描述符,该宏的实现与硬件体系结构相关。

3.  进程状态

每个进程必然处在5种状态之一,如下图所示:

task_running表示一个进程正在执行;

task_interruptible和task_uninterruptible均表示进程出于睡眠状态。(这里的中断是指睡眠能否因其它事件而中断)

uninterruptible表示出于深度睡眠而不受信号的打扰。sleep_on( )和wake_up( )用于深度睡眠,interruptible_sleep_on( )和interruptible_wake_up( );

task_zombie(僵死)表示进程已经结束,只是父进程还没有调用wait4( )系统调用;

task_stopped表示进程停止执行,主要用于调试;

4.  进程上下文的理解

程序在用户空间执行时,当调用了系统调用或者触发异常,就会陷入内核空间。此时称内核“代表进程执行”并处于进程上下文中。所以此时current宏是有效的。

与中断上下文的区别:中断时,系统不代表进程执行,而是执行一个中断处理程序。

5. 进程间的关联方式

1)  进程家族树

所有进程都是PID为1的init进程的后代。

2)  Hash表为基础的进程队列的阵列

对pid进行hash计算,以计算结果为下标在hash表中找到一个队列。

3)   线性队列;

Init_task为头结点,后面创建的进程通过其task_struct结构中的next_task和prev_task两个指针链入这个线性队列。

这3个队列都是静态的,每个进程必同时处于3个队列之中。

在运行的过程中,进程可以动态的链接进可执行队列接受系统的调度。

6. 进程创建

Linux通过fork()和exec()两个函数完成进程的创建。

fork()通过拷贝当前进程创建一个子进程。子进程有自己 的task_struck结构和系统空间堆栈,但与父进程共享其它所有资源。fork()使用写时拷贝copy-on-write页实现。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。

exec()负责读取可执行文件并将其载入地址空间开始运行。

创建子进程后,父进程有3个选择:
1)  继续走自己的路。只是如果子进程先于父进程终止,则由内核发给父进程一个信号;
2) 进入睡眠状态,等待子进程结束,然后再继续运行。Linux为此提供了2个系统调用wait4()和wait3()。
3) 父进程通过系统调用exit()自己结束;
从本质上说是两种选择:一种是父进程不受阻non_blocking的方式,也称异步方式;另一种是父进程受阻blocking方式,也称同步方式。

演示进程生命周期的简单程序:
#include<stdio.h>
int main()
{
    int child;
    char *args[]={"/bin/echo","hello","world",NULL};

    if(!child=fork())  //创建子进程
    {
        //child
        printf("pid %d: %d is my father\n", getpid(), getppid());
        execve("/bin/echo",args, NULL);
        printf("pid %d: I am back,something is wrong!\n", getpid());
    }
    else
    {
        int myself=getpid();
        printf("pid %d: %d is my son\n", myself, child);
        wait4(child, NULL, 0, NULL);
        printf("pid %d: done\n", myself);
    }
    return 0;
}
进入main的进程为父进程,它执行了系统调用fork创建一个子进程。
子进程与父进程有相同返回地址,所以两者从内核空间返回到同一点上,从这一点开始有两个进程子执行。但是两者从fork返回值不一样,子进程返回0,父进程返回子进程的pid,所以if(!child=fork())语句将两者分开。
子进程通过execve执行/bin/echo,最后通过exit结束(exit是每个可执行程序映像必有的,虽然程序中没有,但gcc在编译时会自动加上),而不会执行下面的printf语句。
父进程执行wait4,停下来等待。

6. 进程终结

当一个进程终结时,内核会释放它所占有的资源,并把这一消息告知父进程。
1) 进程的析构发生在它调用exit之后,既可以是显式调用,也可能是隐式的从某个程序的主函数返回(gcc编译链接时会中mian函数返回点后面放置调用exit的代码);
2)当进程接受到它既不能处理也不能乎略的信号或异常时,它可能被动的终结;
进程终结的任务由do_exit来完成。调用do_exit之后,线程处于僵死状态,只占用内核栈、thread_info结构、task_struct结构,这样作的唯一目的就是向它的父进程提供信息。所以进程结束时的清理工作和进程描述符的删除被分开执行。

如果父进程先于子进程退出,必须有机制保证子进程能找到一个新的父进程,否则进程就会在退出时永远处于僵死状态,白白耗费内存。do_exit中会调用notify_parent,该函时通过forget_original_parent来执行寻父过程。


参考书籍:《Linux内核设计与实现》、《Linux内核源代码分析》


酷毙
1

雷人

鲜花

鸡蛋

漂亮

刚表态过的朋友 (1 人)

  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部