Linux任务、进程和线程

来源:互联网 发布:比价软件淘宝小密 编辑:程序博客网 时间:2024/05/16 08:03

参考书籍:《从实践中学嵌入式linux应用程序开发》(华清远见嵌入式学院)

资料下载:http://download.csdn.net/detail/klcf0220/5331230

http://www.kuaipan.cn/file/id_43409466388906159.htm

参考链接:http://www.cnblogs.com/lmjob/archive/2009/08/10/1542684.html

http://www.linuxidc.com/Linux/2011-02/32125.htm

形象化解释:http://blog.jobbole.com/38696/

 

任务(task)是最抽象的,是一个一般性的术语,指由软件完成的一个活动。一个任务既可以是一个进程,也可以是一个线程。简而言之,它指的是一系列共同达到某一目的的操作。例如,读取数据并将数据放入内存中。这个任务可以作为一个进程来实现,也可以作为一个线程(或作为一个中断任务)来实现。

进程(process)常常被定义为程序的执行。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单 位。可以把一个进程看成是一个独立的程序,在内存中有其完备的数据空间和代码空间。一个进程所拥有的数据和变量只属于它自己。

线程(tread)则是某一进程中一路单独运行的程序。也就是说,线程存在于进程之中。一个进程由一个或多个线程构成,各线程共享相同的代码和全局数据,但各有其自己的堆栈。由于堆栈是每个线程一个,所以局部变量对每一线程来说是私有的。由于所有线程共享同样的代码和全局数据,它们比进程更紧密,比单独的进程间更趋向于相互作用,线程间的相互作用更容易些,因为它们本身就有某些供通信用的共享内存:进程的全局数据。

一个进程和一个线程最显著的区别是:线程有自己的全局数据。线程存在于进程中,因此一个进程的全局变量由所有的线程共享。由于线程共享同样的系统区域,操作系统分配给一个进程的资源对该进程的所有线程都是可用的,正如全局数据可供所有线程使用一样

进程是表示资源分配的基本单位,线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。

进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

 

Linux进程:

实际上,当计算机开机的时候,内核(kernel)只建立了一个init进程。Linux kernel并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立。新的进程要通过老的进程复制自身得到,这就是fork。fork是一个系统调用。进程存活于内存中,每个进程都在内存中分配有属于自己的一片空间 。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。

1)Linux操作系统中,每个进程都是通过唯一的进程ID标识的。进程ID 是一个非负数。每个进程除了进程ID外还有一些其它信息,都可以通过相应的函数获得。

2)一个进程除了有一个PID之外,还会有一个PPID(parent PID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。所以说,所有的进程也构成一个以init为根的树状结构。

3)主要的函数有:

pid_t getpid(void) :获得进程ID
pid_t getppid(void) :获得进程父进程的ID
pid_t getuid(void) :获得进程的实际用户ID
pid_t geteuid(void) :获得进程的有效用户ID
pid_t getgid(void) : 获得进程的实际组ID
pid_t getegid(void) : 获得进程的有效组ID

 

fork()函数:

fork通常作为一个函数被调用。调用fork函数后,当前进程分裂为两个进程,一个是原来的父进程,另一个是刚创建的子进程。父进程调用fork后返回值是子进程的ID,子进程中返回值是0,若进程创建失败,只返回-1。失败原因一般是父进程拥有的子进程个数超过了规定限制(返回EAGAIN)或者内存不足(返回ENOMEM)。我们可以依据返回值判断进程,一般情况下调用fork函数后父子进程谁先执行是未定的,取决于内核所使用的调度算法。一般情况下os让所有进程享有同等执行权,除非某些进程优先级高。若有一个孤儿进程,即父进程先于子进程死去,子进程将会由init进程收养。

/*fork.c*/
#include<sys/types.h>#include<unistd.h>#include<stdio.h>int main(){
    printf(“123\n”):
    pid_t pid;
    pid
= fork();

   
if(pid < 0){
        printf(
"error in fork!\n");
    }
   
else if(0 == pid){
        printf(
"i am the child process,curpPID is %d,parentPID is %d\n",pid,getpid());
    }
   
else{
        printf(
"i am the parent process,childPID is %d,parentPID is %d\n",pid,getpid());
    }
    printf(“abc\n”);
}
编译后分别运行./fork 和 ./fork > test.txt 会得出不一样的答案。
 

fork()和vfork()函数之间的区别:

(1)fork():使用fork()创建一个子进程时,子进程只是完全复制父进程的资源。这样得到的子进程独立于父进程具有良好的并发性。
vfork(): 使用 vfor创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程。而是子进程共享父进程的地址空间,即子进程完全运行在父进程的地址空间上。子进程对该地址空间中任何数据的修改同样为父进程所见。

(2)fork():父子进程执行顺序不定;
vfork():保证子进程先运行,在调用exec或exit之前与父进程共享数据,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

孤儿进程:如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由 init 进程收养,成为 init 进程的子进程。

守护进程:(daemon) 是指在后台运行,没有控制终端与之相连的进程。它独立于控制终端,通常周期性地执行某种任务 。

/*daemon.c 创建守护进程实例*/#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<string.h>#include<sys/types.h>#include<unistd.h>#include<sys/wait.h>int main(){    pid_t pid;    int i,fd;    char *buf = "this is a daemon\n";    pid = fork();    if(pid < 0){        printf("error fork\n");        exit(0);    }    if(pid > 0){        exit(0);    }    setsid();//创建一个新的会话组,并担任该会话组的组长    chdir("/");//改变当前目录为根目录    umask(0);//重设文件权限掩码    for(i = 0;i < getdtablesize();i++){        close(i);//关闭文件描述符    }    //这时创建完守护进程,一下开始正式进入守护进程工作    while(1){        if((fd = open("/tmp/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0){            printf("open file error\n");            exit(0);        }        write(fd,buf,strlen(buf)+1);        close(fd);        sleep(10);    }    exit(0);}
tail –f /tmp/daemon.log
ps –ef|grep daemon

 

