一.命令行参数
1.什么是命令行参数?
在Linux中,命令行就是我们操作各种功能的指令,eg:ls cd pwd ...等等。不过我们有时候还要在指令的后面添加一些参数,eg:ls -l ls -l -a等等,所以这一整行指令就叫做命令行参数,下面以一个自写一个程序为例子:
2.命令行参数的实现:
#include<stdio.h>
#include<string.h>
int main(int argc,char* argv[]){
if(argc !=2){
printf("请按照选项来输入指令\n");
return 0;
}
printf("argc = %d\n",argc);
if(strcmp(argv[1],"a") == 0){
printf("这是功能1\n");
}
else if(strcmp(argv[1],"b") == 0){
printf("这是功能2\n");
}
else {
printf("该功能不存在\n");
printf("hello world\n");
printf("hello bit\n");
printf("还在等待来发中。。。\n");
}
return 0;
}
3.运行效果图:
4.解释代码:
其中指令中./a.out 包括后面的a 或者b 或者c 都是命令行参数,main函数中的argc的表示命令行参数的个数,例如在上述的程序中,当输入的参数个数超过2时,就会报错退出; argv是一个指针数组,用来指向各个命令行参数,在上面的这个程序中,argv[0] ==./a.out argv[1] == a,除此之外,argv[2] == NULL(就是说,这个指针数组的最后一个元素为NULL,这是规定使然)。
所以当你输入一个指令的时候,argc最少都为1,表示一个命令行参数,根据输入的参数个数自动变换argc的值,这就是命令行参数。
二.虚拟地址空间
1. 代码展示1:
#include<stdio.h>
#include<unistd.h>
int gval = 100;
int main(){
pid_t id = fork();
if(id == 0){
//子进程
while(1){
printf("我是子进程,pid: %d, ppid: %d, gval: %d, gval地址: %p\n",getpid(),getppid(),gval,&gval);
sleep(1);
//gval++;
}
}
else{
//父进程
while(1){
printf("我是父进程,pid: %d, ppid: %d, gval: %d, gval地址: %p\n",getpid(),getppid(),gval,&gval);
sleep(1);
}
}
return 0;
}
代码1运行结果: 
2.代码展示2:
#include<stdio.h>
#include<unistd.h>
int gval = 100;
int main(){
pid_t id = fork();
if(id == 0){
//子进程
while(1){
printf("我是子进程,pid: %d, ppid: %d, gval: %d, gval地址: %p\n",getpid(),getppid(),gval,&gval);
sleep(1);
gval++;
}
}
else{
//父进程
while(1){
printf("我是父进程,pid: %d, ppid: %d, gval: %d, gval地址: %p\n",getpid(),getppid(),gval,&gval);
sleep(1);
}
}
return 0;
}
代码2结果如图:
从上面案例可以看出:对于一个全局变量,父子进程视角中的地址都是相同的,但是父子进程中的gval值却可以不同的,所以为什么同一个地址,不同的进程访问到的值却不同的呢?
解释:
什么是虚拟地址?
其实我们所学的所有的内存地址都是虚拟地址,换句话说,就是我们所有的语言展示出来的可视化的地址都是虚拟地址,就是我们常看到的堆区、栈区、初始化区、未初始化区等等,以32位为例,通俗意义上讲,虚拟地址空间就是4G的大小。每个进程都可以单独拥有自己的一个虚拟地址空间,所以进程的定义就是
进程 == 内核数据结构(task_struct,mm_struct,页表)(也就是虚拟地址空间)+ 代码和数据
虚拟地址空间可以理解成一个数据结构,他不是真正意义上的内存地址空间。
为什么要有虚拟地址空间?
物理内存就是通俗意义上的CPU中的内存,物理内存是有限的,进程的调度要消耗内存,为了提高效率和解决物理地址空间不足的问题,就给每个进程配一个独立的虚拟内存,虚拟地址空间与物理内存之间通过页表来映射。子进程的创建就以父进程为模板,在不发生修改代码和数据的情况下,父子进程的虚拟地址空间和页表是一样的,也就是说,父子进程通过虚拟地址访问到的物理地址中的数据是一样的,这个就能解释代码展示1中的父子进程中的gval值一样的情况。这样就极大的提高了操作系统调度进程的效率。(通俗上的讲,虚拟地址空间就好像是操作系统给进程画的一张超级大的饼,让进程大胆的进行调度和运行,进程以为操作系统只给她一个人的承诺,但其实操作系统给每个进程都这么画,并且各个进程是相互独立的,不知道其他进程的存在,最终沦为操作系统池塘中的一条鱼)
当然了,采用虚拟地址空间的原因有很多,下面是AI出的好处:
什么是写时拷贝?
创建子进程后,子进程的虚拟内核数据结构是以父进程为模板继承而来的,代码和数据也只有只读属性,当子进程修改代码或者数据时,操作系统会在物理内存中开辟一块新的空间,将修改后的数据写到新的空间中,但是子进程中数据的虚拟地址并没有改变,还是和父进程的虚拟地址一样,改变的是物理地址与虚拟地址之间的这种的映射关系,使得看似一模一样的虚拟地址,通过页表映射访问到物理地址空间中的真实的数据却不一样了,这就是写时拷贝。这样似乎就能解释代码展示2中的,子进程中的gval和父进程中的gval值不一样,但是他们展示出来的虚拟地址却是一样的情况了。写时拷贝,也是进程保证独立性的手段。