今天是中秋节,祝各位小伙伴中秋节快乐,记得吃月饼吖
图片来自网络,侵联删
在Linux中进程信息被保存在task_struct(PCB)
ps ps aux | greap myproc ls /proc
fork():创建一个子进程
#include<iostream> #include<unistd.h> using namespace std; int main() { fork(); while(1) { printf("hehe\n"); } return 0; }
fork之前的代码,被父进程执行,fork之后的代码,父子都可以执行
fork之后,父子进程代码共享
fork之后,父子进行那个先运行不确定,取决于操作系统调度算法
fork函数会有两次返回值,给父进程返回子进程pid,给子进程返回0
6 pid_t id=fork(); 7 if(id==0) 8 { 9 while(1) 10 { 11 printf("我是子进程\n"); 12 sleep(1); 13 } 14 } 15 else if(id>0) 16 { 17 while(1) 18 { 19 printf("父进程\n"); 20 sleep(2); 21 } 22 } 23 else 24 { 25 printf("进程创建失败\n"); 26 } 27 return 0
结果:父子进程同时执行
进程状态------>数据化-------->进程数据被保存到tack_struct中
进程主要有以下几种状态:
R状态:
可以同时存在多个R状态的进程
R状态的进程不一定是正在运行的,表示随时可以调用该进程
系统中所有处于R状态的进程都会被连接起来形成调度队列(run_queue)
S状态:休眠状态(浅度睡眠)通常用来等待某种事件发生,随时可以被唤醒,也可以被杀掉
//休眠状态 #include<stdio.h> #include<unistd.h> int main() { printf("I am Running\n"); sleep(10000000); printf("Ending\n"); return 0; }
D状态:深度睡眠状态,D状态没有办法模拟,表示该进程不会被杀掉,即便是操作系统,除非重启杀掉,或者主动醒来
T状态:将进程进行暂停
如上图所示,摁下19sigstop即可暂停进程
X状态:死亡进程
Z状态:僵尸状态
进程退出,在操作系统层面,曾经申请的资源,并不是被立即释放,而是要暂存一段事件,供OS(父进程)进行读取,,而父进程没有读取,叫做僵尸状态
为什么要有僵尸进程:
进程创建的目的:完成某种工作
当任务完成的时候,调用方应该指导任务完成得怎么样
(除非不关心)
sjw@iZ2zedu4njy79sqivntvprZ test_9_11]$ cat test.c #include<stdio.h> #include<unistd.h> int main() { printf("I am Running\n"); sleep(10000000); printf("Ending\n"); return 20; } [sjw@iZ2zedu4njy79sqivntvprZ test_9_11]$ echo $?//查看进程码,查看最近一次进程退出时得进程码(如返回值return 0等) //进程退出时,进程信息(退出码)是会被暂时保存起来的,相关信息被保存到task_struct,此时,该task_struct相关数据不应该被释放掉 Z 当有进程来读取信息时,task_struct被释放 如何读取信息:进程wait
进程退出的信息(退出码)会被暂时保存起来
保存在task_struct中,如果没有人读取,此时,task_struct相关数据不应该被释放
模拟僵尸状态:
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { pid_t id=fork(); if(id==0) { int count=5; while(count) { printf("I am child, pid:%d, ppid:%d, count:%d\n",getpid(),getppid(),count--); sleep(1); } printf("child exit......\n"); exit(-1); } else if(id>0) { while(1) { printf("I am father,pid:%d, ppid:%d\n",getpid(),getppid()); sleep(1); } } else { printf("fork fail\n"); } return 0; }
[sjw@iZ2zedu4njy79sqivntvprZ test_9_11]$ make clean rm -rf *.o myproc [sjw@iZ2zedu4njy79sqivntvprZ test_9_11]$ make gcc -c test.c -o test.o gcc -o myproc test.o [sjw@iZ2zedu4njy79sqivntvprZ test_9_11]$ ./myproc I am father,pid:9259, ppid:308 I am child, pid:9260, ppid:9259, count:5 I am father,pid:9259, ppid:308 I am child, pid:9260, ppid:9259, count:4 I am father,pid:9259, ppid:308 I am child, pid:9260, ppid:9259, count:3 I am father,pid:9259, ppid:308 I am child, pid:9260, ppid:9259, count:2 I am father,pid:9259, ppid:308 I am child, pid:9260, ppid:9259, count:1 I am father,pid:9259, ppid:308 child exit...... I am father,pid:9259, ppid:308 I am father,pid:9259, ppid:308 I am father,pid:9259, ppid:308 I am father,pid:9259, ppid:308 I am father,pid:9259, ppid:308 ^Z
然后在另外一个窗口输入以下监控脚本:
while :; do ps aux | head -1 && ps aux | grep myproc|grep -v grep;echo "#############################"; sleep 1; done
刚开始运行时,二者都是S状态,到子进程运行完毕退出进行时,子进程编程僵尸进程
僵尸进程的危害:
造成内存浪费
造成内存泄漏
孤儿进程
Linux中,进程关系,主要是父子关系,
孤儿进程:父进程退出,子进程还在运行
孤儿进行会立即被系统领养(操作系统:1号进程Init)
监控脚本:
while :; do ps axj | head -1 && ps axj | grep myproc|grep -v grep;echo "#############################"; sleep 1; done
#include<stdio.h> #include<stdlib.h> #include<unstd.h> int main() { pid_t id=fork(); if(id==0) { while(1) { printf("I am child,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(1); } } else if(id>0) { int count=5; while(count) { printf("I am father,pid:%d,ppid:%d,count:%d\n",getpid(),getppid(),count--); } exit(-1); } return 0; }
查看进程:
ps aux |grep Mytest
进程是能够知道自己当前所处的工作目录的
cpu分配资源的先后顺序,即是进程的优先级,优先级越高说明进程被执行的越早
在Linux下输入:
ps -l
出现如上图所示的结果,有几个内容需要我们关注一下:
UID:代表执行者的身份
PID:当前进程的代号
PPID:父进程的代号
PRI:代表进程的优先级,该数字越低,代表进程的优先级越高,被执行的越早(默认该值为80)
NI:代表进程的nice值
NI与PRI的关系:
PRI用来表示进程的优先级,该值越低,代表进程的优先级越高,被执行的越早,该值默认为80,而NI用来改变进程的优先级,NI的取值范围为-2019,PRI(new)=PRI(old)+NI,也就是说我们通过修改NI的值来改变进程的优先级,那么被修改后的进程优先级PRI的取值范围为6099,60代表优先级最高的,99代表优先级最低(ps:每次修改PRI后再次修改时PRI默认为80,在80的基础上进行修改)
修改进程优先级的方法:
top命令--->输入r--->输入想要修改的进程的pid--->输入NI值--->q保存退出
如上图所示我们将上面的进程NI修改为20,而PRI变为60,即最高优先级
其它概念:
独立性:多个进程独自运行,独自享受各种资源,多个进程之间互不干扰
并行:多个进程在多个CPU上分别,同时进行运行
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程得以推进,称为并发
6.环境变量
环境变量一般使之操作系统中用来指定运行环境得一些参数
如:编写c/c++代码时,在连接的时候,从来不知道我们所连接的动态静态库在哪里,但是照样链接得动,就是相关环境变量帮助环境变量进行查找
常见环境变量
PATH:指定当前搜索路径
HOME:指定用户的主工作目录(即用户登录到LInux系统中时,默认的目录)
SHELL:当前Shell,它的值通常为/bin/bash
查看环境变量的方法
echo $NAME//NAME:你的环境变量名称
我们执行mpproc时前面必须加./,而执行ls之类的却不用加,就是因为ls进行了环境变量的配置,我们也可以进行配置,使之向ls一样直接执行
sudo cp -f mpproc /usr/bin //直接将我们的可执行程序mpproc复制到/usr/bin目录下,但是这种方法不推荐,因为以后如果同名的或者该目录下文件过多可能会造成误删
这样我们就可以像ls一样执行mpproc,除了这种方式之外还有一个更推荐的方法
首先我们先删除这次的配置:
sudo rm -rf /usr/bin/mpproc //注意不要直接把bin目录直接给删了
这样我们就删除了上次的配置,比较推荐的是下面这种方法,这种写法当我们退出服务器后路劲会自动消除:
export PATH=$PATH:/home/sjw/Linux_Learning/test_9_17 //直接将路径设置到环境变量当中去 //export:设置环境变量
查看PATH:
echo $PATH
查看HOME:
echo $HOME
查看SHELL:
echo $SHELL
显示当前所有的环境变量
env
set:显示本地系统中所有的环境变量
export :设置自己的环境变量
export myvalue=100
如上所示我们设置自己的环境变量并进行查看
unset:取消自己设置的环境变量
unset myvalue //取消刚刚设置的环境变量
每个程序都会收到一张环境表,环境表是一个字符指针数组,每个指针指向一个以‘\0’结尾的环境变量字符
这里我们以c/c++程序为例:
所有的c/c++程序从main函数开始执行,main函数是没有参数的,但实际上main是有参数的
int main(int argc,char*argc[],char*envp[]) { } //argc用来统计命令行参数 //argc是一个字符指针数组,里面每一个都是字符型的指针,指向命令行参数 //envp也是一个字符指针数组,里面每一个都是字符型的指针,指向环境变量信 //息,而环境变量信息也是通过这样传递给一个函数的
什么是命令行参数,下面我来带大家演示一下
#include<stdio.h> #include<string.h> int main(int argc, char*argv[],char*envp[]) { for(int i=0;i<argc;i++) { printf("%s\n",argv[i]); } if(strcmp(argv[1],"-a")==0) { printf("hello world\n"); } else { printf("哈哈\n"); } }
gcc test.c -o Mytest -std=c99
./myproc -a
这次我们使main带参数,然后进行编译,链接,在最后执行时我们在命令行上输入 ./Mytest -a
这时argv就发挥了作用,用来存储这两个命令行参数,argc用来记录命令行参数的个数,利用这个命令行参数我们可以在刚开始时就进行判断进行分支语句的执行
再比如:
#include<stdio.h> #include<string.h> int main(int argc, char*argv[],char*envp[]) { printf("argc:%d\n",argc); for(int i=0;i<argc;i++) { printf("argv[%d]:%s\n",i,argv[i]); } return 0; }
如上图所示,即将命令行参数进行了保存
接下来我们来聊一聊envp,它也是一个字符指针数组,指向系统的环境变量信息,通过envp我们可以调用到系统的环境变量信息
#include<stdio.h> #include<string.h> int main(int argc, char*argv[],char*envp[]) { int i=0; while(envp[i]) { printf("envp[%d]:%s\n",i,envp[i]); i++; } return 0; } //envp以'\0'进行结尾
仔细观察就会发现这和上面利用env命令查看到的系统环境变量信息是一致的
在Windows中也存在命令行参数,也可以通过envp来获取系统环境变量信息
//打印Windows下环境变量的信息 #include<stdio.h> //命令行参数的写法 int main(int argc,char*argv[],char*envp[]) { int i = 0; while (envp[i]) { printf("Windows:envp[%d]:%s\n", i, envp[i]); i++; } return 0; }
如上所示即为Windows下的环境变量信息,同学名还可以在自己的电脑上试一试
环境变量是一个系统级别的全局变量,更本原因是bash之下所有的进程都可以获取
通过系统函数调用查看环境变量
getenv(“NAME”)//NAME为环境变量名称
#include<stdio.h> #include<stdlib.h> #include<string.h> int main(int argc, char*argv[],char*envp[]) { //显示PATH环境变量名称 printf("%s",getenv("PATH")); return 0; }
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<sys/type> //定义全局变量 int g_val=100; int main(int argc,char*argv[],char*envp[]) { pid_t id=fork(); if(id==0) { //在子进程中改变这个全局变量,观察父进程中的全局变量是否发生变化 g_val=200; printf("child:pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } else { sleep(2); printf("father:pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",getpid(),getppid(),g_val,&g_val); } sleep(1); }
如上所示,我们在上面代码中定义了一个全局变量g_val,而我们在子进程中改变了g_val的值为200,但是在父进程中g_val的值仍然为100,而且两个进程中g_val的地址是一样的
证明:该地址不是物理地址,而是虚拟地址
是语言层面上见到的地址而不是物理地址
我们在c/c++语言所见到的地址都是虚拟地址,物理地址一般看不到,由操作系统统一进行管理
OS(操作系统)负责将虚拟地址转化为物理地址
每一个进程都有一个进程地址空间,都有一个映射列表
子进程的写入,不会影响父进程,进程之间具有独立性,不会互相影响
写的时候单独拷贝一块空间,数据层面上发生了分离
今天是中秋节,祝各位小伙伴节日快乐,也感谢大家的收藏,评论,转发,期待下次再见
To Be Continued…