您的位置:首页 > 运维架构 > Linux

学习linux多线程编程----相关概念

2009-12-26 21:58 495 查看
学习Linux多进程编程

一、进程的定义:程序执行和资源管理的最小单位。

二、进程控制:

(1)进程标识: 进程标识 子进程号 父进程号
头文件 #include<unistd.h> #include<unistd.h>
函数功能 取得当前进程的进程号 取得当前进程的父进程号
函数原型 Pid_t getpid(void) Pid_t getppid(void)
函数返回值 成功返回进程的进程标识符 成功返回父进程的进程标识符

注:Pid_t其实是一个typedef类型,相当于unsigned int.

例:

#include<stdio.h>

#include<unistd.h>

int main()

{

printf("系统分配的进程号是:%d/n",getpid());

printf("系统分配的父进程号是:%d/n",getppid());

return 0;

}

(2)进程的创建:

1)exec族函数:

头文件 #include<unistd.h>
原型
int execl(const chat *path,const char *args,...)

int execv(const char *path,char const *argv[])

int execle(const cahr *path,const char *arg,...,char *const envp[])

int execve(const char *path,char *const argv[],char *const envp[])

int execlp(const char *file,char *arg,...)

int execvp(const cahr *file,char *const argv[])

返回 返回-1表示出错

由于比较多,在此只举例execve函数:

#include<stdio.h>

#include<unistd>

int main()

{

char *args[]={"/usr/bin/vim",NULL};

printf("系统分配的进程号是:%d/n",getpid());

if(execve("/usr/bin/vim",args,NULL)<0)

perror("创建进程出错!");

return 0;

}

注:在用execve函数创建新进程的后,会以新的程序取代原来的进程,然后系统会从新的进程运行,但是新的进程的PID值会与原来进程的PID值相同.

2)system()函数

头文件 #include<stdlib.h>
功能 在进程中开始另一个进程
原型 int system(const char *string)
传入值 系统变量
返回值
成功则返回执行shell命令后的返回值,调用/bin/sh数百返回127,其他

失败返回-1,三叔string为空返回非零值

例:

#include<stdio.h>

#include<stdlib.h>

main()

{

int newret;

printf("系统分配的进程号是:%d/n",getpid());

newret=system("ping group.google.com/group/linuxerfamily");

return 0;

}

注:执行system函数是,会调用fork,execve,waitpid函数,其中任意一个调用失败将导致system函数调用失败。

3)fork函数:

头文件 #include<unistd.h>
功能 建立一个新进程(复制进程)
原型 pid_t fork(void);
返回值
执行成功则在子进程中返回0,在父进程中返回新建进程的PID,

失败返回-1

例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#inlcude<sys/types.h>

int main()

{

pid_t result;

result=fork();

int newret;

if(result==-1)

{

perror("创建进程失败!");

exit(1);

)}

else if(result==0)

{

printf("返回值是:%d,说明这是子进程!/n此进程的进程号是:%d/n此进程的父进程号是:% d/n",result,getpid(),getppid());

newret=system("ls -l");

}

else

{

sleep(10);

printf("返回值是:%d,说明这是父进程!/n此进程的进程号是:%d,此进程的父进程号是:% d/n",result,getpid(),getppid());

} newret=system("ping "group.google.com/group/linuxerfamily"); }

return 0;

}

注:使用fork要小心,特别是当把它放在if else或循环里,如:

int main()

{

for(;;) fork();

}

可能造成系统死机,因为由fork创建的进程如果比父进程结束的晚,就有可能形成僵尸进程,占用系统资源又没有用,造成资源殆尽。

功能 等待子进程中断或结束
原型 Pid_t wait(int *status)
传入值 status子进程状态
返回值 成功返回子进程的进程号,否则返回-1
注 wait()会暂停目前进程的执行,直到有信号来到或子进程终止

例:

#include<stdio.h>

#include<sys/types.h>

#include<sys/wait.h>

#include<unistd.h>

int main()

{

pid_t child;

int i;

child=fork();

if(child<0)

{

perror("创建进程失败!");

exit(1);

}

else if(child==0)

{

printf("这是子进程,进程号是:%d/n",getpid());

for(i=0;i<100;i++)

printf("这是子进程第%d此打印!/n",i+1);

printf("子进程结束!");

}

else

{

printf("这是父进程,进程号是:%d/n",getppid());

printf("父进程等待子进程结束...");

wait(&child);

printf("父进程结束!");

}

}

注:wait()函数会阻塞父进程运行直到子进程正常结束。

5)waitpid()函数

头文件
#include<sys/types.h>

#include<sys/wait.h>

功能 等待子进程中断或结束
原型 pid_t waitpid(pid_t pid,int *status,int options)
传入值
pid为子进程号