进程退出: exit 和 _exit 函数

(1)正常退出

a. 在main()函数中执行return 。renturn执行完后把控制权交给调用函数。

b.调用exit()函数,exit执行完后把控制权交给系统。

c.调用_exit()函数。

exit()和_exit()的区别:
a._exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。
b. 调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr  ...).   exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。

image

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){         int result;         result = fork();        if(result == -1)        {                perror("fork fail!\n");                exit(0);        }        else if(result == 0)        {                printf("test _exit()\n");                printf("this is the content1 in buffer");                _exit(0);        }        else{                printf("testing exit()\n");                printf("this is the content2 in buffer");                exit(0);    }}
运行结果:
image

(2)异常退出

a.调用abort函数

b.进程收到某个信号,而该信号使程序终止。

不管是那种退出方式,系统最终都会执行内核中的同一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。

 

wait()和waitpid():

1)wait()函数使父进程暫停执行,直到它的一个子进程结束为止,该函数的返回值是终止运行的子进程的PID. 参数status所指向 的变量存放子进程的退出码,即从子进程的main函数返回的值或子进程中exit()函数的参数。如果status不是一个空指针,状态信息将被写入它指向的变量。

2)waitpid()也用来等待子进程的结束,但它用于等待某个特定进程结束。参数pid指明要等待的子进程的PID,参数 status的含义与wait()函数中的 status相同。options参数可以用来改变waitpid的行为,若将该参数赋值为WNOHANG,则使父进程不被挂起而立即返回执行其后的代码。

 

exec()函数族:

image

exec()函数族的具体实现:
1>.其中只有 execve 是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
2>.exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容 ,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
3>.在使用exec函数族时,一定要加上错误判断语句,因为exec很容易支行失败。其中最常见的原因有:
a、找不到文件或路径,此时error被设置为ENOENT;
b、数组argv和envp忘记用NULL结束,此时error被设置为EFAUL;
c、没有对应的可执行文件的运行权限,此时errno被设置为EACCES。
4>.当进程调用一种exec()函数时,该进程执行的程序完全替换为新程序,而新程序则从main函数开始执行 。调用exec()并不创建新进程,所以前后的进程ID不变。函数exec()只是用一个全新的程序替换当前进程的正文、数据、堆和栈段。
5>.无论是哪个exec()函数,都是将可执行程序的路径,命令行参数和环境变量3个参数传递给可执行程序的main()函数。
6>.具体介绍exec()函数族是如何main()函数需要的参数传递个它的。
a、execv()函数:execv()函数是通过路径名方式调用可执行文件作为新的进程映像。它的argv参数用来提供给main()函数的argv参数。argv参数是一个以NULL结尾的字符串数组 。
b、execve()函数:参数pathname时将要执行的程序的路径名,参数argv,envp 与main()函数的argv,envp对应 。
c、execl()函数:此函数与execv函数用法类似。只是在传递argv 参数的时候,每个命令行参数都声明为一个单独的参数(参数中使用“......"说明参数的个数是不确定的),要注意的是这些参数要以一个空指针作为结束。
d、execle()函数:该函数与execl函数用法类似,只是要显示指定环境变量。环境变量位于命令行参数最后一个参数的后面,也就是位于空指针之后。
e、execvp函数:该函数和execv函数用法类似,不同的是参数filename。该参数 如果包含/,则将其视为路径名,否则就按PATH环境变量搜索可执行文件。
f、execlp()函数:该函数于execl函数类似,它们的区别和execvp与execv的区别一样。

/*函数实例*//*exec.c*/#include<stdio.h>#include<sys/types.h>#include<unistd.h>#include<stdlib.h>int main(int argc,char *argv[],char ** environ){         pid_t pid;         int status;         printf("Exec example!\n");         pid = fork();         if(pid < 0){                 perror("Process creation failed\n");                 exit(1);         }         else if(0 == pid){                 printf("child process is running\n");                 printf("My pid = %d ,parentpid = %d\n",getpid(),getppid());                 printf("uid = %d,gid = %d\n",getuid(),getgid());                 execve("processimage",argv,environ);                 printf("process never go to here!\n");                 exit(0);         }         else {                 printf("Parent process is runnig\n");         }         wait(&status);         exit(0);}
/*processimage.c*/#include<stdio.h>#include<sys/types.h>#include<unistd.h>int main(int argc,char * argv[],char ** environ){        int i;        printf("I am a process image!\n");        printf("My pid =%d,parentpid = %d\n",getpid(),getppid());        printf("uid = %d,gid = %d\n",getuid(),getgid());        for(i = 0;i<argc;i++){                printf("argv[%d]:%s\n",i,argv[i]);
        }
}
gcc –o processimage processimage.c
gcc –o exec exec.c
./exec

 

system函数:

#include <stdlib.h>
int system(const char *command);

        system()函数调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell,比如bash,-c选项是告诉shell从字符串command中读取命令;
详细的了解可以参考:http://my.oschina.net/renhc/blog/53580

/*system.c*/
#include<stdio.h> #include<stdlib.h>int main(){         int result;         result = system("ls -l");}
运行结果是列出当前目录下的所有文件。在不同的目录下运行程序会有不同的结果。

weisha


<script type="text/javascript"><!--google_ad_client = "ca-pub-1944176156128447";/* cnblogs 首页横幅 */google_ad_slot = "5419468456";google_ad_width = 728;google_ad_height = 90;//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>