linux 进程(二) --- 进程的创建及相关api
来源:互联网 发布:音乐变调器软件 编辑:程序博客网 时间:2024/05/18 20:36
一、进程的创建fork()函数
由fork创建的新进程被称为子进程(child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是 新子进程的进程ID。将子进程ID返回给父进程的理由是:因为一个进程的子进程可以多于一个,所有没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID(进程 ID 0总是由交换进程使用,所以一个子进程的进程ID不可能为0)。
子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程拥有的拷贝。父、子进程并共享这些存储部分。如果正文段是只读的,则父、子进程共享正文段。
现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在fork之后经常跟随着exec。作为替代,使用了写时复制(copy-on-write,cow)的技术。这些区域由父、子进程共享,而且内核将他们的存取许可权改变位只读的。如果有进程试图修改这些区域,则内核包异常,典型的是虚存系统中的“页”,做一个拷贝。
实例1:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glob = 6;
char buf[] = "a write to stdout\n";
int main()
{
int var;
int pid;
var = 88;
if(write(STDOUT_FILENO,buf,sizeof(buf) -1) != sizeof(buf) -1)
{
perror("fail to write");
return -1;
}
printf("before fork\n");
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}else
if(pid == 0)
{
glob ++;
var ++;
}else{
sleep(2);
}
printf("pid = %d,glob = %d,var = %d\n",getpid(),glob,var);
exit(0);
}
运行结果:
从上面可以看出,因为子进程和父进程拥有独立的物理内存空间,所以当子进程对拷贝来的数据做修改的时候,并没有影响到父进程。
注意:
1.一般来说,fork之后父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
2.从上面可以看到两次的运行结果不一样。我们知道write函数是不带缓存的。因为在fork之前调用write,所以其数据写到标准输出一次。但是,标准 I/O库是带缓存的。如果标准输出连到终端设备,则它是行缓存的,否则它是全缓存的。当以交互方式运行该程序时,只得到printf输出的行一次,其原因是标准输出缓存由新行符刷新。但是当将标准输出重新定向到一个文件时,却得到printf输出行两次。其原因是,在fork之前调用了printf一次,当调用fork时,该行数据仍在缓存中,然后在父进程数据空间复制到子进程中时,该缓存数据也被复制到子进程中。于是那时父、子进程各自有了带该行内容的缓存。在exit之前的第二个printf将其数据添加到现存的缓存中。当每个进程终止时,其缓存中的内容被写到相应文件中。
实例 2:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glob = 6;
int main()
{
int var;
int pid;
var = 88;
printf("father:\n");
printf("&glob = %p\n",&glob);
printf("&var = %p\n",&var);
printf("__________________________________\n");
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}else
if(pid == 0)
{
printf("child var value not change\n:");
printf("&glob = %p\n",&glob);
printf("&var = %p\n",&var);
glob ++;
var ++;
printf("__________________________________\n");
printf("child var value change:\n");
printf("&glob = %p\n",&glob);
printf("&var = %p\n",&var);
}
exit(0);
}
运行结果如下:
从上面可以看出,根据copy-on-write的思想,在子进程中,改变父进程的数据时,会先 复制父进程的数据修然后再改,从而达到子进程对数据的修改不影响父进程。但是我们发现,复制的前后,其值的地址都是一样的。为什么呢?子进程拷贝的时候也拷贝了父进程的虚拟内存"页",这样他们的虚拟地址都一样,但是对应不同的物理内存空间。
二、copy-on-write工作原理
假设进程A创建子进程B,之后进程A和进程B共享A的地址空间,同时该地址空间中的页面全部被标识为写保护。此时B若写address的页面,由于写保护的原因会引起写异常,在异常处理中,内核将address所在的那个写保护页面复制为新的页面,让B的address页表项指向该新的页面,新页面可写。而A的address页表项依然指向那个写保护的页面。然后当B在访问address时就会直接访问新的页面了,不会在访问到哪个写保护的页面。当A试图写address所在的页面时,由于写保护的原因此时也会引起异常,在异常处理中,内核如果发现该页面只有一个拥有进程,此种情况下也就是A,则直接对该页面取消写保护,此后当A再访问address时不会在有写保护错误了。如果此时A又创建子进程C,则该address所在的页面又被设置为写保护,拥有进程A和C,同时其他页面例如PAGEX依然维持写保护,只是拥有进程A、B和C。如果此时A访问PAGEX,则异常处理会创建一个新页面并将PAGEX中的内容复制到该页面,同时A相应 的pte指向该新页面。如果此时C也访问PAGEX,也会复制新页面并且让C对应的pte指向新页面。如果B再访问PAGEX,则由于此时PAGEX只有一个拥有进程B,故不再复制新页面,而是直接取消该页面的写保护,由于B的pte本来就是直接指向该页面,所以无需要在做其它工作。
三、exit和_exit
(1)正常终止:
(a)在main函数内执行return语句。这等效于调用exit。
(b)调用exit函数
(c)调用_exit系统调用函数
(2)异常终止:
(a)调用abort。它产生SIGABRT信号,所以是一种异常终止的一种特列。
(b)当进程接收到某个信号时。例如,进程越出其地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。
注意:不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
exit和_exit的不同
_exit()函数的作用最为简单:直接进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O"缓冲。
探究 1._exit()
//_exit(0) exit(0) return 0
编译运行结果:
编译运行结果:
从上面我们看到,test.txt的内容为空.为什么呢?因为标准I/O函数是带缓存的,进行fputs的时候是先向缓存中写的,只有当缓存满的时候才会刷新的缓冲区的。从以上我们发现,当进程退出时,执行_exit()函数并没有刷新缓冲区的数据,而是直接终止进程的。
探究2.exit()
编译运行结果:
编译运行结果:
从上面我们可以看到,当exit()函数结束进程的时候,对缓存进行了处理,把缓存的数据写到了磁盘文件中。
探究3.return
由读者自己完成,其实return语句用在main函数中,和exit是一样的。但是我们知道,return返回的值是给调用者的,它代表着一个函数的结束。
四、exec函数族
exec.c 调用exec其中的一个函数; gcc exec.c -o exec; ./exec
exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段、和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
何时使用?
当进程认为自己不能再为系统和用户做任何贡献了就可以调用exec函数族中的函数,让自己执行新的程序。
当前目录: 可执行程序A B(1,2,3)
如果某个进程想同时执行另一个程序,它就可以调用fork函数创建子进程,然后在子进程中调用任何一个exec函数。这样看起来就好像通过执行应用程序而产生了一个新进程一样。
execl("./B","B","1","2","3",NULL);
char *const envp[] = {"B","1","2","3",NULL}
execv("./B",envp);
注意:不管file,第一个参数必须是可执行文件的名字
可执行文件查找方式
表中的前四个函数的查找方式都是指定完整的文件目录路劲,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量"$PATH"所包含的路径中进行查找。
参数表传递方式
两种方式:一个一个列举和将所有参数通过指针数组传递
一函数名的第5个字母按来区分,字母"l"(list)的表示一个一个列举方式;字母"v"(vector)的表示将所有参数构造成指针数组传递,其语法为char *const argv[]
环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以"e"(Envirment)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量。
使用的区别
可执行文件查找方式
参数表传递方式
环境变量的使用
程序运行结果:
案例一execl
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("start to execl.\n");
if(execl("/bin/ls","ls",NULL) < 0)
{
perror("Fail to execl");
return -1;
}
printf("end of execl.\n");
return 0;
}
运行结果如下:
案例二、execlp
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("start to execl.\n");
if(execlp("ls","ls","-l",NULL) < 0)
{
perror("Fail to execl");
return -1;
}
printf("end of execl.\n");
return 0;
}
运行结果:
案例三、execle
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
if(getenv("B") == NULL)
{
printf("fail to getenv B.\n");
}else{
printf("env B = %s.\n",getenv("B"));
}
if(getenv("C") == NULL)
{
printf("fail to getenv C.\n");
}else{
printf("env C = %s.\n",getenv("C"));
}
if(getenv("PATH") == NULL)
{
printf("fail to getenv PATH.\n");
}else{
printf("env PATH = %s.\n",getenv("PATH"));
}
return 0;
}
运行结果:
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("start to execle.\n");
char * const envp[] = {"B=hello",NULL};
if(execle("./A.out","A.out",NULL,envp) < 0)
{
perror("Fail to execl");
return -1;
}
printf("end of execl.\n");
return 0;
}
运行结果:
案例四:execv
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
char * const arg[] = {"ps", "-ef", NULL};
//if (execl("/bin/ps", "ps", "-ef", NULL) < 0)
if (execv("/bin/ps" ,arg) < 0)
{
perror("execl");
exit(-1);
}
while (1);
return 0;
}
五、进程的创建vfork()函数
vfork与fork一样都创建一个子进程,但是它并不将父进程的地址空完全复制到子进程中,因为子进程会立即调用exec(或exit)于是也就不会存、访该地址空间。不过在子进程调用exec或exit之前,它在父进程的空间中运行。
vfork和fork之间的另一个区别是:vfork保证子进程先运行,在它调用exec或exit之后 父进程才可能被调度运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁)
探究1.vfork()
编译运行:
编译运行:
因为我们知道vfork保证子进程先运行,子进程运行结束后,父进程才开始运行。所以,第一次打印的是子进程的打印的信息,可以看到var值变成了89。子进程结束后,父进程运行,父进程首先打印fork调用返回给他pid的值(就是子进程pid)。以上我们可以看出,vfork创建的子进程和父进程运行的地址空间相同(子进程改变了var 值,父进程中的var值也进行了改变)。
注意:如果子进程中执行的是exec函数,那就是典型的fork的copy-on-wirte。
五、wait和waitpid
wait函数:调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。
waitpid函数:功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)。
wait函数
#include <sys/types.h>
#include <sys/waith.h>
pid_t wait(int *status);
函数参数:
status是一个整型指针,指向的对象用来保存子进程退出时的状态。
A.status若为空,表示忽略子进程退出时的状态
B.status若不为空,表示保存子进程退出时的状态
子进程的结束状态可由Linux中一些特定的宏来测定。
案例一、
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
if((pid = fork()) < 0)
{
perror("Fail to fork");
return -1;
}else if(pid == 0){
printf("child exit now.\n");
exit(0);
}else{
while(1);
}
exit(0);
}
运行结果:
输入字符后:
给进程15494发个信号
程序运行结果:
从以上可以看出,子进程正常退出时,处于僵尸态。这个时候子进程的pid,以及内核栈资源并没有释放,这样是不合理的,我们应该避免僵尸进程。如果父进程先退出呢,子进程又会怎样?
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
if((pid = fork()) < 0)
{
perror("Fail to fork");
return -1;
}else if(pid == 0){
printf("child running now - pid : %d.\n",getpid());
while(1);
}else{
getchar();
printf("Father exit now - pid : %d.\n",getpid());
exit(0);
}
}
从上面可以看出,如果父进程先退出,则子进程的父进程的ID号变为1,也就是说当一个子进程的父进程退出时,这个子进程会被init进程自动收养。
案例二、利用wait等待回收处于僵尸态的子进程
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
if((pid = fork()) < 0)
{
perror("Fail to fork");
return -1;
}else if(pid == 0){
printf("child runing now - pid : %d.\n",getpid());
getchar();
printf("child exiting now - pid : %d.\n",getpid());
exit(0);
}else{
printf("Father wait zombie now - pid : %d.\n",getpid());
wait(NULL);
printf("Father exiting now - pid : %d.\n",getpid());
exit(0);
}
}
没有输入字符前:
输入字符后:
此时我们没有发现僵尸进程,当子进程退出时,父进程的wait回收了子进程未释放的资源。
案例三、获取进程退出时的状态
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
while(1);
}else{
while((pid = wait(&status)) != -1)
{
if(WIFEXITED(status))
{
printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status));
}else if(WIFSIGNALED(status)){
printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status));
}else{
printf("Not know.\n");
}
}
}
}
printf("All child process is exit,father is exit.\n");
exit(0);
}
给进程15494发个信号
程序运行结果:
从以上探究可以知道,每当子进程结束后,wait函数就会返回哪个子进程结束的pid。如果没有子进程存在,wait函数就返回-1。
函数返回值:
成功:子进程的进程号
失败:-1
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
参数:
1.在父进程中创建两个子进程(A B)
2.A进程打印"child process %d exit",调用exit(2),结束
3.B进程一直运行
注意:父进程调用while(waitpid(-1,&status,WUNTRACED) != -1 )
status:同wait
options:
WNOHANG,若由pid指定的子进程并不立即可用,则waitpid不阻塞,此时返回值为0
WUNTRACED,若某实现支持作业控制,则由pid指定的任一子进程状态已暂停,且其状态自暂停以来还没报告过,则返回其状态。
0:同wait,阻塞父进程,等待子进程退出。
返回值
正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
调用出错:-1
案例一、
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
while(1);
}else{
while((pid = wait(&status)) != -1)
{
if(WIFEXITED(status))
{
printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status));
}else if(WIFSIGNALED(status)){
printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status));
}else{
printf("Not know.\n");
}
}
}
}
printf("All child process is exit,father is exit.\n");
exit(0);
}
程序运行结果:
使用ps -aux结果
使用ps -aux结果
从以上可以看出,子进程15783退出时,父进程并没有回收它的资源,此时可以看到它处于僵尸态。
由于父进程调用waitpid等待子进程15784退出,此时这个进程还没退出,看可以看到父进程处于可中断的睡眠状态。
我们给子进程15784发个信号,在看看结果
程序运行结果:
可以看到当waitpid指定等待的进程退出时,waitpid立即返回,此时父进程退出。
案例二、
#include <stdio.h>
#include <stdlib.h>
int main()
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
while(1);
}else{
sleep(2);
printf("Father wait child %d exit.\n",pid);
while((pid = waitpid(pid,NULL,WNOHANG)))
{
printf("The process %d is exit.\n",pid);
}
printf("The process %d is exit.\n",pid);
}
}
exit(0);
}
从上面探究我们可以看出,如果有子进程处于僵尸态,waitpid(pid,NULL,WNOHANG)立即处理后返回,如果没有子进程处于僵尸态,此时waitpid(pid,NULL,WNOHANG)也会立即返回,而不阻塞,此时返回值为0。
案例探究三、
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
int pid;
int status;
if((pid = fork()) < 0)
{
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
printf("create child process : %d.\n",getpid());
printf("child process in proces group %d.\n",getpgid(0));
printf("child process : %d calling exit(7).\n",getpid());
exit(7);
}else{
if((pid = fork()) < 0 ){
perror("Fail to fork");
exit(-1);
}else if(pid == 0){
sleep(3);
printf("create child process : %d.\n",getpid());
setpgid(0,0); //让子进程属于以自己ID作为组的进程组
printf("child process in proces group %d.\n",getpgid(0));
printf("child process : %d calling exit(6).\n",getpid());
}else{
while(pid = waitpid(0,NULL,0))
{
printf("Father wait the process %d is exit.\n",pid);
}
}
}
exit(0);
}
运行结果:
当在父进中创建子进程时,父进程和子进程都在以父进程ID号为组的进程组。以上代码中,有一个子进程改变了自己所在的进程组.
此时waitpid(0,NULL,0);只能处理以父进程ID为组的进程组中的进程,可以看到第一个子进程结束后waitpid函数回收了它未释放的资源,而第二个子进程则处于僵尸态
0
上一篇:进程间通信--信号(进程间通信唯一的异步方式)
下一篇:linux 标准IO缓冲机制探究
相关热门文章
- linux 常见服务端口
- xmanager 2.0 for linux配置
- 【ROOTFS搭建】busybox的httpd...
- openwrt中luci学习笔记
- 什么是shell
- linux dhcp peizhi roc
- 关于Unix文件的软链接
- 求教这个命令什么意思,我是新...
- sed -e "/grep/d" 是什么意思...
- 谁能够帮我解决LINUX 2.6 10...
给主人留下些什么吧!~~
评论热议
0 0
- linux 进程(二) --- 进程的创建及相关api
- linux 进程(二) --- 进程的创建及相关api
- linux 进程(二) --- 进程的创建及相关api
- 进程的创建及相关api
- inux 进程2 --- 进程的创建及相关api
- 进程及相关API
- 进程及相关API
- linux进程创造 - 创建进程API及过程
- Linux - 进程 (二) 进程创建
- 进程相关的API
- Linux C创建守护进程(daemon)及终端相关概念
- 【Linux】守护进程及守护进程的创建
- linux进程的创建及使用
- linux进程(二)进程创建与调度
- 【linux】后台及进程相关
- Linux进程及相关定义
- Linux 进程创建及多进程
- Linux基础学习系列:对于fork()函数的学习,及进程创建相关知识
- Linux网络编程:原始套接字的魔力【上】
- 进程间通信---共享内存
- E: 软件包 atom 需要重新安装,但是我无法找到相应的安装文件。
- Ubuntu安装磁共振处理软件Fsl步骤
- 进程间通信--信号(进程间通信唯一的异步方式)
- linux 进程(二) --- 进程的创建及相关api
- linux 标准IO缓冲机制探究
- 进程间同步---system v ipc 对象信号灯集
- Linux 原始套接字--myping的实现
- u-boot启动完全分析
- ssh框架整合实例
- linux网络编程——套接字(socket)入门
- samba移植到android流程
- boa服务移植到安卓手机
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
儿童氨基酸
氨基酸洁面乳
旁氏氨基酸洗面奶
氨基酸洁面膏
复合氨基酸粉
氨基酸分类
氨基酸厂家
氨基酸分析仪
氨基酸分析仪价格
氨基酸分析
arg氨基酸
农用氨基酸
怎样补充氨基酸
游离氨基酸
氨基酸氧化酶
氨基酸生产厂家
洗面奶氨基酸
氨基酸洗发液
氨基酸软骨素
氨基酸测定
氨基酸皂
疏水氨基酸
氨基酸液肥
蛹虫草氨基酸
氨基酸过敏
氨基酸注射
口服氨基酸
氨基酸衍生物
疏水性氨基酸
氨基酸洗发露
植物氨基酸
氨基酸护肤品
快步氨基酸
氨基酸产品
氨基酸钙片
氨基酸洗发
氨基酸香皂
液体氨基酸
氨基酸颗粒
氨基酸洗面
氨基酸公司