linux fork函数,linux rm -rf
各位老铁们好,相信很多人对linux fork函数都不是特别的了解,因此呢,今天就来为大家分享下关于linux fork函数以及linux rm -rf的问题知识,还望可以帮助大家,解决大家的一些困惑,下面一起来看看吧!
Linux下fork,vfork,clone和exec的区别
前三个和最后一个是两个类型。前三个主要是Linux用来创建新的进程(线程)而设计的,exec()系列函数则是用来用指定的程序替换当前进程的所有内容。所以exec()系列函数经常在前三个函数使用之后调用,来创建一个全新的程序运行环境。Linux用init进程启动其他进程的过程一般都是这样的。
下面说fork、vfork和clone三个函数。这三个函数分别调用了sys_fork、sys_vfork、sys_clone,最终都调用了do_fork函数,差别在于参数的传递和一些基本的准备工作不同。可见这三者最终达到的最本质的目的都是创建一个新的进程。在这里需要明确一下,Linux内核中没有独立的“线程”结构,Linux的线程就是轻量级进程,换言之基本控制结构和Linux的进程是一样的(都是通过struct task_struct管理)。
fork是最简单的调用,不需要任何参数,仅仅是在创建一个子进程并为其创建一个独立于父进程的空间。fork使用COW(写时拷贝)机制,并且COW了父进程的栈空间。
vfork是一个过时的应用,vfork也是创建一个子进程,但是子进程共享父进程的空间。在vfork创建子进程之后,父进程阻塞,直到子进程执行了exec()或者exit()。vfork最初是因为fork没有实现COW机制,而很多情况下fork之后会紧接着exec,而exec的执行相当于之前fork复制的空间全部变成了无用功,所以设计了vfork。而现在fork使用了COW机制,唯一的代价仅仅是复制父进程页表的代价,所以vfork不应该出现在新的代码之中。在Linux的manpage中队vfork有这样一段话:It is rather unfortunate that Linux revived this specter from the past. The BSD man page states:"This system call will be eliminated when proper system sharing mechanisms are implemented. Users should not depend on the memory sharing semantics of vfork() as it will, in that case, be made synonymous to fork(2)."
clone是Linux为创建线程设计的(虽然也可以用clone创建进程)。所以可以说clone是fork的升级版本,不仅可以创建进程或者线程,还可以指定创建新的命名空间(namespace)、有选择的继承父进程的内存、甚至可以将创建出来的进程变成父进程的兄弟进程等等。clone和fork的调用方式也很不相同,clone调用需要传入一个函数,该函数在子进程中执行。此外,clone和fork最大不同在于clone不再复制父进程的栈空间,而是自己创建一个新的。
关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html
Linux启动新进程的几种方法及比较
有时候,我们需要在自己的程序(进程)中启动另一个程序(进程)来帮助我们完成一些工作,那么我们需要怎么才能在自己的进程中启动其他的进程呢?在Linux中提供了不少的方法来实现这一点,下面就来介绍一个这些方法及它们之间的区别。一、system函数调用 system函数的原型为:#include int system(const char*string);它的作用是,运行以字符串参数的形式传递给它的命令并等待该命令的完成。命令的执行情况就如同在shell中执行命令:sh-c string。如果无法启动shell来运行这个命令,system函数返回错误代码127;如果是其他错误,则返回-1。否则,system函数将返回该命令的退出码。注意:system函数调用用一个shell来启动想要执行的程序,所以可以把这个程序放到后台中执行,这里system函数调用会立即返回。可以先先下面的例子,源文件为new_ps_system.c,代码如下:#include#include int main(){ printf("Running ps with system\n");//ps进程结束后才返回,才能继续执行下面的代码 system("ps au");// 1 printf("ps Done\n"); exit(0);}该程序调用ps程序打印所有与本用户有关的进程,最后才打印ps Done。运行结果如下:如果把注释1的语句改为:system("ps au&");则system函数立即返回,不用等待ps进程结束即可执行下面的代码。所以你看到的输出,ps Done可能并不是出现在最后一行,而是在中间。一般来说,使用system函数不是启动其他进程的理想手段,因为它必须用一个shell来启动需要的程序,即在启动程序之前需要先启动一个shell,而且对shell的环境的依赖也很大,因此使用system函数的效率不高。二、替换进程映像——使用exec系列函数 exec系列函数由一组相关的函数组成,它们在进程的启动方式和程序参数的表达方式上各有不同。但是exec系列函数都有一个共同的工作方式,就是把当前进程替换为一个新进程,也就是说你可以使用exec函数将程序的执行从一个程序切换到另一个程序,在新的程序启动后,原来的程序就不再执行了,新进程由path或file参数指定。exec函数比system函数更有效。 exec系列函数的类型为:#include char**environ; int execl(const char*path, const char*arg0,...,(char*)0); int execlp(const char*file, const char*arg0,...,(char*)0); int execle(const char*path, const char*arg0,...,(char*)0, char*const envp[]); int execv(const char*path, char*const argv[]); int execvp(cosnt char*file, char*const argv[]); int execve(const char*path, char*const argv[], char*const envp[]);这类函数可以分为两大类,execl、execlp和execle的参数是可变的,以一个空指针结束,而execv、execvp和execve的第二个参数是一个字符串数组,在调用新进程时,argv作为新进程的main函数的参数。而envp可作为新进程的环境变量,传递给新的进程,从而变量它可用的环境变量。承接上一个例子,如果想用exec系统函数来启动ps进程,则这6个不同的函数的调用语句为:注:arg0为程序的名字,所以在这个例子中全为ps。 char*const ps_envp[]={"PATH=/bin:usr/bin","TERM=console", 0}; char*const ps_argv[]={"ps","au", 0}; execl("/bin/ps","ps","au", 0); execlp("ps","ps","au", 0); execle("/bin/ps","ps","au", 0, ps_envp); execv("/bin/ps", ps_argv); execvp("ps", ps_argv); execve("/bin/ps", ps_argv, ps_envp);下面我给出一个完整的例子,源文件名为new_ps_exec.c,代码如下:#include#include#include int main(){ printf("Running ps with execlp\n"); execlp("ps","ps","au",(char*)0); printf("ps Done"); exit(0);}运行结果如下:细心的话,可以发现,最后的ps Done并没有输出,这是偶然吗?并不是,因为我们并没有再一次返回到程序new_ps_exec.exe上,因为调用execlp函数时,new_ps_exec.exe进程被替换为ps进程,当ps进程结束后,整个程序就结束了,并没有回到原来的new_ps_exec.exe进程上,原本的进程new_ps_exec.exe不会再执行,所以语句printf("ps Done");根本没有机会执行。注意,一般情况下,exec函数是不会返回的,除非发生错误返回-1,由exec启动的新进程继承了原进程的许多特性,在原进程中已打开的文件描述符在新进程中仍将保持打开,但任何在原进程中已打开的目录流都将在新进程中被关闭。三、复制进程映像——fork函数1、fork函数的应用 exec调用用新的进程替换当前执行的进程,而我们也可以用fork来复制一个新的进程,新的进程几乎与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间、环境和文件描述符。 fork函数的原型为:#include#include pid_t fork();注:在父进程中,fork返回的是新的子进程的PID,子进程中的fork返回的是0,我们可以通过这一点来判断父进程和子进程,如果fork调用失败,它返回-1.继承上面的例子,下面我给出一个调用ps的例子,源文件名为new_ps_fork.c,代码如下:#include#include#include#include int main(){ pid_t pid= fork(); switch(pid){ case-1: perror("fork failed"); exit(1); break; case 0://这是在子进程中,调用execlp切换为ps进程 printf("\n"); execlp("ps","ps","au", 0); break; default://这是在父进程中,输出相关提示信息 printf("Parent, ps Done\n"); break;} exit(0);}输出结果为:我们可以看到,之前在第二点中没有出现的ps Done是打印出来了,但是顺序却有点不对,这是因为,父进程先于子程序执行,所以先输出了Parent, ps Done,那有没有办法让它在子进程输出完之后再输出,当然有,就是用wait和waitpid函数。注意,一般情况下,父进程与子进程的生命周期是没有关系的,即便父进程退出了,子进程仍然可以正常运行。 2、等待一个进程 wait函数和waitpid函数的原型为:#include#include pid_t wait(int*stat_loc); pid_t waitpid(pid_t pid, int*stat_loc, int options);wait用于在父进程中调用,让父进程暂停执行等待子进程的结束,返回子进程的PID,如果stat_loc不是空指针,状态信息将被写入stat_loc指向的位置。 waitpid等待进程id为pid的子进程的结束(pid为-1,将返回任一子进程的信息),stat_loc参数的作用与wait函数相同,options用于改变waitpid的行为,其中有一个很重要的选项WNOHANG,它的作用是防止waippid调用者的执行挂起。如果子进程没有结束或意外终止,它返回0,否则返回子进程的pid。改变后的程序保存为源文件new_ps_fork2.c,代码如下:#include#include#include#include int main(){ pid_t pid= fork(); int stat= 0; switch(pid){ case-1: perror("fork failed"); exit(1); break; case 0://这是在子进程中,调用execlp切换为ps进程 printf("\n"); execlp("ps","ps","au", 0); break; default://这是在父进程中,等待子进程结束并输出相关提示信息 pid= wait(&stat); printf("Child has finished: PID=%d\n", pid);//检查子进程的退出状态 if(WIFEXITED(stat)) printf("Child exited with code%d\n", WEXITSTATUS(stat)); else printf("Child terminated abnormally\n"); printf("Parent, ps Done\n"); break;} exit(0);}输出为:可以看到这次的输出终于正常了,Parent的输出也在子进程的输出之后。总结——三种启动新进程方法的比较首先是最简单的system函数,它需要启动新的shell并在新的shell是执行子进程,所以对环境的依赖较大,而且效率也不高。同时system函数要等待子进程的返回才能执行下面的语句。 exec系统函数是用新的进程来替换原先的进程,效率较高,但是它不会返回到原先的进程,也就是说在exec函数后面的所以代码都不会被执行,除非exec调用失败。然而exec启动的新进程继承了原进程的许多特性,在原进程中已打开的文件描述符在新进程中仍将保持打开,但需要注意,任何在原进程中已打开的目录流都将在新进程中被关闭。 fork则是用当前的进程来复制出一个新的进程,新进程与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间、环境变量和文件描述符,我们通常根据fork函数的返回值来确定当前的进程是子进程还是父进程,即它并不像exec那样并不返回,而是返回一个pid_t的值用于判断,我们还可以继续执行fork后面的代码。
Linux进程和线程的基础与管理
一.进程的基本概念
程序是为了完成某种任务而设计的软件,比如vi是程序。什么是进程呢?进程就是运行中的程序。一个运行着程序,可能有多个进程。比如Web服务器是Apache服务器,当管理员启动服务后,可能会有好多人来访问,也就是说许多用户同时请求httpd,Apache服务器将会创建多个httpd进程来对其进行服务。
首先我们看看进程的定义。进程是一个具有独立功能的程序关于某个数据集合的一次可以并发执行的运行活动,是处于活动状态的计算机程序。进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本实体。了解进程的本质,对于理解、描述和设计操作系统有着极为重要的意义。了解进程的活动、状态,也有利于编制复杂程序。
二.进程的属性
进程的定义:一个进程是一个程序的一次执行的过程;程序是静态的,它是一些保存在磁盘上的可执行的代码和数据集合;进程是一个动态的概念,它是Linux系统的基本的调度单位。
一个进程由如下元素组成:
程序读取的上下文,它表示程序读取执行的状态。程序当前执行的目录。程序服务的文件和目录。程序访问的权限。内存和其他分配给进程的系统资源。 Linux进程中最知名的属性就是它的进程号(Process Idenity Number,PID)和它的父进程号(Parent Process ID,PPID)。PID、PPID都是非零正整数。一个PID唯一地标识一个进程。一个进程创建新进程称为创建了子进程(Child Process)。相反地,创建子进程的进程称为父进程。所有进程追溯其祖先最终都会落到进号为1的进程身上,这个进程叫做init进程,是内核自举后第一个启动的进程。init进程扮演终结父进程的角色。因为init进程永远不会终止,所以系统总是可以确信它的存在,并在必要的时候以它为参照。如果某个进程它在衍生出来的全部子进程结束之前被终止,就会出现必须以init为参照的情况。此时那些失去了父进程的子进程就都会以init作为它们的父进程。如果执行一下ps-af命令,可以列出许多父进程ID为1的进程来。Linux提供了一条pstree命令,允许用户查看系统内正在运行的各个进程之间的继承关系。直接在命令行中输入pstree即可,程序会以树状结构方式列出系统中正在运行的各进程之间的继承关系。
三.理解Linux下进程的结构
Linux中一个进程在内存里有三部分数据,就是“数据段”、“堆栈段”、“代码段”。基于I386兼容的中央处理器,都有上述三种段寄存器,以方便操作系统的运行,如下图所示。
代码段
数据段
堆栈段
代码段是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段。而数据段则存放程序的全局变量、常数及动态数据分配的数据空间。堆栈段存放的就是子进程的返回地址、子程序的参数及程序的局部变量。堆栈段包含在进程控制块PCB(Process Control Block)中。PCB处于进程核心堆栈的底部,不需要额外分配空间。
四.进程状态
现在我们来看看,进程在生存周期中的各种状态及状态的转换。下面是Linux系统的进程状态模型的各种状态。
用户状态:进程在用户状态下运行的状态。内核状态:进程在内核状态下运行的状态。内存中就绪:进程没有执行,但处于就绪状态,只要内核调度它,就可以执行。内存中睡眠:进程正在睡眠并且进程存储在内存中,没有被交换到SWAP设备。就绪且换出:进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它运行。睡眠且换出:进程正在睡眠,且被换出内存。被抢先:进程从内核状态返回用户状态时,内核抢先于它做了上下文切换,调度了另一个进程。原先这个进程就处于被抢先状态。僵死状态(zombie):进程调用exit结束,进程不再存在,但在进程表项中仍有记录,该记录可由父进程收集。现在我们从进程的创建到退出来看看进程的状态转化。需要说明的是,进程在它的生命周期里并不一定要经历所有状态。
五.Linux进程的创建
fork函数在Linux下产生新的进程的系统调用,这个函数名是英文中“分叉”的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了,所以这个名字取得很形象。fork的语法如下所示:
复制代码
代码如下:
#include unistd.h
#include sys/types.h
pid_t fork();
在Linux网络编程中经常用到fork()系统调用。例如在一个客户机/Web服务器构建的网络环境中,Web服务器往往可以满足许多客户端的请求。如果一个客户机要访问Web服务器,需要发送一个请求,此时由服务器生成一个父进程,然后父进程通过fork()系统调用产生一个子进程,此时客户机的请求由子进程完成。父进程可以再度回到等待状态不断服务其他客户端。原理如下图所示。
有一个更简单的执行其他程序的函数system,参数string传递给一个命令解释器(一般为sh)执行,即string被解释为一条命令,由sh执行该命令。若参数string为一个空指针,则检查命令解释器是否存在。该命令可以和同命令行下的命令形式相同,但由于命令作为一个参数放在系统调用中,应注意编译时对特殊意义字符的处理。命令的查找是按PATH环境变量的定义执行的。命令所生成的后果一般不会对父进程编程造成影响。返回值:当参数为空指针时,只有当命令解释器有效时返回值为非零。若参数不为空指针,返回值为该命令的返回状态(同waitpid())的返回值。命令无效或语法错误则返回非零值,所执行的命令被终止。其他情况则返回-1.它是一个较高层的函数,实际上相当于在shell下执行一条命令,除了system之外,系统调用exec来执行一个可执行文件,来代替当前进程的执行映像。系统调用exit的功能是终止发出调用的进程。sleep函数调用用来指定进程挂起的秒数。wait函数族用来等待和控制进程。poppen函数和system函数类似,区别是它用管道方式处理输出。
父进程和子进程的关系是管理和被管理的关系,当父进程终止时,子进程也随之而终止。但子进程终止时,父进程并不一定终止。比如httpd服务器运行时,我们可以杀掉其子进程,父进程并不会因为子进程的终止而终止。
六.进程的管理
1.启动进程
输入需要运行的程序的程序名,执行一个程序,其实也就是启动了一个进程。在Linux系统中,每个进程都具有一个进程号(PID),用于系统识别和调度进程。启动一个进程有两个主要途径:手工启动和调度启动,后者是事先进行设置,根据用户要求自动启动。由用户输入命令,直接启动一个进程便是手工启动进程。但手工启动进程又可以分为很多种,根据启动的进程类型不同;性质不同,实际结果也不一样。
(1)前台启动
前台启动是手工启动一个进程的最常用的方式。用户键入一个命令“df”,就已经启动了一个进程,而且是一个前台的进程。这时候系统其实已经处于多进程状态。有许多运行在后台的、系统启动时就已经自动启动的进程正在悄悄运行着。有的用户在键入“df”命令以后赶紧使用“ps-x”查看,却没有看到df进程,会觉得很奇怪。其实这里因为df这个进程结束太快,使用ps查看时该进程已经执行结束了。如果启动一个比较耗时的进程,例如在根命令下运行:find,然后使用ps aux查看,就会看到在里面有一个find进程。
(2)后台启动
直接从后台手工启动一个进程用得比较小一些,除非是该进程甚为耗时,且用户也不急着需要结果。假设用户要启动一个需要长时间运行的格式化文本文件的进程,为了不使整个shell在格式化过程中都处于“瘫痪”状态,从后台启动这个进程是明智的选择。
2.进程调度
当需要中断一个前台进程的时候,通常使用Ctrl+C组合键。但是对于一个后台进程,就不是一个组合键所能解决的了,这时就必须使用kill命令。该命令可以终止后台进程。至于终止后台进程的原因有很多,或许是该进程占用的CPU时间过多;或许是该进程已经挂死。这种情况是经常发生的。kill命令的工作原理是:向Linux系统的内核发送一个系统操作信号和某个程序的进程标识号,然后系统内核就可以对进程标识号指定的进程进行操作。
七.Linux的第一个进程:init
init是Linux系统执行的第一个进程,进程ID为1,是系统所有进程的起点,主要用来执行一些开机初始化脚本和监视进程。Linux系统在完成核内引导以后就开始运行init程序,init程序需要读取配置文件/etc/inittab。Inittab是一个不可执行的文本文件,它由若干行命令所组成。
在RHEL 4系统中,inittab配置文件的内容如下所示:
复制代码
代码如下:
#
#inittab
#
#
#author
#
#Default runlevel.the runlevels used by rhs are:
#0- halt(do not set initdefault to this)
#1- single user mode
#2- multiuser,without nfs(the same as 3, if you do not haver networking)
#3- full multiuser mode
#4- unused
#5- X11
#6- reboot(do not set initdefault to this)
#
//表示当前缺省运行级别为5,启动系统进入图形化界面
id:5:initdefault:
//启动时自动执行/etc/rc.d/rc.sysinit脚本
#system initialization.
si::sysinit:/etc/rc.d/rc.sysinit
10:0:wait:/etc/rc.d/rc 0
11:1:wait:/etc/rc.d/rc 1
12:2:wait:/etc/rc.d/rc 2
13:3:wait:/etc/rc.d/rc 3
14:4:wait:/etc/rc.d/rc 4
//当运行级别为5时,以5为参数运行/etc/rc.d/rc脚本,init将等待其返回
15:5:wait:/etc/rc.d/rc 5
16:6:wait:/etc/rc.d/rc 6
//在启动过程中允许按[ctrl-alt-delete]重启系统
#trap ctrl-alt-delete
ca::ctrlaltdel:/sbin/shutdown-t3-r now
#
..................................
#
//在运行级别2、3、4、5以上ttyX为参数执行/sbin/mingetty程序,打开ttyX终端用于用户登录,如果进程退出则再次运行mingetty程序
#run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
//在级别5上运行xdm程序,提供xdm图形方式登录界面,并在退出时重新执行
x:5:respawn:/etc/x11/prefdm-nodaemon
#run xdm in runleverl 5
Inittab配置文件每行的基本格式如下。
id:runlevels:action:procees
其中某些部分可以为空,下面我们逐一介绍。
1.id
1~2个字符,配置行的惟一标识,在配置文件中不能重复。
2.runlevels
配置行适用的运行级别,在这里可填入多个运行级别,比如12345或者35等。
Linux有7个运行级别:
0:关机
1:单用户字符界面
2:不具备网络文件系统(NFS)功能的多用户字符界面
3:具有网络功能的多用户字符界面
4:保留不用
5:具有网络功能的图形用户界面
6:重新启动系统
3.action
init有如下几种行为,如下表所示。
init行为
行为
描述
respawn
启动并监视第4项指定的process,若process终止则重启它
wait
执行第4项指定的process,并等待它执行完备
once
执行第4项指定的process
boot
不论在哪个执行等级,系统启动时都会运行第4项指定的process
bootwait
不论在哪个执行等级,系统启动时都会运行第4项指定的process,且一直等它执行完备
off
关闭任何动作,相当于忽略该配置行
ondemand
进入ondemand执行等级时,执行第4项指定的process
initdefault
系统启动后进入的执行等级,该行不需要指定process
sysinit
不论在哪个执行等级,系统会在执行boot及bootwait之前执行第4项指定的process
powerwait
当系统的供电不足时执行第4项指定的process,且一直等它执行完备
powerfailnow
当系统的供电严重不足时执行第4项指定的process
ctrlaltdel
当用户按下ctrl+alt+del时执行的操作
kbrequest
当用户按下特殊的组合键时执行第4项指定的process,此组合键需在keymaps文件定义
4.process
Process为init执行的进程,这些进程都保存在目录/etc/rc.d/rcX中,其中的X代表运行级别,rc程序接收X参数,然后运行/etc/rc.d/rc.X下面的程序。使用如下命令可以查看/etc/rc.d目录内容。
复制代码
代码如下:
#ls–l/etc/rc.d/
total 112
drwxr-xr-x 2 root root 4096 3/15 14:44 init.d
-rxwr-xr-x 1 root root 2352 2004-3-17 rc
drwxr-xr-x 2 root root 4096 3/15 14:44 rc0.d
drwxr-xr-x 2 root root 4096 3/15 14:44 rc1.d
drwxr-xr-x 2 root root 4096 3/15 14:44 rc2.d
drwxr-xr-x 2 root root 4096 3/15 14:44 rc3.d
drwxr-xr-x 2 root root 4096 3/15 14:44 rc4.d
drwxr-xr-x 2 root root 4096 3/15 14:44 rc5.d
drwxr-xr-x 2 root root 4096 3/15 14:44 rc6.d
-rxwr-xr-x 1 root root 2200 2004-3-17 rc.local
-rxwr-xr-x 1 root root 2352 2004-3-17 rc.sysinit
…………
使用如下命令查看/etc/rc.d/rc5.d的内容。
复制代码
代码如下:
#ls–l/etc/rc.d/rc5.d
这些文件都是符号链接,以S打头的标识启动该程序,而以K打头的标识终止该程序,后面的数字标识执行顺序,越小越先执行,剩下的标识程序名。系统启动或者切换到该运行级别时会执行以S打头的程序,系统切换到该运行级别时会执行以K打头的程序。
这个目录下的程序可通过chkconfig程序进行管理,当然这个目录下的程序需要符合一定规范,如果了解shell编程,可以查看这些符号链接所指向的程序的源码。
init也是一个进程,和普通的进程具有一样的属性。比如修改了/etc/inittab,想让修改马上生效,可通过运行“kill-SIGHUP 1”来实现,也可通过运行“init q”来实现。
八.Linux的线程简介
1.Linux线程的定义
线程(thread)是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述和信号处理。在两个普通进程(非线程)间进行切换时,内核准备从一个进程的上下文切换到另一个进程的上下文要花费很大的开销。这里上下文切换的主要任务是保存老进程CPU状态并加载新进程的保存状态,用新进程的内存映像替换进程的内存映像。线程允许你的进程在几个正在运行的任务之间进行切换,而不必执行前面提到的完整的上下文。另外本文介绍的线程是针对POSIX线程的,也就是Pthread。也因为Linux对它的支持最好,相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。也可以将线程和轻量级进程(LWP)视为等同的,但其实在不同的系统/实现中有不同的解释,LWP更恰当的解释为一个虚拟CPU或内核的线程。它可以帮助用户态线程实现一些特殊的功能。Pthread是一种标准化模型,它用来把一个程序分成一组能够同时执行的任务。
2.什么场合使用Pthread,即线程
(1)在返回前阻塞的I/O任务能够使用一个线程处理I/O,同时继续执行其他处理任务。
(2)在有一个或多个任务受不确定性事件,比如网络通信的可获得性影响的场合,能够使用线程处理这些异步事件,同时继续执行正常的处理。
(3)如果某些程序功能比其他的功能更重要,可以使用线程以保证所有功能都出现,但那些时间密集型的功能具有更高的优先级。
以上三点可以归纳为:在检查程序中潜在的并行性时,也就是说在要找出能够同时执行任务时使用Pthread。上面已经介绍了,Linux进程模型提供了执行多个进程的能力,已经可以进行并行或并发编程,可是纯种能够让你对多个任务的控制程序更好、使用资源更少,因为一个单一的资源,如全局变量,可以由多个线程共享。而且,在拥有多个处理器的系统上,多线程应用会比用多个进程实现的应用执行速度更快。
3.Linux进程和线程的发展
1999年1月发布的Linux 2.2内核中,进程是通过系统调用fork创建的,新的进程是原来进程的子进程。需要说明的是,在2.2.x版本中,不存在真正意义上的线程(thread)。Linux中常用的线程Pthread实际上是通过进程来模拟的。也就是说Linux中的线程也是通过fork创建的,是“轻”进程。Linux 2.2只默认允许4096个进程/线程同时运行。高端系统同时要服务上千个用户,所以这显然是一个问题,它一度是阻碍Linux进入企业级市场的一大因素。
2001年1月发布的Linux 2.4内核消除了这个限制,并且允许在系统运行中动态调整进程数上限。因此,进程数现在只受制于物理内存的多少。在高端服务器上,即使安装了512MB内存,现在也能轻而易举地同时支持1万6千个进程。
2003年12月发布的2.6内核,进程调度经过重新编写,去掉了以前版本中效率不高的算法。以前,为了决定下一步要运行哪一个任务,进程调度程序要查看每一个准备好的任务,并且经过计算机来决定哪一个任务相对来更为重要。进程标识号(PID)的数目也从32000升到10亿。内核内部的大改变之一就是Linux的线程框架被重写,以使NPTL(Native POSIX Thread Library)可以运行于其上。对于运行负荷繁重的线程应用的Pentium Pro及更先进的处理器而言,这是一个主要的性能提升,也是企业级应用中的很多高端系统一直以来所期待的。线程框架的改变包含Linux线程空间中的许多新的概念,包括线程组、线程各自的本地存储区、POSIX风格信号,以及其他改变。改进后的多线程和内存管理技术有助于更好地运行大型多媒体应用软件。
4.总结
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在对称处理器的计算机上运行,而进程则可以跨机器迁移。另外,进程可以拥有资源,线程共享进程拥有的资源。进程间的切换必须保存在进程控制块PCB(Process Control Block)中。同一个进程的多个线程间的切换不用那么麻烦。最后一个实例来作为本文的结束:当你在一台Linux PC上打开两个OICQ,每一个OICQ是一个进程;而当你在一个OICQ上和多人聊天时,每一个聊天窗口就是一个线程。
OK,本文到此结束,希望对大家有所帮助。