三、进程标识符
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:父进程先跑起来肯定为bash3、分流后的数据
(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)安全(物理内存有些地方坏了,可以成功映射的才是自己的,不能成功映射的不是自己的,
软件层保证了安全,用户无法直接写内存,更加安全)