目录
一、冯诺依曼体系结构
二、操作系统
三、进程概念
1、程序与进程的区别:
2、cpu分时机制
3、pcb——进程控制块
4、进程是什么?
四、进程状态
1、linux状态
2、僵尸态
pid_t fork(void):
fork创建进程之后,父子进程谁先运行?
僵尸进程
孤儿进程
守护进程
五、fork() 创建一个子进程
1、返回值
2、创建子进程再认知
3、创建僵尸进程(滑稽)
4、当子进程成为僵尸进程时,父进程退出了!
5、僵尸进程如何进行处理?
6、孤儿进程
7、孤儿进程退出后会成为僵尸进程嘛?
六、环境变量
1、概念
2、优点
3、命令env
LS_COLOR颜色标记变量
PATH路径变量
4、其他指令
七、环境变量的访问
1、getenv()接口 :获取用来获取环境变量的值
2、main函数参数
打印argv运行参数:
打印env环境变量:
3、全局变量 extern char **environ;
一、冯诺依曼体系结构
由冯.诺依曼提出来的关于计算机硬件体系结构:
输入设备:键盘
输出设备:显示器
存储器:内存
运算器&控制器:中央处理器-CPU
CPU想要处理数据,是从内存中取出数据进行处理的,--CPU想要执行一个程序,第一件事就是先把程序从硬盘加载到内存中。
二、操作系统
本质:就是一个软件程序
功能:控制和管理计算机上的软硬件资源
目的:使计算机更加好久
完整的操作系统:内核+外部应用
我们所说的LInux就是Linux内核
计算机大体应用层次:用户->应用软件程序->系统调用接口->(操作系统)->驱动程序->硬件
系统调用接口:操作系统内核向上提供的用于访问内核指定功能的接口
库函数: 就是对系统调用接口的进一步封装
库函数就是为了让这些系统调用接口在用户或者说程序员,让他们使用的时候更加容易
三、进程概念
1、程序与进程的区别:
程序是一堆指令集+数据 躺尸在硬盘上
而进程是运行中的程序
运行中的程序有很多,cpu应该先处理哪一个呢?
2、cpu分时机制
由操作系统进行控制管理,一个程序只在cpu上运行很短一段时间(一个时间片),然后切换到下一个程序,cpu处理每一个程序只会有一个时间片的时间,时间片运行完了就切换到下一个。
那如果是这样进行时间片轮转运行的,那么同一个程序cpu如何知道这个程序上次运行的情况呢?
3、pcb——进程控制块
操作系统需要对一个程序的运行状态进行描述(比如上次时间片轮转处理到哪个数据了)都要把cpu寄存器上的数据给保存下来,等到下次重新切换回来的时候,在把这些数据重新加载到寄存器上。
而这个对程序运行过程的描述,就叫做pcb——进程控制块,在linux下是一个task_struct结构体
操作系统调度管理程序运行就是通过pcb来实现的,那么之前的cpu分时机制也就可以正常运行了。
4、进程是什么?
进程是运行中的程序,这样回答很片面,因为这是站在用户的角度进行的描述。
在操作系统角度,进程就是系统对运行中程序动态运行过程的描述-PCB(进程控制快)
在linux下是一个task_struct结构体,系统通过这个描述实现对程序运行的管理及调度
四、进程状态
时间片:系统中cpu分时机制,让每一个程序只在cpu上执行一段很短的时间(时间片)
每一个运行中的程序,都应该有一个状态,该状态标记了一个进程该如何被系统进行调度运行
命令:ps -aux | grep loop :
ps -aux 是查看所有进程信息, grep是进行字符串匹配,
|管道符连接俩个命令(将前面命令结果交给后面来处理)
1、linux状态
前台进程&后台进程
前台进程就是指占据了一个终端的进程;
后台进程就是没有关联的进程,默默运行在系统中
下面的状态符号中 R+就表示前台运行态程序
①、运行态--R 正在被执行,以及拿到时间片就能执行的一种状态
②、可中断休眠态--S,一种阻塞态(因为某种运行条件不满足,而暂时不能被调度运行的进程状态,比如sleep(3))
③、不可中断休眠态--D:无法被中断打断阻塞,只能等待阻塞的唤醒条件满足后才能被调度执行
④、停止态--T:什么都不做(这跟休眠不一样,休眠是阻塞,停止还会被调度)
小明是个植物人,还活着只是什么都不能做
⑤ 僵尸态
2、僵尸态
进程退出了,但是资源没有完全被释放,等待处理的一种状态。
僵尸进程:处于僵尸态的进程,是一种退出了,但是资源没有完全被释放的进程
子进程先于父进程退出,然而父进程没有接收到子进程的返回值,所以子进程的资源不能完全释放。
pid_t fork(void):
通过复制调用进程(父进程)来创建一个新的进程(子进程)
返回值:在父进程中返回值是子进程的PID(大于0的值);在子进程中返回0,失败返回-1
站在系统角度进程就是pcb,在linux下是一个task_struct结构体
创建了一个进程出来,这个进程叫做子进程,它复制了父进程,里面的大部分数据都是从父进程pcb中复制过来的(内存指针、上下文数据……)
fork创建进程之后,父子进程谁先运行?
答案:不一定,没有固定顺序,因为进程就是pcb,是系统用于进行程序运行调度管理的描述,相当于系统调度到谁就运行谁
僵尸进程
僵尸进程:子进程先于父进程退出,而父进程没有接收到子进程的返回值,导致子进程无法完全释放资源从而成为僵尸进程
危害:资源泄露(资源没有完全释放)
僵尸进程的避免:进程等待(等待子进程退出,获取退出子进程的返回值,释放子进程的资源,避免产生僵尸进程)
孤儿进程
产生:父进程先于子进程退出,子进程成为孤儿进程
特性:运行在后台,父进程成为1号进程(init进程)
孤儿进程退出后不会成为僵尸进程
孤儿进程没有危害,他会被init进程给接收
守护进程
一种特殊的孤儿进程
长期运行在后台,不跟任何终端控制相关联
一般操作系统启动时候就启动,关闭的时候就关闭
五、fork() 创建一个子进程
fork函数通过系统调用结构创建了一个与原来进程几乎相同的进程(这个子进程pcb中信息和父进程几乎一致,包括内存指针、上下文数据……),两个进程可以做相同的事儿。
1、返回值
可以利用返回值不同来判断父子进程,进行代码分流,进入不同的分支
在父进程中返回子进程的pid(一个大于0的值)
在子进程中返回0
创建失败返回-1
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main(){
5 pid_t fpid;
6 int count = 0;
7 fpid = fork();
8 if(fpid<0)
9 printf("error in fork");
10 else if(fpid == 0)
11 {
12 printf("i am the child process, my process id is %d", getpid());
13 printf("我是儿子\n");
14 count++;
15 }
16 else
17 {
18 printf("i am the parent process, my process id is %d", getpid());
19 printf("我是爸爸\n");
20 count++;
21 }
22 printf("统计结果是: %d\n", count);
23 return 0;
24 }
运行结果:
[dev@localhost fork]$ ./fork5
i am the parent process, my process id is 16866我是爸爸
统计结果是: 1
i am the child process, my process id is 16867我是儿子
统计结果是: 1
分析:
使用fork创建子进程,创建出一个pcb,里面存储的信息和父进程pcb中的信息几乎一致,保留的父进程运行的上下文数据、内存指针……
当创建子进程后,子进程走fpid==0的分支打印我是儿子,打印count为1
父进程走fpid>0的分支,打印我是爸爸,打印count为1
2、创建子进程再认知
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
7 printf("hello world\n");
8 pid_t ret = fork();
9 printf("it's over!\n");
10 return 0;
11 }
运行结果:
[dev@localhost fork]$ ./fork1
hello world
it's over!
it's over!
分析
hello world运行一次,而over运行了俩次
当创建子进程之后,子进程pcb中的信息与父进程中的信息几乎一致,(内存指针、上下文数据……)父进程运行到第9行,子进程也保留父进程的运行上下文信息,也运行到第9行,所以父进程与子进程都打印了over代码。
3、创建僵尸进程(滑稽)
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7 pid_t ret = fork();
8 if(ret < 0){
9 printf("error fork\n");
10 }else if(ret == 0){
11 printf("i am child\n");
12 exit(0);// 子进程退出
13 }
14 else{
15 printf("i am parent\n");
16 }
17 while(1) // 能走到这里的只有父进程
18 {
19 sleep(3);
20 }
21 return 0;
22 }
上面代码在子进程分支中进行退出,产生僵尸进程,使用下方代码进行查看信息
ps -efl | head -n 1 && ps -efl | grep fork1
PID 表示进程,PPID的值为父进程的PID,即第二行PID为18126的父进程为18125也就是第一行进程。由于子进程18126先于父进程退出,所以子进程状态为僵尸状态(Z)
列序号 列含义 列含义说明
1 UID 用户标识ID
2 PID 进程ID
3. PPID 父进程ID
4 C CPU占用率
5 STIME 进程开始时间
6 TTY 启动此进程的TTY(终端设备)
7 TIME 此进程运行的总时间
8 CMD 完整的命令名(带启动参数)
ps命令有一些参数:
-e : 显示所有进程
-f : 全格式
-h : 不显示标题
-l : 长格式
-w : 宽输出
a :显示终端上的所有进程,包括其他用户的进程。
r :只显示正在运行的进程。
u :以用户为主的格式来显示程序状况。
x :显示所有程序,不以终端机来区分。
4、当子进程成为僵尸进程时,父进程退出了!
那么这个子进程还存在吗?
使用kill命令杀死父进程18376
程序直接终止了,如果父进程退出了,子进程存在的意义也就没有了。
(子进程就是来给父进程服务的)
5、僵尸进程如何进行处理?
由上一个问题可以知道可以通过kill父进程来关闭僵尸子进程
进行进程等待
6、孤儿进程
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4
5 int main()
6 {
7 printf("hello \n");
8 pid_t ret = fork();
9 if(ret < 0){
10 printf("create error\n");
11 }else if(ret == 0)
12 {
13 printf("i am child\n");
14 }
15 else
16 {
17 // 父进程先于子进程退出成为孤儿进程
18 printf("i am parent\n");
19 exit(0);
20 }
21 while(1)
22 {
23 sleep(3);
24 printf("i am %d\n",ret);
25 }
26 return 0;
27 }
由下图可见父进程退出后,原来子进程的PPID变成了1号进程,即孤儿18591被1号进程所收养
下图中进程11971为子进程(一般子进程PID大于父进程PID)
它在第一次查看信息时(还没有关闭父进程)显示是S+(S+表示是一个前台进程)
在第二次查看信息时(关闭父进程后)显示是S(成为了一个后台进程)后台进程的样子就是下方这个状态,不停地在打印 我是子进程这句话
7、孤儿进程退出后会成为僵尸进程嘛?
不会的,僵尸进程是因为在子进程退出时父进程没有接收子进程的返回值,而成为孤儿进程之后,子进程对应的父进程会变为1号进程init,1号进程是个特别负责任的父亲,如果孤儿进程退出了,1号进程会立即关闭它。所以孤儿进程退出不会成为僵尸进程。
六、环境变量
1、概念
所谓变量,就是保存着一个数据,环境变量:保存着当前运行环境参数的变量
2、优点
配置之后即时生效
让运行环境配置更加灵活
通过环境变量可以给运行中的程序传递数据
3、命令env
在终端使用env命令查看当前环境变量
LS_COLOR颜色标记变量
由于环境变量比较多,介绍几个了解就行,下面这个LS_COLORS变量,存储这关于ls命令查看文件颜色的一种变量(颜色标记)什么样的后缀名应该用什么样的颜色来显示
PATH路径变量
在我们平常一段代码写好,编译之后进行运行的时候,都是在终端上敲入一段路径,比如我在当前路径下touch一个test文件,运行的时候输入 ./test 然而在我们之前的Linux指令中,却不需要这么多表示路径的操作,比如一个ls文件我在任何目录下都可以进行打开,然后查看当前目录文件操作,这就是PATH环境变量的作用,它使得其内部的文件在任何目录下都可以进行运行,PATH变量告诉编译器它里面存储的文件的文件检索地址,所以不需要通过人为的告诉编译器这个文件在哪里。
4、其他指令
echo ${valuename} 打印指定内容(可以是变量内容)到终端显示
export 声明一个变量为环境变量
set 查看所有变量
unset 删除一个变量
七、环境变量的访问
1、getenv()接口 :获取用来获取环境变量的值
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int main() 5 { 6 char *ptr = getenv("myvalue"); 7 { 8 if(ptr != NULL) 9 printf("%s\n",ptr); 10 } 11 return 0; 12 }
!gcc 重新编译上份代码
2、main函数参数
通常的main函数书写都为 int main()
其实它也是有参数的
int main(int argc, char* argv[], char* env[])
argc 保存参数个数,argv保存参数名,env参数保存着环境变量
打印argv运行参数:
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main(int argc, char* argv[])
5 {
6 for(int i = 0; i < argc; i++)
7 {
8 printf("argv[%d]:%s\n", i, argv[i]);
9 }
10 return 0;
11 }
打印env环境变量:
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main(int argc, char* argv[], char* env[])
5 {
6 for(int i = 0; i < argc; i++)
7 {
8 printf("argv[%d]:%s\n", i, argv[i]);
9 }
10 for(int i = 0; env[i] != NULL; i++)
11 {
12 printf("env[%d]:%s\n",i, env[i]);
13 }
21 return 0;
22 }
除了上方的运行参数,还有所有的全局变量
3、全局变量 extern char **environ;
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main()
5 {
6 extern char**environ;
7 for(int i = 0; environ[i] != NULL; i++)
8 {
9 printf("environ[%d]:%s\n",i, environ[i]);
10 }
11 return 0;
12 }