Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程

一、fork父子进程逻辑地址相同

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main()
{
    char *s=NULL;
    int n=0;//控制父子进程的次数

    //调用fork
    pid_t id=fork();
    assert(id != -1);//fork的返回值,-1表示出错,所以加断言

    if(id == 0)//子进程
    {
        s="child";
        n=3;
    }
    else//父进程
    {
        s="parent";
        n=7;;
    }
    
    //父子进程一起执行
    int i=0;
    for(;i<n;i++)
    {
    	//n的地址=%d  --- 会警告,但能运行 --- 因为将地址转成整形 可用%p,输出地址
    	//注意,在进程中,看到的是进程的逻辑地址,所以父进程和子进程地址显示一样
        printf("s=%s,pid=%d,ppid=%d,n的地址=%d\n",s,getpid(),getppid(),&n);
        sleep(1);

    }
    exit(0);
}

示例运行:

在这里插入图片描述

我们在进程中看到的地址都是逻辑地址;

在这里插入图片描述
父子进程的逻辑地址是一样的,但物理地址是不一样的;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main()
{
    char *s=NULL;
    int n=0;//控制父子进程的次数

    //调用fork
    pid_t id=fork();
    assert(id != -1);//fork的返回值,-1表示出错,所以加断言

    if(id == 0)//子进程
    {
        s="child";
        n=3;
    }
    else//父进程
    {
        s="parent";
        n=7;;
    }
    
    //父子进程一起执行
    int i=0;
    for(;i<n;i++)
    {
    	//n的地址=%d  --- 会警告,但能运行 --- 因为将地址转成整形 可用%p,输出地址
    	//注意,在进程中,看到的是进程的逻辑地址,所以父进程和子进程地址显示一样
        printf("s=%s,pid=%d,ppid=%d,n的地址=%d\n,n的值=%d",s,getpid(),getppid(),&n,n);
        sleep(1);

    }
    exit(0);
}

在这里插入图片描述

二、进程的逻辑地址与物理地址

在这里插入图片描述
32位系统下的寻址地址空间为2^32 = 4,294,967,296 ~~== 4G;
用户真正能使用的是内核下几个区域,大概为3G,内核占1G;
4G/4k — 物理能有 2^20 个页面,前提是32位系统;

三、fork相关例题

1、打印了几个a?产生了几个进程?

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int i=0;
    for(;i<2;i++)
    {
        fork();
        printf("a\n");
    }
    exit(0);
}

运行结果为6个:

在这里插入图片描述
运行结果不一定完全一样,但都是输出六个a;
在这里插入图片描述
最终生成4个进程;
在这里插入图片描述

2、没有’\n’,先printf,在fork

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("a\n");
    fork();
    
    exit(0);
}

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    printf("a");
    fork();
    
    exit(0);
}

运行结果:

在这里插入图片描述

3、没有’\n’,循环fork

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    int i=0;
    for(;i<2;i++)
    {
        fork();
        printf("a");
    }
    exit(0);
}

运行结果:
在这里插入图片描述
在这里插入图片描述
输出8个a;

因为没有\n,所以第一次fork(),打印一个a,但没有打印出来,在缓冲区里;
所以后面fork(),会将缓冲区内本来就有的a保留。

4、‘||’

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    fork() || fork();
    printf("a\n");
    
    exit(0);
}

运行结果为3个;

在这里插入图片描述
注意:父进程的返回值为子进程的pid+1;大于0;
在这里插入图片描述

5、‘&&’

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
    fork() && fork();
    printf("a\n");
    
    exit(0);
}

运行结果为3个:

在这里插入图片描述
注意:子进程的返回值为0;

在这里插入图片描述

四、僵死进程

1、正常释放结束子进程:

1、进程实体已经被释放;
2、PID内部的exit_code = 0; //进程的退出码,将PID内部的退出码置零;
3、父进程获取退出码;
在这里插入图片描述

2、非正常流程、僵死状态:没有获取退出码

在这里插入图片描述

3、僵死进程产生的原因或者条件:

