好一点网站建设公司,网页视频下载不了,全国好的视频制作,池州网站建设网站建设文章目录 一、概述二、查看进程信息1. 系统文件夹 /proc2. 用户级工具 ps3. getpid() 函数#xff1a;查看进程 PID4. 用 kill 杀进程5. 进程优先级 二、进程状态分析0. 1. R (running) 运行状态2. S (sleeping) 休眠状态3. D (disk sleep) 不可中断的休眠状态4. T (stopped) … 文章目录 一、概述二、查看进程信息1. 系统文件夹 /proc2. 用户级工具 ps3. getpid() 函数查看进程 PID4. 用 kill 杀进程5. 进程优先级 二、进程状态分析0. 1. R (running) 运行状态2. S (sleeping) 休眠状态3. D (disk sleep) 不可中断的休眠状态4. T (stopped) 暂停状态5. t (tracing stop) 追踪暂停状态6. Z (zombie) 僵尸状态7. X (dead) 终止状态8. 孤儿进程9. 一些概念 三、 环境变量1. 常见的环境变量2. 有关指令3. 通过代码如何获取环境变量4. 通过系统调用 获取 或 设置 环境变量5. 补充命令行的 int argc 和 char *argv[] 四、进程地址空间1. 如何理解 进程地址空间2. 为什么要有地址空间3. malloc 的本质4. 再谈 地址空间 下接 进程篇Ⅱ进程开始、进程终止、进程等待、程序替换 硬件 - - 冯诺依曼计算机 1、CPU 不和外设直接沟通而是和内存打交道 2、数据层面外设也只会和内存打交道 软件 - - 操作系统 手段对下通过管理好软硬件资源 目的对上给用户提供良好安全、稳定…的执行环境 管理的本质先描述再组织 管理的实际是数据用面向对象进行描述数据结构进行组织。 一、概述 进程 内核关于进程的相关数据结构 // task_struct 当前进程的代码和数据 这个相关的数据结构就是我们通常所说的 PCBprocess control blockLinux 下的 PCB 是 task_struct
比如我们输入 ./可执行程序 的时候数据从磁盘调到内存变成进程 阻塞就是不被调度。 阻塞一定是因为 当前进程需要等待某种资源就绪 也一定是 进程 task _struct 结构体需要在某种被 OS 管理的资源下排队queue。 挂起操作系统对阻塞的进程为了腾出内存空间将进程的代码和数据部分放入磁盘中直到轮到进程被调度时在调出代码和数据进入内存。可以理解成一种特殊的阻塞状态 二、查看进程信息
1. 系统文件夹 /proc
正在执行的进程会有一个和进程 PID 同名的文件夹存在 /proc 目录下其中存放进程相关信息。
进程消失后同名文件夹消失。
2. 用户级工具 ps
# 查看全部进程
ps axj
# 查看某个程序的进程
ps axj | grep [可执行程序]
# 拿 进程表头 某个程序的进程
ps axj | head -1 ps ajx | grep [可执行程序]
# 拿 进程表头 某个程序的进程 去掉自己 grep 这个进程
ps axj | head -1 ps ajx | grep [可执行程序] | grep -v grep
# 在上面的基础上每隔一秒打印一次结果
while :; do ps axj | head -1 ps ajx | grep [可执行程序] | grep -v grep; sleep 1; echo ----------; done3. getpid() 函数查看进程 PID 函数声明 pid_t getpid(void); // 查看自身进程 PID pid_t getppid(void); // 查看父进程 PID 头文件包含 #include sys/types.h #include unistd.h getpid() 当前程序运行时可以获得 自身进程 PID getppid() 当前程序运行时可以获得 父进程 PID
pid_t 相当于一个有符号整数返回的就是 PID 号也是 /proc 里的文件名 测试代码 观察结果如下 频繁多次运行发现子进程每次进入都是新的 PID父亲的 PPID 一直都是同一个。查看这里的 3395 为例可知父进程是 bash 结论 bash命令行解释器 也是个进程 命令行启动 的 所有程序最终都会变成进程而该进程 对应的 父进程 都是 bash 这里有个生动案例帮助理解
角色设定bash -- 媒婆子进程 -- 媒婆实习生说村里阿猫阿狗太多媒婆为了保护自己的声誉放出他的实习生说媒但凡某个实习生谈崩了或者被骗了总之没处理好这活坏掉的是这个实习生的声誉媒婆狂喜...同样bash 放出 子程序去测你写的代码如果你的代码有问题崩的是子程序保护了 bash...4. 用 kill 杀进程
除了 ctrlC杀进程有专门的命令 kill
方法一
kill -9 [进程PID]方法二
killall [可执行文件]如果我们不小心 bash 把他杀了bash 会崩溃…需要重新连接一下 结论 如何创建的子进程 fork 之后执行流会变成两个执行流 fork 之后谁先运行由调度器决定 fork 之后fork 之后的代码共享通常我们通过 if 和 else if 进行执行流分流 5. 进程优先级 cpu资源分配的先后顺序就是指进程的优先权priority。 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用可以改善系统性能。 还可以把进程运行到指定的CPU上这样一来把不重要的进程安排到某个CPU可以大大改善系统整体性能 输入 ps -l 命令可以得到的关键信息有如下内容
UID : 代表执行者的身份PID : 代表这个进程的代号PPID 代表这个进程是由哪个进程发展衍生而来的亦即父进程的代号PRI 代表这个进程可被执行的优先级其值越小越早被执行NI 代表这个进程的 nice 值
PRI and NI
PRI 也还是比较好理解的即进程的优先级或者通俗点说就是程序被 CPU 执行的先后顺序此值越小 进程的优先级别越高那 NI 呢就是我们所要说的 nice 值了其表示进程 可被执行的优先级的 修正数值PRI值越小越快被执行那么加入nice值后将会使得PRI变为PRI(new)PRI(old即 80)nice这样当 nice 值为负值的时候那么该程序将会优先级值将变小即其优先级会变高则其越快被执行所以调整进程优先级在 Linux 下就是调整进程 nice 值nice 其取值范围是 -20 至 19一共 40 个级别。
PRI vs NI
需要强调一点的是进程的 nice 值不是进程的优先级他们不是一个概念但是进程 nice 值会影响到进程的优先级变化。可以理解 nice 值是进程优先级的修正修正数据
用 top 命令更改已存在进程的 nice
进入 top后按 rrenice – 输入进程 PID – 输入 nice 值 二、进程状态分析
task_struct 是一个结构体内部会包含各种属性其中就有一项是当前状态。
struct task_struct
{int status;//...
}; Linux内核源代码部分
static const char * const task_state_array[] {
R (running), /* 0 */
S (sleeping), /* 1 */
D (disk sleep), /* 2 */
T (stopped), /* 4 */
t (tracing stop), /* 8 */
X (dead), /* 16 */
Z (zombie), /* 32 */
};0. 进程状态后面带 号 则说明该进程在 前台运行可以用 ctrl C 让程序停止。 进程状态后面没有 号 该进程在 后台运行不能用 ctrl C 让程序停止了。 1. R (running) 运行状态
进程只要是 R 状态就一定是在CPU上运行吗 事实上进程是 R 状态并不直接代表进程在运行而代表该进程在运行队列中排队这个队列是由操作系统维护的。
操作系统在内存里这个队列也在内存里被维护的。操作系统对 task_struct 的管理就是把他们放到不同的队列当中。
进程是什么状态一般也看这个进程在哪里排队是 task_struct 在排队而不是代码和数据。
运行状态 R 是瞬时状态。当进程会调用资源如打印到显示器时由于 CPU 运行速度太快我们去 ps axj 进程信息的时候极大概率只能看到进程的其他状态而无法捕捉到 R 状态。
2. S (sleeping) 休眠状态
S 休眠状态是 可中断休眠本质上就是一种 阻塞状态处于等待某种资源的状态。
3. D (disk sleep) 不可中断的休眠状态
D 是 不可中断休眠也是阻塞状态的的一种在做系统管理、运维、系统存储的时候才会遇到。
面对普通的休眠状态的进程在特殊场景下操作系统可以做出判断并杀掉休眠进程。D 状态的休眠则是操作系统无法杀掉的。只能等进程自己运作或者拔掉电源…
4. T (stopped) 暂停状态
T 是 暂停状态。
用户主动使用 kill -19 操作可以让进程进入 T 状态
kill -19 [进程PID]用户主动关闭 T 状态使进程变成 R / S后台运行 / 休眠 状态继续运行
kill -18 [进程PID]此时进程变成后台运行无法通过 ctrl C 的方式结束需要输入另一个信号 kill -9
kill -9 [进程PID]5. t (tracing stop) 追踪暂停状态
追踪暂停也是暂停的一种。当我们给程序打上断点并在断点处停下时进程会显示追踪状态。
6. Z (zombie) 僵尸状态
在了解 Z 状态之前我们先引出一个概念。
main 函数 里的 return 0实际上是进程退出码。可以交给程序去判断进程结束的结果是否正确。
// 进程退出码使用举例
int main()
{// 算法省略int result 10;if(result 10)return 0; // 正常退出elsereturn 3; // 异常退出
}查看进程退出码
echo $?注意$? 只会保存最后一次执行的退出码。
僵尸状态
子进程退出后等待后续父进程OS读取子进程退出的退出结果的状态。 僵尸进程的危害 进程的退出状态必须被维持下去因为他要告诉关心它的进程父进程你交给我的任务我办的怎么样了。可父进程如果一直不读取那子进程就一直处于Z状态是的 维护退出状态本身就是要用数据维护也属于进程基本信息所以保存在task_struct(PCB)中换句话说Z状态一直不退出PCB一直都要维护是的 那一个父进程创建了很多子进程就是不回收是不是就会造成内存资源的浪费是的因为数据结构对象本身就要占用内存想想C中定义一个结构体变量对象是要在内存的某个位置进行开辟空间。 会造成内存泄漏。 7. X (dead) 终止状态
终止状态也是一个瞬时状态。当进程从 Z 状态被回收会变成 X 终止状态继而操作系统才会正真释放进程的所有资源。
8. 孤儿进程
孤儿进程父进程退出子进程会被 OS 自动领养通过让 1 号进程成为新的父进程。被领养的进程就是孤儿进程
9. 一些概念
竞争性系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级独立性多进程运行需要独享各种资源多进程运行期间互不干扰并行多个进程在多个CPU下分别同时进行运行这称之为并行并发多个进程在一个CPU下采用进程切换的方式在一段时间之内让多个进程都得以推进称之为并发 三、 环境变量 环境变量(environment variables) 一般是指在操作系统中用来指定操作系统运行环境的一些参数。 环境变量本质就是一 个 内存级 的一张表这张表由 用户在登陆系统的时候进行给特定用户形成属于自己的环境变量表。在系统当中通常具有全局特性可以被子进程继承。 环境变量中的每一个都有自己的用途有的是进行路径查找的有的时进行身份认证的有的时进行动态库查找的有的是用来进行确认当前路径…等等。每一个环境变量都有自己的特定应用场景。每一个元素都是 kv 的。 我们平时写代码中生成的可执行文件 xx在我们需要运行它时输入的 ./xx 实际上就是这个可执行文件的路径。而众多的命令实际也是一个个可执行文件为什么命令可以直接被读取而我们生成的可执行文件则要带上路径呢
分别 which 一下随便某个命令、再 which 我们的可执行文件可以发现。是因为我们的可执行文件不在 PATH 路径下。
两个解决思路让我们输入可执行文件名 xx就可以执行程序 1、把我们生成的可执行文件 cp -rf 到 PATH 的路径下。 2、把可执行文件所在路径 export 到原有路径后面。
1. 常见的环境变量
PATH : 指定命令的搜索路径HOME : 指定用户的主工作目录即用户登陆到 Linux 系统中时默认的目录SHELL : 当前 Shell它的值通常是 /bin/bash。
2. 有关指令
which在环境变量中查找某个命令的路径
env输出所有 环境变量
set 同时输出 环境变量 和 本地变量
unset [变量名]取消某个 本地 / 环境变量
echo $[环境变量名称]查看某个环境变量
export [变量名]设置新的 / 更新环境变量 本质上就是把本地变量添加到环境变量表里
注意如果环境变量被我们误操作不慎覆盖导致命令无法使用只需要重启虚拟机即可
以 PATH 环境变量举例
------------------# 添加路径到环境变量
export PATH $PATH:[指定路径]# 设置并覆盖原来的环境变量
export PATH [指定路径]环境变量 存在 shell 里 放进环境变量表 可以被子进程继承 普通本地变量 存在 shell 里 只能由 shell 内部调用 不能被子进程继承 # 设置新的环境变量env 中可查可以被子进程继承
export hello 123456
# 设置普通的本地变量env 中没有
hey abcde# 查看变量的值
echo $hello
echo $hey这里要引出一个问题了既然我们说本地变量只能在 shell 内部使用不能被子进程继承echo 命令必然会调用子进程子进程又是怎么访问到本地变量的呢
这里要用 内建命令 来解释了。后续更新。3. 通过代码如何获取环境变量
命令行第三个参数就是 环境变量表
#include stdio.h
int main(int argc, char *argv[], char *env[])
{int i 0;for(; env[i]; i){printf(%s\n, env[i]);}return 0;
}通过第三方变量 environ 获取 libc 中定义的全局变量 environ 指向环境变量表environ 没有包含在任何头文件中所以在使用时 要用 extern 声明。
#include stdio.h
int main(int argc, char *argv[])
{extern char **environ;int i 0;for(; environ[i]; i){printf(%s\n, environ[i]);}return 0;
}其实获取环境变量最主要的是下面这种方式
4. 通过系统调用 获取 或 设置 环境变量
putenv getenv
常用getenv和putenv函数来访问特定的环境变量。
我们模拟实现一个pwd
#include stdio.h
#include stdlib.hint main()
{char* pwd getenv(PWD);if(pwd NULL)perror(geienv);elseprintf(%s\n, pwd);return 0;
}5. 补充命令行的 int argc 和 char *argv[] char *argv[] 命令行输入的 int argc
void Usage(const char *name)
{printf(\nUsage: %s -[a|b|c]\n\n, name);exit(0); // 终止进程
}int main(int argc, char *argv[])if(argc ! 2) Usage(argv[0]);if(strcmp(argv[1], -a) 0) printf(打印当前目录下的文件名\n);else if(strcmp(argv[1], -b) 0) printf(打印当前目录下的文件的详细信息\n);else if(strcmp(argv[1], -c) 0) printf(打印当前目录下的文件名(包含隐藏文件)\n);else printf(其他功能待开发\n);return 0;
}四、进程地址空间
先看如下这个测试
测试代码
#include stdio.h
#include unistd.h
#include assert.h
int g_val 100;
int main()
{pid_t id fork();assert(id 0);else if(id 0) //child{ while(1){printf(child[%d]: %d : %p\n, getpid(), g_val, g_val); g_val;sleep(1);}}else //parent{ while(1){printf(parent[%d]: %d : %p\n, getpid(), g_val, g_val);sleep(1);}}return 0;
}测试结果
parent[2995]: 100 : 0x80497d8
child[2996]: 100 : 0x80497d8
parent[2995]: 100 : 0x80497d8
child[2996]: 101 : 0x80497d8
parent[2995]: 100 : 0x80497d8
child[2996]: 102 : 0x80497d8
parent[2995]: 100 : 0x80497d8
child[2996]: 103 : 0x80497d8我们已经知道的是
子进程对全局数据修改并不影响父进程。进程具有独立性
进而可以发现父子进程输出地址是一致的但是变量内容不一样能得出如下结论:
变量内容不一样所以父子进程输出的变量绝对不是同一个变量。但地址值是一样的说明该地址绝对不是物理地址在 Linux 地址下这种地址叫做 虚拟地址 / 线性地址我们在用 C/C 语言所看到的地址全部都是虚拟地址物理地址用户一概看不到由OS统一管理。
OS 必须负责将 虚拟地址 转化成 物理地址。
1. 如何理解 进程地址空间 进程地址空间本质上也是一个内核数据结构struct mm_struct{}; 2. 为什么要有地址空间
防止地址随意访问保护物理内存与其他进程。地址空间 和 物理内存 之间 由 页表 连接这张表不止简单的定义了映射关系还为用户设置了各种区域的不同权限。例如我们知道 char* str “hello”; 如果我们去编译 str ‘H’; 肯定是不能通过的因为我们通过 页表 所映射的 “hello” 所存入的物理内存的常量区在 key 被设置为只读我们平时说的 代码是只读的也是这个道理。
3. malloc 的本质
当我们像 OS 申请内存时操作系统是立马给我们这个地址还是需要的时候再给我们首先OS 一般不允许任何的浪费不高效。其次申请内存不一定立马使用。什么意思呢在我们申请成功之后使用之前就有一段小小的时间窗口这个空间没有被正常使用但别人用不了这块空间处于闲置状态这个不高效的行文OS 就不允许的于是我们申请成功后地址空间给我们一个地址连接页表的 key此前是 进程管理而 value 也是没有映射存在的即此时没有开辟空间。只有当我们调用或者访问内存OS 发现没有相应的数据才会给我们把数据换入。这里是内存管理这个现象叫做 缺页中断也是一个典型的 解耦合 4. 再谈 地址空间 所以为什么要有地址空间 防止地址随意访问保护物理内存与其他进程。将 进程管理 和 内存管理 进行 解耦合更重要的是可以让进程以统一的视角看待自己的代码和数据 扩展解释第三点 我们的程序在被编译还没有被加载到内存的时候程序内部也存在地址 编译器编译可执行程序时本来就是按照 虚拟地址空间 的方式、各种内存布局来编译的在磁盘上已经给我们规定好了代码区、已初始化数据区…等等这样的概念。编译时只需要进行模块式的加载进行对应的映射到内存则有了物理地址。而代码彼此之间是使用虚拟地址互相跳转的。所以说程序在没加载到内存的时候就有了地址这个地址是虚拟地址。 比如我们写一个函数再去调用它在反汇编中查看能看到他们的地址这个地址就是 程序自拟的 虚拟地址。 可以看到的是地址空间约束的不光是 OS我们的编译器也需要遵守这样的规则。所以一个可执行程序加载到内存是拥有两套地址的。
最后一个问题
进程和代码必须一直在内存中
不一定实际上我们拥有了虚拟地址空间我们的代码是可以边加载边执行的。需要的数据才加载这样就可以保证我们在内存使用量很低的情况下还完成了大软件的运行。 下接 进程篇Ⅱ进程开始、进程终止、进程等待、程序替换 链接在此 如果本文对你有些帮助请给个赞或收藏你的支持是对作者大大莫大的鼓励(✿◡‿◡) 欢迎评论留言~~