status为子进程状态

option可以为0、WNOHANG(如果没有任何已终止的子进程则马上返回,不予等待)或WUNTRACED(如果子进程进入暂停执行则马上返回,但终止状态不予理会)

返回值 成功返回子进程号,否则返回-1
注 此函数会暂停目前进程的执行,直到有信号来到或子进程终止

例:略(同wait()例差不多)。

进程终止:

6)exit()函数

头文件 #include<stdlib.h> #include<stdlib.h>
功能 正常终止进程 终止进程执行
原型 void exit(int status) void _exit(int status)
传入数值 整数 status 整数 status

exit()用来正常终止目前进程的

执行,并把参数status返回给父

进程,而进程所有的缓冲数据会

自动写回并关闭未关闭的文件

_exit()哟年来立刻终止目前进程

的执行,并把参数status返回给父

进程,并关闭未关闭的文件,但不处

理标准I/O缓冲区

例:

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

int main()

{

pid_t result;

result=fork();

if(result==-1)

{

perror("创建进程失败!");

esit(0);

}

else if(result==0)

{

printf("测试终止进程函数_exit()!/n");

printf("这一行用缓存!");

_exit(0);

}

else

{

printf("测试终止进程函数exit()!/n");

printf("这一行用缓存!");

exit(0);

}

return 0;

}
-----------------------------------------------------
出自:俞磊 2002年01月07日 14:01
(一) 理解Linux下进程的结构
  Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,其实学过汇编
语言的人一定知道,一般的CPU象I386,都有上述三种段寄存器,以方便操作系统的运行。“代码段”,顾名
思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一
个代码段。
  堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局
变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,
这里限于篇幅就不多介绍了。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据
段。

(二) 如何使用fork
  在Linux下产生新的进程的系统调用就是fork函数,这个函数名是英文中“分叉”的意思。为什么取这个
名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了,所以这
个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架:

void main(){
int i;
if ( fork() == 0 ) {
/* 子进程程序 */
for ( i = 1; i " );
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了。
---------------------------------------------------------------------------
linux多进程编程
在linux中,运行的一个进程,会占去linux的三个地方,代码区,堆栈区和数据区.如果同时运行多个相同的程序,他们就会使用相同的代码区,代码区中存放的就程序的代码,但是数据区和堆栈区分别存放的是程序的数据,全局变量和局部变量,因此即使是相同的程序,也不可同时使用相同的数据和堆栈区.

#include<stdio.h>

#include<unistd.h>

int main()

{

if(fork() == 0)

{

printf("First./n");

if(fork() == 0)

{

printf("Second./n");

}

else

{

printf("Third./n");

}

}

else

{

printf("Fourth./n");

if(fork() == 0)

{

printf("Fivth./n");

}

else

{

printf("Sixth./n");

}

}

}

可以关注下程序的运行结果,以及输出的顺序,并理解为什么是这种输出结果.

fork()函数:

程序调用fork()函数,系统就为新的进程准备了堆栈区,数据区和代码区.系统先让fork()出的进程和原先的进程使用同一个代码区.那么数据区和堆栈区就不能共享了,因此系统会复制一份完全一抹一样的给fork()出的那个进程.这样父进程的所有数据就给了子进程.这样子进程开始运行时,虽然复制了父进程的数据和堆栈,但是数据和堆栈已经分开了,相互之间已经没有影响了.对于父进程,fork()函数返回了子进程的进程号,对于子进程,fork()函数则返回0,因此根据fork()函数的返回值就能知道,程序现在处的位置是在子进程中还是在父进程中.

也就是传说中的,一个函数具有两个返回值,就是指fork()函数.

那么引来一个问题...如何在一个进程中让另外一个进程启动呢..?

在linux中,基本上使用exec类的函数,但是exec类函数的使用有一个特点就是:一旦你使用exec类的函数,原先的进程就废掉了,因为代码段会被新的进程占据,数据区和堆栈区也会被废掉,并产生新的数据区和堆栈区供新的进程使用.唯一没有变的就是进程号,实际上,只有PID是一样的,其他的东西已经物是人非了.对系统而言,还是同一个进程,因为系统只认进程号,而对编程而言,已经完全是一个新的进程了.

那么...又来了...

如果你想继续原先的进程运行,并且同时启动新的进程..要怎么办..?

看上面我给的代码...对了..就是利用fork()函数和exec类的函数搭配使用.

fork()出一个和父进程完全一抹一样的进程,然后再使用exec类函数来启动新的进程,这样,原先的进程也在运行,新的进程也在运行了.只不过,区别是,现在的关系变成了父进程和子进程的关系了,而不是原来的同一个进程的关系.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: