在C语言阶段,我们知道main函数其实是可以带有参数的,只是我们在实际写main函数的时候并没有带上对应的参数,那么实际上的main原型应该是这样子的.
int main(int argc,char * argv[]);那么有的uu会好奇,这个指针数组是用来干啥的,我们来看下面这段代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
for(int i = 0; i < argc; i++)
{
printf("argv[%d]->%s\n",i,argv[i]);
}
}

那么有的uu会好奇,这个指针数组是以什么结尾的呢?其实是NULL,我们来验证一下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
for(int i = 0; argv[i]; i++)
{
printf("argv[%d]->%s\n",i,argv[i]);
}
}

有的uu会好奇,为什么要有命令行参数呢,我们来看下面这段代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
int main(int argc,char *argv[])
{
if(argc != 2)
{
printf("Usage:%s -[a,b,c,d]\n",argv[0]);
return 1;
}
else if(strcmp(argv[1],"a") == 0)
{
printf("This is Function 1\n");
}
else if(strcmp(argv[1],"b") == 0)
{
printf("This is Function 2\n");
}
else if(strcmp(argv[1],"c") == 0)
{
printf("This is Function 3\n");
}
else if(strcmp(argv[1],"d") == 0)
{
printf("This is Function 4\n");
}
else
{
printf("No This Function\n");
}
return 0;
}
我们可以看到,当输入不同的命令行参数时,会执行不同的功能,所以命令行参数的本质是

我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
printf("hello world\n");
}
我们可以看到,当执行自己的可执行程序时一定要带路径,那么这是为什么呢
那么有没有什么办法可以通过不带路径去执行自己的可执行程序呢,其实是有的.


当我们将自己可执行程序所在的路径导入到环境变量中,此时再去执行自己的可执行程序就不用再带路径了.并且我们也可以看到,当查看环境变量PATH以后,此时也多了我们自己的可执行程序的路径.


但是,当我们重启Xshell以后,会发现此时PATH路径又重新回来了,那么这是为什么呢



有的uu会比较好奇,为什么这些路径是以:为分隔符呢






每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以'\0'结尾的环境字符串.并且结尾以NULL结束
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[],char * env[])
{
for(int i = 0;env[i]; i++)
{
printf("%s\n", env[i]);
}
return 0;
}
#include <stdio.h>
int main(int argc, char *argv[])
{
//libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明
extern char **environ;
int i = 0;
for(; environ[i]; i++)
{
printf("%s\n", environ[i]);
}
return 0;
}
我们可以看到,这些环境变量,就是刚刚Shell内部的环境变量,而我们通过代码的方式获取到了,说明环境变量是默认可以被子进程拿到的(在命令行执行的可执行程序都是bash的子进程).

#include <stdio.h>
#include <stdlib.h>
int main()
{
char * env = getenv("MYENV");
if(env){
printf("%s\n", env);
return 0;
}
我们可以发现,当将MYENV环境变量导入以后,此时再运行程序就有了,说明环境变量是可以被子进程继承下去的.
有的uu就会好奇,export也是条命令,那么在使用的时候为什么不会创建子进程呢?因为如果创建的了子进程,那么导入的环境变量就不应该被bash看到,像echo export等等都是内建命令,这是由bash亲自来执行的,而百分之80的命令都是由bash创建子进程来执行的,像ps这些.

在C语言阶段,相信大家见过下面的空间布局图,可是uu们对这个并不了解,下面我们先通过一段代码来感受一下.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int global_value = 100;
int main()
{
printf("Parent process is running and my pid = %d,ppid%d\n",getpid(),getppid());
pid_t id = fork();
//返回0则表示的是子进程
if(id == 0)
{
//child
int count = 0;
while(1)
{
printf("I am child process and my pid = %d,ppid = %d,global_value = %d,&global_value = %p\n",getpid(),getppid(),global_value,&global_value);
sleep(1);
count++;
if(5 == count)
{
global_value = 300;
printf("I am child process,change %d--->%d\n",100,global_value);
}
}
}
else
{
//parent
while(1)
{
printf("I am parent process and my pid = %d,ppid = %d,global_value = %d,&global_value = %p\n",getpid(),getppid(),global_value,&global_value);
sleep(1);
}
}
return 0;
}
我们可以清晰地看到,在global_value修改前,父子进程输出来的变量值和地址是一模一样的,这个很好理解,因为子进程会以父进程为模板,父子并没有对变量进行任何修改. 但是当子进程对global_value进行修改时,我们会发现输出的地址是一样的,但是变量内容不一样,那么可以得出以下结论:


那么我们再回到刚刚的代码1

在上面我们有提到页表
在磁盘中存在一个区域叫做swap分区,这个swap分区呢,是在当进程变换位挂起状态时,此时将其对应的在物理内存中的代码 + 数据唤入到磁盘当中的swap分区,然后将其在页表对应的标记位标记为0,将物理地址消除,虚拟地址保留.
char * str = "hello world";
*str = 'H';上面这段代码在运行的时候,很明显会崩溃的,在C语言阶段博主给出的答案是字符常量区的变量不能被修改,那么这是为什么呢



在之前博主有讲到系统的fork函数,他能够帮助我们创建子进程,而且返回值是两个不同的id,但是那时候博主并没有讲原因,这里其实是因为发生了写时拷贝.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
while (1)
{
printf("I am child,my id == %d,%p\n",id,&id);
sleep(1);
}
}
else if(id > 0)
{
while (1)
{
printf("I am Parent,my id == %d,%p\n",id,&id);
sleep(1);
}
}
return 0;
}
我们可以看到上面两个id的虚拟地址是一样的,只是值不一样,因为return的本质是对id进行写入,fork函数能够返回两个不一样的id其本质就是因为发生了写时拷贝.
这里首先就要讲解划分区域,我们通过一个小故事来理解划分区域,相信uu们在念书的时候,总会遇到一些比较离谱的同桌,那么这时候呢,就会和同桌划分区域,一开始呢,两人商量好了,假设桌子是100cm长,两人是一人一半,然后呢你那同桌觉得这面积有些窄,想占你便宜,说自个儿要占70cm,然后呢你气不过,觉得对方得寸进尺,这时候也不跟对方客气了,直接就是自己占70cm 那么用计算机语言来描述就是.
//区域划分
struct area
{
int start;
int end;
}
//桌子
struct desktop
{
struct area left;
struct area right;
}
//最初的一人一半
struct desktop d;
d.left.start = 1;
d.left.end = 50;
d.right.start = 50;
d.right.end = 100;
//调整区域
d.left.end += 20;
d.right.start += 20;那么结合上面的小故事,地址空间的本质其实就是一个struct结构体!内部的很多属性是start,end的范围.






上图是Linux2.6内核中进程队列的数据结构
在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法!


