获取ID
需包含的头文件
#include <sys/types.h>
#include<unistd.h>
Pid_t getpid(void)
获取本进程ID
Pid_t getppid(void)
获取父进程ID
进程创建-fork
#include <unistd.h>
Pid_t fork(void)
功能:创建子进程
Fork的奇妙之处在于它被调用一次,却返回两次,它可能有三种不同的返回值。
1、在父进程中,fork返回新创建子进程的ID
2、在子进程中,fork返回为0
3、如果出现错误,fork返回一个负值。
fork的返回值这样设计是有原因的,fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程ID,也可以调用getppid函数得到父进程的进程ID。在父进程中使用getpid函数可以得到自己的进程ID,然而要想得到子进程的进程ID,只有将fork的返回值记录下来,别无它法。
因为系统中表示一个进程的实体是进程控制块,当调用fork时,实际上就是创建一个新控制块,而创建一个新控制块最简单的方法就是“复制”。当然“复制”并不是完全复制,因为父进程控制块中的某些内容还需按照子进程的特点来修改,例如进程的标识、状态等。另外,子进程控制块还必须有自己的私有空间,如数据空间,用户堆栈。
父进程进行fork系统调用时完成的操作
假设id=fork(),父进程进行fork系统调用时,fork所做工作如下:
① 为新进程分配task_struct任务结构体内存空间。
② 把父进程task_struct任务结构体复制到子进程task_struct任务结构体。
③ 为新进程在其内存上建立内核堆栈。
④ 对子进程task_struct任务结构体中部分变量进行初始化设置。
⑤ 把父进程的有关信息复制给子进程,建立共享关系。
⑥ 把子进程加入到可运行队列中。
⑦ 结束fork()函数,返回子进程ID值给父进程中栈段变量id。
⑧ 当子进程开始运行时,操作系统返回0给子进程中栈段变量id。
调用fork前的父进程在内存中的映像和调用fork之后父子进程在内存中的映像。
因此下列代码的运行结果是:
count = 1
count = 1
进程创建-vfork
包含的头文件
#include <unistd.h>
#include<sys/types.h>
Pid_t fork(void)
功能:创建子进程
Vfork与fork的区别
1、fork:子进程拷贝父进程的数据段
vfork :子进程和父进程共享数据段,父子进程资源共享。
2、fork:父,子进程的执行顺序不确定
Vfork:子进程先运行,父进程后运行
Exec函数族
Exec函数族的作用是根据指定的文件名找到可执行文件,并将其关联到调用exec族函数的进程,从而使进程执行该可执行文件。简单的说,就是用exec族函数加载的程序文件替换该进程原来的程序文件。
与一般函数不同,exec函数执行成功后一般不会返回调用点,因为它运行了一个新的程序,进程的代码段,数据段和堆栈等都已经被新的数据所取代,只留下进程ID等一些表面信息仍保存原样。
函数execv()
为了运行中能够加载并运行一个可执行文件,linux提供了系统调用execv()。
Int execv(const char * path,char *const argv[])
其中,参数path为可执行文件路径,argv[]为命令行参数。
如果一个进程调用execv(),那么该函数变回把函数参数path所指定的可执行文件加载到进程的用户内存空间,并覆盖掉原文件,然后并运行这个新加载的可执行文件。
在实际应用中,调用execv()通常为子进程。人们之所以创建一个子进程,其目的就是执行一个与父进程代码不同的程序,而系统调用execv()就是子进程执行一个新程序的手段。子进程调用execv()后,系统会立即为子进程加载可执行文件分配私有内存空间,从此子进程也成为一个真正的进程。如果说子进程是父进程的儿子,那么子进程在调用execv()前,它所具有的单独用户堆栈和数据区也仅相当于它的私有“衣橱”和“书柜”;但因它还没有自己的“住房”,因此也只能寄住在父亲家,而不能自立门户,尽管它有自己的“户口”(进程控制块)。只有当子进程调用了execv()并有了自己的可执行文件后,他才算娶了媳妇,从而系统才能为其分配”住房”(程序执行空间),这时它才真正的独立了。
调用execv()后,父进程与子进程存储结构示意:
#include<unistd.h>
Main()
{
Char * argv[]={“ls”,”-al”,”};
}
执行结果
Execl函数
Int execl(const char *path,const char *arg1,...)
参数
Path:被执行程序名(含完整路径)。
Arg1..argn:被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
运行如下程序
#include<unistd.h>
Main()
{
Execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char*)0);
}
该程序的运行结果与
Ls -al /etc/passwd运行结果一致。
Execlp函数
Int execlp(const char *path,const char *arg1,..)
参数:
Path:被执行程序名(不含路径,将从path环境变量中查找该程序)
Arg1-argn:被执行程序所需的命令行参数,含程序名。以空指针(NULL)结束。
#include <unistd.h>
Main()
{
Execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char*)0);
}
该程序的运行结果与
Ls -al /etc/passwd运行结果一致。
System函数
Int system(const char* string)
功能:
调用fork产生子进程,由子进程来调用/bin/sh -c string来执行参数string所代表的命令。
程序
#include<stdlib.h>
void main()
{
System(“ls -al/etc/passwd”);
}
进程等待
所需头文件
#include <sys/types.h>
#include<sys/wait.h>
Pid_t wait(int *status)
功能:
虽然子进程调用函数execv()之后拥有自己的内存空间,成为一个真正的进程,但由于子进程毕竟是由父进程所创建,所以按照计算机技术中谁创建谁销毁的惯例,父进程需要在子进程结束之后释放子进程所占有的系统资源。为实现上述目标,当子进程运行结束时,系统会向该子进程的父进程发出一个信息,请求父进程释放子进程所占用的系统资源。当然,如果这时父进程还没有结束,那么父进程就会义不容辞的完成这项任务。如果父进程没有把握结束于子进程之后,那么为了保证完成子进程释放资源的任务,父进程应该调用系统调用wait().
如果一个进程调用了这个系统调用,那么进程就立即进入中止运行的等待状态(阻塞状态),一直等到系统为本进程发出一个消息。在处理父进程与子进程的关系上,那就是在等待某个子进程已经退出的消息;如果父进程得到这个信息,父进程就会在处理子进程的“后事”之后才会继续运行。
实验程序:
#include <unistd.h>
#include <sys/types.h>
main()
{
char i;
pid_t pid;
if(!(pid = fork()))
{
execv("/home/sun/test/hello1.c",NULL);
//execv运行失败将执行下面语句
printf("pid %d:i am back ,sth is wrong!\n",getppid());
}
else
{
printf("my pid is %d\n",getpid());
wait4(pid,NULL,0,NULL);//等待子进程退出系统
for(i=0;i<10;i++)
printf("done\n");
}
}
程序运行结果
如果父进程先于子进程结束,则子进程就会因失去了父进程而成为孤儿进程。在linux中,如果一个进程变成了孤儿进程,那么这个进程将以系统在初始化创建的init进程为父进程。