📄 多进程1.txt
字号:
首页 | 博客群 | 公社 | 专栏 | 论坛 | 图片 | 资讯 | 注册 | 帮助 | 博客联播 | 随机访问 毛主席语录Linux下的多进程编程2- -| 回首页 | 2006年索引 | - -Linux上安装GCC编译器过程Linux下的多进程编程1
Linux下的多进程编程
本文出自: 作者:俞磊 (2001-08-21 15:00:00)
(一) 理解Linux下进程的结构
Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,其实学过汇编
语言的人一定知道,一般的CPU象I386,都有上述三种段寄存器,以方便操作系统的运行。“代码段”,顾名
思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一
个代码段。
堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局
变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,
这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据
段。
(二) 如何使用fork
在Linux下产生新的进程的系统调用就是fork函数,这个函数名是英文中“分叉”的意思。为什么取这个
名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了,所以这
个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:
void main(){
int i;
if ( fork() == 0 ) {
/* 子进程程序 */
for ( i = 1; i < 1000; i ++ )
printf("This is child process\n");
}
else {
/* 父进程程序*/
for ( i = 1; i < 1000; i ++ )
printf("This is process process\n");
}
}
程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序还在运行中
,你用ps命令就能看到系统中有两个它在运行了。
那么调用这个fork函数时发生了什么呢?一个程序一调用fork函数,系统就为一个新的进程准备了前述三
个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈
段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,
虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不
再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)
来操作。现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数
则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。
读者也许会问,如果一个大程序在运行中,它的数据段和堆栈都很大,一次fork就要复制一次,那么fork
的系统开销不是很大吗?其实UNIX自有其解决的办法,大家知道,一般CPU都是以“页”为单位分配空间的,
象INTEL的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,
fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行fork时,物理空间上两
个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区
别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。
一个小幽默:下面演示一个足以"搞死"Linux的小程序,其源代码非常简单:
void main()
{
for(;;) fork();
}
这个程序什么也不做,就是死循环地fork,其结果是程序不断产生进程,而这些进程又不断产生新的进程
,很快,系统的进程就满了,系统就被这么多不断产生的进程"撑死了"。用不着是root,任何人运行上述程序
都足以让系统死掉。哈哈,但这不是Linux不安全的理由,因为只要系统管理员足够聪明,他(或她)就可以
预先给每个用户设置可运行的最大进程数,这样,只要不是root,任何能运行的进程数也许不足系统总的能运
行和进程数的十分之一,这样,系统管理员就能对付上述恶意的程序了。
(三) 如何启动另一程序的执行
下面我们来看看一个进程如何来启动另一个程序的执行。在Linux中要使用exec类的函数,exec类的函数
不止一个,但大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp,下面
我只以execlp为例,其它函数究竟与execlp有何区别,请通过manexec命令来了解它们的具体情况。
一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的
数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,
还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。)
那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的
使用。下面一段代码显示如何启动运行其它程序:
char command[256];
void main()
{
int rtn; /*子进程的返回数值*/
while(1) {
/* 从终端读取要执行的命令 */
printf( ">" );
fgets( command, 256, stdin );
command[strlen(command)-1] = 0;
if ( fork() == 0 ) {
/* 子进程执行此命令 */
execlp( command, command );
/* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
perror( command );
exit( errorno );
}
else {
/* 父进程, 等待子进程结束,并打印子进程的返回值 */
wait ( &rtn );
printf( " child process return %d\n",. rtn );
}
}
}
此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统
调用的朋友一定知道DOS/WINDOWS也有exec类函数,其使用方法是类似的,但DOS/WINDOWS还有spawn类函数,
因为DOS是单任务的系统,它只能将“父进程”驻留在机器内再执行“子进程”,这就是spawn类的函数。
WIN32已经是多任务的系统了,但还保留了spawn类函数,WIN32中实现spawn函数的方法同前述UNIX中的方法
差不多,开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统,所以从核
心角度上讲不需要spawn类函数。
另外,有一个更简单的执行其它程序的函数system,它是一个较高层的函数,实际上相当于在SHELL环境
下执行一条命令,而exec类函数则是低层的系统调用。
(四) Linux的进程与Win32的进程/线程有何区别
熟悉WIN32编程的人一定知道,WIN32的进程管理方式与UNIX上有着很大区别,在UNIX里,只有进程的概念
,但在WIN32里却还有一个“线程”的概念,那么UNIX和WIN32在这里究竟有着什么区别呢?
UNIX里的fork是七十年代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果,一方面,
它使操作系统在进程管理上付出了最小的代价,另一方面,又为程序员提供了一个简洁明了的多进程方法。
WIN32里的进程/线程是继承自OS/2的。在WIN32里,“进程”是指一个程序,而“线程”是一个“进程”
里的一个执行“线索”。从核心上讲,WIN32的多进程与UNIX并无多大的区别,在WIN32里的线程才相当于UNIX
的进程,是一个实际正在执行的代码。但是,WIN32里同一个进程里各个线程之间是共享数据段的。这才是与
UNIX的进程最大的不同。
下面这段程序显示了WIN32下一个进程如何启动一个线程:(请注意,这是个终端方式程序,没有图形界面
)
int g;
DWORD WINAPI ChildProcess( LPVOID lpParameter ){
int i;
for ( i = 1; i < 1000; i ++) {
g ++;
printf( "This is Child Thread: %d\n", g );
}
ExitThread( 0 );
};
void main()
{
int threadID;
int i;
g = 0;
CreateThread( NULL, 0, ChildProcess, NULL, 0, &threadID );
for ( i = 1; i < 1000; i ++) {
g ++;
printf( "This is Parent Thread: %d\n", g );
}
}
在WIN32下,使用CreateThread函数创建线程,与UNIX不同,线程不是从创建处开始运行的,而是由
CreateThread指定一个函数,线程就从那个函数处开始运行。此程序同前面的UNIX程序一样,由两个线程各打
印1000条信息。threadID是子线程的线程号,另外,全局变量g是子线程与父线程共享的,这就是与UNIX最大
的不同之处。大家可以看出,WIN32的进程/线程要比UNIX复杂,在UNIX里要实现类似WIN32的线程并不难,只
要fork以后,让子进程调用ThreadProc函数,并且为全局变量开设共享数据区就行了,但在WIN32下就无法实
现类似fork的功能了。所以现在WIN32下的C语言编译器所提供的库函数虽然已经能兼容大多数UNIX的库函数,
但却仍无法实现fork。
对于多任务系统,共享数据区是必要的,但也是一个容易引起混乱的问题,在WIN32下,一个程序员很容
易忘记线程之间的数据是共享的这一情况,一个线程修改过一个变量后,另一个线程却又修改了它,结果引
起程序出问题。但在UNIX下,由于变量本来并不共享,而由程序员来显式地指定要共享的数据,使程序变得
更清晰与安全。
Linux还有自己的一个函数叫clone,这个函数是其它UNIX所没有的,而且通常的Linux也并不提供此函数
(要使用此函数需自己重新编译内核,并设置CLONE_ACTUALLY_WORKS_OK选项),clone函数提供了更多的创建
新进程的功能,包括象完全共享数据段这样的功能。
至于WIN32的“进程”概念,其含义则是“应用程序”,也就是相当于UNIX下的exec了。
======================================================================
多进程编程
本文出自: 饮水思源 bbs.sjtu.edu.cn (2001-05-29 16:27:57)
写在前面的话
本文主要根据本人在UNIX系统上的编程实践经验总结而成, 既做为自己在
一个时期内编程实践的部分总结, 又可成为文章发表. 对UNIX程序员初学者来
说是一个小小的经验, 仅供参考; 对UNIX老手来说则不值一哂, 请各位多多指
教.
一.多进程程序的特点
由于UNIX系统是分时多用户系统, CPU按时间片分配给各个用户使用, 而在
实质上应该说CPU按时间片分配给各个进程使用, 每个进程都有自己的运行环境
以使得在CPU做进程切换时不会"忘记"该进程已计算了一半的"半成品". 以DOS
的概念来说, 进程的切换都是一次"DOS中断"处理过程, 包括三个层次:
(1)用户数据的保存: 包括正文段(TEXT), 数据段(DATA,BSS), 栈段
(STACK), 共享内存段(SHARED MEMORY)的保存.
(2)寄存器数据的保存: 包括PC(program counter,指向下一条要执行的指
令的地址), PSW(processor status word,处理机状态字), SP(stack
pointer,栈指针), PCBP(pointer of process control block,进程控
制块指针), FP(frame pointer,指向栈中一个函数的local变量的首地
址), AP(augument pointer,指向栈中函数调用的实参位置), ISP(
interrupt stack pointer,中断栈指针), 以及其他的通用寄存器等.
(3)系统层次的保存: 包括proc,u,虚拟存储空间管理表格,中断处理栈.
以便于该进程再一次得到CPU时间片时能正常运行下去.
既然系统已经处理好所有这些中断处理的过程, 我们做程序还有什么要担
心的呢? 我们尽可以使用系统提供的多进程的特点, 让几个程序精诚合作, 简
单而又高效地把结果给它搞出来.
另外,UNIX系统本身也是用C语言写的多进程程序,多进程编程是UNIX的特
点,当我们熟悉了多进程编程后,将会对UNIX系统机制有一个较深的认识.
首先我介绍一下多进程程序的一些突出的特点:
1.并行化
一件复杂的事件是可以分解成若干个简单事件来解决的, 这在程序员
的大脑中早就形成了这种概念, 首先将问题分解成一个个小问题, 将小问
题再细分, 最后在一个合适的规模上做成一个函数. 在软件工程中也是这
么说的. 如果我们以图的方式来思考, 一些小问题的计算是可以互不干扰
的, 可以同时处理, 而在关键点则需要统一在一个地方来处理, 这样程序
的运行就是并行的, 至少从人的时间观念上来说是这样的. 而每个小问题
的计算又是较简单的.
2.简单有序
这样的程序对程序员来说不亚于管理一班人, 程序员为每个进程设计
好相应的功能, 并通过一定的通讯机制将它们有机地结合在一起, 对每个
进程的设计是简单的, 只在总控部分小心应付(其实也是蛮简单的), 就可
完成整个程序的施工.
3.互不干扰
这个特点是操作系统的特点, 各个进程是独立的, 不会串位.
4.事务化
比如在一个数据电话查询系统中, 将程序设计成一个进程只处理一次
查询即可, 即完成一个事务. 当电话查询开始时, 产生这样一个进程对付
这次查询; 另一个电话进来时, 主控程序又产生一个这样的进程对付, 每
个进程完成查询任务后消失. 这样的编程多简单, 只要做一次查询的程序
就可以了.
二.常用的多进程编程的系统调用
1.fork()
功能:创建一个新的进程.
语法:#include
#include
pid_t fork();
说明:本系统调用产生一个新的进程, 叫子进程, 是调用进程的一个复
制品. 调用进程叫父进程, 子进程继承了父进程的几乎所有的属
性:
. 实际UID,GID和有效UID,GID.
. 环境变量.
. 附加GID.
. 调用exec()时的关闭标志.
. UID设置模式比特位.
. GID设置模式比特位.
. 进程组号.
. 会话ID.
. 控制终端.
. 当前工作目录.
. 根目录.
. 文件创建掩码UMASK.
. 文件长度限制ULIMIT.
. 预定值, 如优先级和任何其他的进程预定参数, 根据种类不同
决定是否可以继承.
. 还有一些其它属性.
但子进程也有与父进程不同的属性:
. 进程号, 子进程号不同与任何一个活动的进程组号.
. 父进程号.
. 子进程继承父进程的文件描述符或流时, 具有自己的一个拷贝
并且与父进程和其它子进程共享该资源.
. 子进程的用户时间和系统时间被初始化为0.
. 子进程的超时时钟设置为0.
. 子进程的信号处理函数指针组置为空.
. 子进程不继承父进程的记录锁.
返回值: 调用成功则对子进程返回0, 对父进程返回子进程号, 这也是
最方便的区分父子进程的方法. 若调用失败则返回-1给父进程,
子进程不生成.
例子:pid_t pid;
if ((pid=fork())>0) {
/*父进程处理过程*/
}
else if (pid==0) {
/*子进程处理过程*/
exit(0); /*注意子进程必须用exit()退出运行*/
}
else {
printf("fork error\n");
exit(0);
}
2.system()
功能:产生一个新的进程, 子进程执行指定的命令.
语法:#include
#include
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -