(一) 理解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了。
WIFEXITED/WEXITSTATUS/WIFSIGNALED
If the exit status value (*note Program Termination::) of the child
process is zero, then the status value reported by `waitpid' or `wait'
is also zero. You can test for other kinds of information encoded in
the returned status value using the following macros. These macros are
defined in the header file `sys/wait.h'.
-- Macro: int WIFEXITED (int STATUS)
This macro returns a nonzero value if the child process terminated
normally with `exit' or `_exit'.
-- Macro: int WEXITSTATUS (int STATUS)
If `WIFEXITED' is true of STATUS, this macro returns the low-order
8 bits of the exit status value from the child process. *Note
Exit Status::.
-- Macro: int WIFSIGNALED (int STATUS)
This macro returns a nonzero value if the child process terminated
because it received a signal that was not handled. *Note Signal
Handling::.
子进程的结束状态返回后存于status,底下有几个宏可判别结束情况
WIFEXITED(status)如果子进程正常结束则为非0值。
WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status)取得引发子进程暂停的信号代码
发送信号的kill和raise函数
int kill(pid_t pid, int sig);
int raise(int sig);
kill()发送信号给指定进程,raise()发送信号给进程本身。对kill()的pid,有如下描述:
pid > 0 将信号发送给ID为pid的进程
pid == 0 将信号发送给与发送进程属于同个进程组的所有进程
pid < 0 将信号发送给进程组ID等于pid绝对值的所有进程
pid == -1 将信号发送给该进程有权限发送的系统里的所有进程
所有信号的发送都要先经过权限检查,如果进程没有相应发送的权限,kill()会出错返回,并把errno设为EPERM。但也有一个例外,对SIGCONT,进程可以将它发送给当前会话的所有进程。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
if((pid=fork())<0){
perror("fork");
exit(1);
}
if(pid == 0){
raise(SIGSTOP);
exit(0);
}
else{
printf("pid=%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0)
{
if((ret=kill(pid,SIGKILL))==0)
printf("kill %d\n",pid);
else{
perror("kill");
}
}
}
}
waitpid(父进程等待子进程中断或结束)
表头文件
#include<sys/types.h>
#include<sys/wait.h>
定义函数 pid_t waitpid(pid_t pid,int * status,int options);
函数说明
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程
结束。如果在调用 waitpid()时子进程已经结束,则 waitpid()会立即
返回子进程结束状态值。 子进程的结束状态值会由参数 status 返回,
而子进程的进程识别码也会一起返回。如果不在意结束状态值,则
参数 status 可以设成 NULL。参数 pid 为欲等待的子进程识别码,
其他数值意义如下:
pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
pid=-1 等待任何子进程,相当于 wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为 pid 的子进程。
参数options提供了一些额外的选项来控制waitpid,参数 option 可以为 0 或可以用"|"运算符把它们连接起来使用,比如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如: ret=waitpid(-1,NULL,0);
WNOHANG 如果没有任何已经结束的子进程则马上返回, 不予以等待。
WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
子进程的结束状态返回后存于 status,底下有几个宏可判别结束情况:
WIFEXITED(status)如果子进程正常结束则为非 0 值。
WEXITSTATUS(status)取得子进程 exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用 WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用 WIFSTOPPED 来判断后才使用此宏。
如果执行成功则返回子进程识别码(PID) ,如果有错误发生则返回
产生时钟信号SIGALRM的alarm函数
unsigned int alarm(unsigned int seconds);
alarm()函数可设置一个计时器,计时器超时就产生SIGALRM信号。由于每个进程只能有一个SIGALRM处理程序,所以只能为一个进程设置一个计时器,所以alarm()和setitimer()会共享同一个SIGALRM信号和该信号的处理函数。也就是说,alarm()和 setitimer()彼此会互相影响。调用alarm(),会使先前设置的计时器失效,并把没有超时的时间作为当前alarm的返回值。如先前设置的时钟为100秒,当前调用alarm()时才经过30秒,剩余的70秒就作为alarm()的返回值,并用alarm()中指定的秒数重新设置计时器。如果 seconds为0,则会取消先前设置的计时器,并将其余留值作为alarm()的返回值。
等待信号的pause函数
int pause(void);
pause()会使当前进程挂起,直到捕捉到一个信号,对指定为忽略的信号,pause()不会返回。只有执行了一个信号处理函数,并从其返回,puase()才返回-1,并将errno设为EINTR。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
void my_func(int sign_no)
{
if(sign_no==SIGINT)
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUIT \n ");
signal(SIGINT, my_func);
signal(SIGQUIT, my_func);
pause();
exit(0);
}
分享到:
相关推荐
【Linux操作系统下的多进程编程】 Linux操作系统是一种广泛应用于服务器、嵌入式设备和各种计算平台的开源操作系统。其核心特性之一就是支持多进程(Multi-Process)编程模型,这一特性在海洋环境信息系统的开发中...
Linux多进程编程这份文档详细讲述了Linux下的多进程编程现实例程。
Linux下的多进程编程 Linux 操作系统作为一个完整的 UNIX 类操作系统,具有广泛的应用前景。其中,多进程编程是 Linux 开发中一个非常重要的概念。本文将对 Linux 下的多进程编程进行详细的介绍,包括进程的结构、...
【Linux下的多进程编程初步】 Linux操作系统以其强大的多任务处理能力而闻名,其中多进程编程是其核心特性之一。在Linux环境下,通过多进程可以实现并发执行,提高系统的资源利用率和程序的执行效率。本文将深入...
### Linux下的多进程编程初步 #### 摘要 多线程程序设计的概念自六十年代初被提出以来,直到八十年代中期才在Unix系统中正式引入多线程机制。随着时间的发展,多线程编程因其高效性及灵活性在软件开发领域占据了...
Linux系统是一个免费使用和自由传播的类Unix操作系统,基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统,Linux是许多企业...
linux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多进程编程 forklinux 多...
在"嵌入式Linux多进程编程"这个主题中,我们将深入探讨如何在嵌入式设备上实现多进程以及相关的技术要点。 首先,理解进程的基本概念至关重要。进程是程序的一次执行实例,每个进程都有自己的虚拟地址空间,包括...
Linux多进程编程是操作系统原理中的重要概念,尤其对于软件开发者来说,理解并掌握这一技能是必要的。在Linux系统中,多进程编程允许我们利用系统资源,通过创建多个并发执行的进程来提高程序的效率和响应速度。以下...
Linux下多线程及多进程及同步与互斥编程详细介绍
Linux下C语言应用编程》深入浅出地讲解了Linux下C应用程序开发所需的基本工具、知识和技巧,主要包括:开发工具的使用和技巧、文件I/O编程、多进程编程、进程间通信、多线程编程、网络编程等内容。可以让读者轻松、...
通过对这个项目的深入学习,你可以掌握Linux下多进程编程的基本技巧,理解进程通信的原理,并能应用于实际的文件传输场景。这不仅有助于提升你的C语言编程能力,也会增强你在系统编程领域的专业素养。
对于开发者而言,深入理解Linux进程编程是提升技术水平的重要一环。本文将详细探讨Linux进程编程的基础知识,包括进程的概念、创建与管理进程、进程间通信以及线程的使用。 首先,我们需要了解什么是进程。在操作...
### Linux下多进程、多线程编程 #### 进程结构理解 在深入探讨Linux下的多进程和多线程编程之前,我们先来了解一下Linux进程的基本结构。在Linux环境中,每一个进程都拥有独立的地址空间,这个地址空间可以被划分...
linux网络编程-多进程并发
在Linux多进程编程中,进程可以通过信号与操作系统或其他进程进行交互。信号是一种非常简短的消息,用于进程间的通信。PHP的pcntl扩展同样支持信号的发送和处理,但是文章并没有深入讲解这部分内容,仅提示读者这一...