进程篇上之初识进程(2)

三、进程标识符

1、获取当前进程的pid

(1)getpid函数
返回值:无符号整形或者长整形
(2)编写程序进行查看
首先编写makefile:
my_pid:my_pid.c
gcc -o my_pid my_pid.c


.PHONY:clean(依赖关系暂时为空)
rm -f my_pid

主程序:

#include<stdio.h>
#include<sys/ioctl.h>
#include<unistd.h>
	
int main()
{
	printf("my_pid: %d\n",getpid());
	return 0;
}

执行代码之后,便可以看到自己进程的pid
每次运行的时候pid都是不同的,和操作系统分配pid的算法相关
2、获取父进程的pid

(1)使用getppid,父父进程是没有的

(2)编写程序

int main()
{
	printf("my_pid: %d\n",getpid());
	printf("father_pid: %d\n",getppid());
	sleep(50);
	return 0;
}


在打开一个终端,假如2839是父进程(父进程不会亲力亲为的去做,而是创建一个父进程去做)
父进程的父进程都是bash,直接和bash打交道,bash也是创建一个子进程代替它去完成任务

四 、进程的地址空间

操作系统将程序加载到内存中,PCB放到调度队列中,从队列中选择进程

1、地址空间内存布局

(1)堆和栈的生长方向,
对于堆来讲,生长方向是向上的(向着内存地址增加的方向);
对于栈来讲,生长方式是向下的(向着内存地址减小的方向)。
(2)数组元素的地址
定义一个a[10]的数组,a[9]的地址高于a[0](因为遍历数组的时候可以用指针的方式,指针是++的,地址当然也就是++,由此往后找的时候地址也就是增加的,整体的地址是往上生长,但是在数组内部是往下生长的,地址最低的元素是a[9]
(3)结构体:(不同数据类型的集合)
访问结构体的任意一个元素(或者说是其中的一个变量),依次定义出a、b、c三个变量,那么c的地址是最高的,因为对于结构体,访问规则和数组相同,在不使用元素名的时候,可以使用偏移量
思考:
如果有一个结构体类型,知道任意一个元素的地址,那么怎么知道该结构体的首地址
上面一道题,涉及到对内存的理解(推荐看操作系统的内部寻址的相关知识)

2、编写函数来验证

(1)
#include<stdio.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<stdlib.h>
int g_a=100;
static g_b;
int g_c[200];


int main()
{
        char* mem0=(char*)malloc(8);
        char* mem1=(char*)malloc(8);
        char* mem2=(char*)malloc(8);
        int d[20];
        printf("stack_addr:d[0]->%p d[1]->%p\n",&d[0],&d[19]);
        printf("heap:mem0->%p mem1->%p mem2->%p\n",mem0,mem1,mem2);
        printf("init_addr:g_a->%p g_b->%p\n",&g_a,&g_b);
        printf("umint_addr:g_c[0]->%p g_c[20]->%p\n",&g_c[0],&g_c[20]);
        printf("code_addr:->%p\n",main);
        free(mem0);
        free(mem1);
        free(mem2);
        return 0;
}

3、结果总结

(1)可以看出stack的地址最大,code的地址最小,heap的地址次大,第二小的是初始化的全局变量
(2)符合地址空间的分配

五、C语言程序员在程序运行过程中将变量定义在那个地方

1、都是在内存中

2、验证观点正确与否

(1)在linux下,进程可以派生子进程
使用fork,创建一个子进程
fork当前的进程,会有两个返回值。在父进程中调用一次,在父进程和子进程中各返回一次。
给父进程返回子进程的PID,给子进程返回0
(2)为什么说系统中多了一个进程?怎么知道系统中有多少个进程?
一个进程只有一个PCD,有多少个进程就有多少个PCD,这些PCD是被组织起来。多了一个PCD之后,当然就多了一个进程
(3)为什么两个的返回值不相同?
一个父亲,三个小孩A,B,C,父亲想把小C叫过来,这个时候不能仅仅喊儿子,儿子喊父亲,不是直接叫名字,喊的是父亲(一个标识符),一个父亲可以有多个子女,任何一个人只有一个父亲,一个有直接血缘关系的父亲(一对多的关系),
所以返回给父亲一个名字,子进程知道谁是它爹,容易准确找到,关心的是是否创建成功
(4)一种数据结构有这个性质
树形结构,只有一个父节点,所以可以用路径表示linux的结构(路径唯一)
(5)两个执行流
frok两个执行流,谁先运行,谁的优先级高谁先运行,调度出来的谁先运行不一定,调度器与调度算法相关,但是谁先

退出是一定的

六、fork函数

1、代码验证

int main()
{
        pid_t id=fork();
        if(id<0)
        {
                printf("fork error");
                return 1;
        }
        else if(id==0)
        {
                printf("child: pid:->%d ppid:->%d\n",getpid(),getppid());
        }
        else
        {
                sleep(2);
                printf("father: pid:->%d ppid:->%d\n",getpid(),getppid());
        }
        return 0;
}

2、有两个执行流,父进程代码的分流

分析:father的ppid:父进程先跑起来肯定为bash

3、分流后的数据

(1)代码一:

int g_val=200;


int main()
{
        pid_t id=fork();
        if(id<0)
        {
                printf("fork error");
                return 1;
        }
        else if(id==0)
        {
                printf("child: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("chile: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        else
        {
                sleep(2);
                printf("father: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("father: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        return 0;
}

父子进程的g_val是一样的,
(2)定义为静态
static int g_val=200;


int main()
{
        pid_t id=fork();
        if(id<0)
        {
                printf("fork error");
                return 1;
        }
        else if(id==0)
        {
                printf("child: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("chile: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        else
        {
                sleep(2);
                printf("father: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("father: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        return 0;
}

父子进程的g_val是一样的
(3)代码三
定义在main函数内部

int main()
{
int g_val=200;
        pid_t id=fork();
        if(id<0)
        {
                printf("fork error");
                return 1;
        }
        else if(id==0)
        {
                printf("child: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("chile: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        else
        {
                sleep(2);
                printf("father: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("father: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        return 0;
}


父子进程的g_val是一样的
(4)代码四

int main()
{
<span style="white-space:pre">	</span>int g_val=100;
        pid_t id=fork();
        if(id<0)
        {
                printf("fork error");
                return 1;
        }
        else if(id==0)
        {
g_val=200;
                printf("child: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("chile: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        else
        {
                sleep(2);
                printf("father: pid:->%d ppid:->%d\n",getpid(),getppid());
                printf("father: g_val_addr:->%p g_val:->%d\n",&g_val,g_val);
        }
        return 0;
}


一个是全局一个是局部的,这个时候,数据的值是不一样的,但是数据的地址是一样的

4、为什么地址一样,数据不一样?

一个内存,地址是统一,统一的地址打印出来的值一样(这个指的是物理地址,指向同一个内存区域,这里不是内存地址)
刚才打印出来的地址,不是物理地址,而是地址空间,用C语言(或者linux下)打印出来的是虚拟地址或者线性地址,
虚拟地址相同,存在虚拟地址到物理地址的转换(虚拟地址映射到不同的物理地址)

5、为什么有地址空间?

地址空间描述的进程描述的活动空间,活动空间是一片区域,并不是代表进程拥有这片空间,

6、通过什么支持地址转换?

页表(建立虚拟地址到物理地址的映射的表,有二级页表和三级页表(64位平台下))(是一个数据结构)、MMU(硬件)

7、地址空间有两个

地址空间是有两个的,这两个地址空间中相同变量的地址相同,这两个变量(父子进程的)通过不同的页表和MMU映射到的物理内存不同

8、整个映射有操作系统和底层来实现(软件给屏蔽了)

9、为什么要这样实现?

(对此想详细了解:深入理解计算机系统(虚拟空间一章))
(1)方便进程之间的隔离
(2)安全(物理内存有些地方坏了,可以成功映射的才是自己的,不能成功映射的不是自己的,
软件层保证了安全,用户无法直接写内存,更加安全)




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值