当子进程先于父进程结束,父进程没有获取子进程的退出码,此时子进程变成僵死进程
;

4、演示僵死进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main()
{
    char *s=NULL;
    int n=0;//控制父子进程的次数

    //调用fork
    pid_t id=fork();
    assert(id != -1);//fork的返回值,-1表示出错,所以加断言

    if(id == 0)//子进程
    {
        s="child";
        n=3;
    }
    else//父进程
    {
        s="parent";
        n=7;
    }
    
    //父子进程一起执行
    int i=0;
    for(;i<n;i++)
    {
        printf("s=%s,pid=%d,ppid=%d\n",s,getpid(),getppid());
        sleep(1);

    }
    exit(0);
}

测试运行:

在这里插入图片描述

注意:

./main & — //main为进程名字,'&'表示将进程放在后台运行;
要将进程放在后台运行;这样才能运行ps查看;否则ps不能在进程运行的时候同时运行查看。
ps — 默认显示与当前终端有关的进程信息;
ps -e — 显示系统中所有的进程信息;
ps -f — 显示更多的进程信息;
(子进程结束而父进程未结束的时候才有僵死进程)

5、如何处理、预防僵死进程:

(1)父进程先于子进程结束

比如子进程的n改为7,父进程的n改为3;

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main()
{
    char *s=NULL;
    int n=0;//控制父子进程的次数

    //调用fork
    pid_t id=fork();
    assert(id != -1);//fork的返回值,-1表示出错,所以加断言

    if(id == 0)//子进程
    {
        s="child";
        n=7;
    }
    else//父进程
    {
        s="parent";
        n=3;
    }
    
    //父子进程一起执行
    int i=0;
    for(;i<n;i++)
    {
        printf("s=%s,pid=%d,ppid=%d\n",s,getpid(),getppid());
        sleep(1);

    }
    exit(0);
}

在这里插入图片描述
在这里插入图片描述
注意: 子进程在父进程结束后,子进程的PPID发生改变。有些系统父进程结束后,子进程的ppid为1,这里为1460;当父进程结束后,子进程会变成“孤儿进程”,会被“收养”;

在这里插入图片描述

父进程先于子进程结束,子进程就会被收养,新的"父进程"就会获取退出码;(调用wait实现);

(2)父进程获取退出码

使用 wait
man wait
在这里插入图片描述
pid_t wait(int *wstatus); //返回值是所获取的子进程的pid,参数是将获取的退出码写入这个整型变量中;

    else//父进程 
	{ 
		s="parent"; 
		n=7; 
		int val;
 		wait(&val); 
 		printf("val=%d\n",val);  
	 }

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/wait.h>//wait的头文件

int main()
{
    char *s=NULL;
    int n=0;//控制父子进程的次数

    //调用fork
    pid_t id=fork();
    assert(id != -1);//fork的返回值,-1表示出错,所以加断言

    if(id == 0)//子进程
    {
        s="child";
        n=3;
    }
    /*else//父进程
    {
        s="parent";
        n=7;
    }*/
    else//父进程 
	{ 
		s="parent"; 
		n=7; 
		int val;
 		wait(&val); 
 		printf("val=%d\n",val);  
	 }
    
    //父子进程一起执行
    int i=0;
    for(;i<n;i++)
    {
        printf("s=%s,pid=%d,ppid=%d\n",s,getpid(),getppid());
        sleep(1);

    }
    exit(0);
}

运行结果:
没有僵死进程
在这里插入图片描述
在这里插入图片描述
注意:这个先运行子进程,子进程运行完后才运行父进程;
wait需要获取子程序的退出码,只有当子程序运行结束,才能获取退出码,否则会阻塞,所以先运行子进程;

总结:
其实两种处理僵死进程的方法本质都是一样的,都调用了wait,获取退出码。
但是两种方法又有区别,就是父进程调用wait会阻塞,等子进程执行完之后,父进程才会执行。
一般我们父进程调用wait是配合信号使用的。目前暂时理解为说明父进程调用wait可以避免僵死进程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值