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

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

#include 
#include 
#include 
#include 
#include 

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);
}

示例运行:

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第1张图片

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

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第2张图片
父子进程的逻辑地址是一样的,但物理地址是不一样的;

#include 
#include 
#include 
#include 
#include 

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);
}

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第3张图片

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

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第4张图片
32位系统下的寻址地址空间为2^32 = 4,294,967,296 ~~== 4G;
用户真正能使用的是内核下几个区域,大概为3G,内核占1G;
4G/4k — 物理能有 2^20 个页面,前提是32位系统;

三、fork相关例题

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

#include 
#include 
#include 

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

运行结果为6个:

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第5张图片
运行结果不一定完全一样,但都是输出六个a;
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第6张图片
最终生成4个进程;
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第7张图片

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

#include 
#include 
#include 

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

#include 
#include 
#include 

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

运行结果:

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第8张图片

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

#include 
#include 
#include 

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

运行结果:
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第9张图片
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第10张图片
输出8个a;

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

4、‘||’

#include 
#include 
#include 

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

运行结果为3个;

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第11张图片
注意:父进程的返回值为子进程的pid+1;大于0;
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第12张图片

5、‘&&’

#include 
#include 
#include 

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

运行结果为3个:

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第13张图片
注意:子进程的返回值为0;

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第14张图片

四、僵死进程

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

1、进程实体已经被释放;
2、PID内部的exit_code = 0; //进程的退出码,将PID内部的退出码置零;
3、父进程获取退出码;
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第15张图片

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

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第16张图片

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

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

4、演示僵死进程:

#include 
#include 
#include 
#include 
#include 

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);
}

测试运行:

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第17张图片

注意:

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

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

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

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

#include 
#include 
#include 
#include 
#include 

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);
}

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第18张图片
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第19张图片
注意: 子进程在父进程结束后,子进程的PPID发生改变。有些系统父进程结束后,子进程的ppid为1,这里为1460;当父进程结束后,子进程会变成“孤儿进程”,会被“收养”;

Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第20张图片

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

(2)父进程获取退出码

使用 wait
man wait
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第21张图片
pid_t wait(int *wstatus); //返回值是所获取的子进程的pid,参数是将获取的退出码写入这个整型变量中;

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

#include 
#include 
#include 
#include 
#include 
#include //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);
}

运行结果:
没有僵死进程
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第22张图片
Linux8-fork父子进程逻辑地址相同、进程的逻辑地址与物理地址、fork相关例题、僵死进程_第23张图片
注意:这个先运行子进程,子进程运行完后才运行父进程;
wait需要获取子程序的退出码,只有当子程序运行结束,才能获取退出码,否则会阻塞,所以先运行子进程;

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

你可能感兴趣的:(笔记,Linux学习笔记,c++,c语言,linux)