进程总结
文章目录
一、操作系统
计算机系统内包含的一个基本的计算机程序集合。
包括,内核(进程管理,内存管理,驱动管理)+其他程序(函数库,shell程序等)。
描述被管理对象,组织被管理对象;描述使用struct结构体,组织是用链表或其它高效数据结构来进行组织。
二、进程
运行中的程序。担当分配系统资源(CPU时间,内存)的实体。
1 进程本质
描述进程(PCB):
- 进程信息被放在称为进程控制块(pcb)的数据结构中
- Linux操作系统下的pcb是task_struct
task_struct:
- 在Linux中描述进程的结构体
- Linux内核中的一种数据结构,会被装载到RAM内存中并包含着进程信息
task_struct结构体内容字段:
标识符
:描述该进程的唯一标识进程状态
:任务状态,退出代码,退出信号等优先级
:相对于其他进程的优先级内存指针
:包括程序代码和进程相关数据的指针,及其他进程共享的内存块指针上下文数据
:进程执行时处理器的寄存器中的数据程序计数器
:程序中即将要被执行的下一条指令的地址I/O状态信息
:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
2 进程查看
★ 查看进程
浏览 /proc 系统文件进行查看:
ls /proc/
★ 查看指定pid的进程信息
ls /proc/pid
如,查看pid为1的进程信息。
系统调用获取进程pid:
进程id(pid);父进程id(ppid)。
[dev@localhost ~]$ vim main.c
[dev@localhost ~]$ cat main.c
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
int main()
{
printf("pid=%d\n",getpid());
printf("ppid=%d\n",getppid());
return 0;
}
[dev@localhost ~]$ gcc main.c -o main
[dev@localhost ~]$ ./main
pid=3330
ppid=2956
[dev@localhost ~]$
系统调用获取进程pid(fork函数):
man手册查看fork文档
man fork
fork创建子进程,其中父子进程代码共享,数据独有。
[dev@localhost ~]$ vim fork.c
[dev@localhost ~]$ cat fork.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
int ret=fork();
if(ret<0)
{
perror("fork error");
return 1;
}
else if(ret==0)
{
printf("I am child:%d,ret:%d\n",getpid(),ret);
}
else
{
printf("I am father:%d,ret=%d\n",getpid(),ret);
}
sleep(1);
return 0;
}
[dev@localhost ~]$ gcc fork.c -o fork
[dev@localhost ~]$ ./fork
I am father:3630,ret=3631
I am child:3631,ret:0
[dev@localhost ~]$
可以看到子进程返回值为0,父进程的返回值是子进程的pid。
三、进程状态
标记了当前的进程该如何被操作系统进行调度管理。
R--运行态
:表明进程要么在运行要么在运行队列里头S--可中断休眠态
:进程在等待事件完成,一种阻塞状态,如sleep(3)D--休眠态
:不可中断休眠态,进程等待IO结束T--停止态
:可通过发送SIGSTOP信号给进程来停止该进程,被暂停进程可以通过发送SIGCONT信号让进程继续运行X--死亡态
:一个返回状态Z--僵尸态
:进程退出运行,但是资源并没有得到释放,等待处理的一种状态
★ 查看进程状态
ps aux | ps axj
[dev@localhost ~]$ ps aux | ps axj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 0:05 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
0 2 0 0 ? -1 S 0 0:00 [kthreadd]
2 3 0 0 ? -1 S 0 0:01 [ksoftirqd/0]
2 7 0 0 ? -1 S 0 0:01 [migration/0]
2 8 0 0 ? -1 S 0 0:00 [rcu_bh]
2 9 0 0 ? -1 S 0 0:05 [rcu_sched]
2 10 0 0 ? -1 S 0 0:00 [watchdog/0]
3.1 僵尸进程
//通过复制父进程来创建一个子进程
pid fork(void);
返回值:
子进程返回0
父进程返回子进程pid
出错返回-1
fork创建子进程后,父进程与子进程谁先运行取决于系统调度。
僵尸进程,就是子进程先于父进程退出,子进程要保存退出原因给父进程,所以子进程退出后并没有完全释放资源而成为僵尸进程。
僵尸进程危害:
- 造成资源的泄漏
避免:
- 进程等待
父进程等待子进程的退出,获取退出子进程返回值,释放子进程资源,避免产生僵尸进程
因为僵尸进程存在原因就是为了保存子进程退出状态信息给父进程看的,只要父进程获取了子进程的状态信息,资源就会被释放了
实例:维持30秒的僵尸进程
[dev@localhost ~]$ vim zom.c
[dev@localhost ~]$ cat zom.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork error");
return 1;
}
else if(id>0)
{
//父进程
printf("parent[%d] is sleeping...\n",getpid());
sleep(30);
}
else
{
printf("child[%d] is begin Z...\n",getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
[dev@localhost ~]$ gcc zom.c -o zom
编译文件:
在另一个终端进行监控:
可以看到,子进程状态变为Z+了,即现在子进程是个僵尸进程。
3.2 孤儿进程
父进程先于子进程退出,子进程成为孤儿进程。孤儿进程被init进程领养,由init进程进行回收。
实例:
[dev@localhost ~]$ vim child.c
[dev@localhost ~]$ cat child.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t id=fork();
if(id<0)
{
perror("fork error");
return 1;
}
else if(id==0)
{
printf("I am child,pid:%d\n",getpid());
sleep(10);
}
else
{
printf("I am father,pid:%d\n",getpid());
sleep(3);
exit(0);
}
return 0;
}
[dev@localhost ~]$ gcc child.c -o child
编译文件:
在另一个终端进行监控:
3.3 守护进程
守护进程是运行在后台的一种特殊的孤儿进程,独立于控制终端并且周期性地执行某种任务或等待处理某些事件。
守护进程特点:
-
后台运行
-
必须与其运行前的环境隔离开来
这些环境包括,未关闭的文件描述符、控制终端。会话和进程组、工作目录等,这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。 -
启动方式的特殊性
① 可在Linux系统启动时从启动脚本/etc/rc.d
中启动;
② 可由作业规划进程crond
启动;
③ 还可由用户终端(shell)执行。
守护进程实现:
- 在父进程中执行
fork
并exit
退出 - 在子进程中调用
setsid
函数创建新的会话 - 在子进程中调用
chdir
函数,让根目录/
成为子进程的工作目录 - 在子进程中调用
umask
函数,设置进程umask
为0 - 在子进程中关闭任何不需要的文件描述符
四、环境变量
环境变量:保存了当前程序运行环境参数的变量。
如,在编写C代码,在链接的时候从来不知道所链接的动态静态库在哪,但是依旧可以链接成功生成可执行程序,就是有相关环境变量来进行查找。
变量即时生效,让运行环境配置更加灵活;通过环境变量可以给运行程序传递数据。
//查看所有环境变量
env
//打印指定内容到终端显示
echo
//声明一个变量为环境变量
export
//查看所有变量
set
//删除一个变量
unset
常见环境变量:
- PATH:指定命令的搜索途径
- HOME:指定用户的主工作目录(即用户登录到Linux系统中时默认的目录)
- SHELL:当前shell,值通常为
/bin/bash
在程序中访问环境变量方式一:
char* getenv(char* name)接口
在程序中访问环境变量方式二:
main函数第三个参数:main(int argc,char* argv[],char* env[])
在程序中访问环境变量方式三:
全局变量:extern char** environ
libc中定义的全局变量environ
指向环境变量表,environ
没有包含在任何头文件中,股灾使用的时候得用extern
进行声明。
环境变量组织方式:
每个程序都会收到一张环境表,是一个字符指针数组,每个指针指向以恶搞以\0结尾的环境字符串。
而环境变量通常具有全局属性,即可以被子进程继承下去。
五、程序地址空间
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 0;
}
else if (id == 0)
{
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
else
{
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
程序输出的变量值和地址都是一样的,因为子进程是父进程的一份拷贝,代码是共享的。
将程序修改一下,
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if (id < 0)
{
perror("fork");
return 0;
}
else if (id == 0)
{
//子进程先运行完,即子进程先修改数据,完成之后,父进程再读取数据值
g_val = 100;
printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
else
{ // parent
sleep(3);
printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
}
sleep(1);
return 0;
}
可以看到,变量地址一样,但是值却不一样。
实际上,Linux下该地址是虚拟地址,在C/C++语言中所看到的地址都是虚拟地址,物理地址对于用户来说是不可见的,由OS统一管理。
故而,同一个变量地址相同,实际上是它们的虚拟地址相同,而内容的不同实际上是被映射到了不同的物理地址上访问不同的内容。
进程并不直接访问物理地址,而是通过访问虚拟地址,进行映射访问物理地址的方式来进行数据的访问。
这样的好处是:
- 数显数据在物理内存上的离散式存储,提高碎片化内存利用率
- 实现内存访问控制
虚拟地址空间是一个结构体,Linux下是mm_struct。
系统通过mm_struct向每个进程都描述了一个虚拟的,连续的,完整的,线性的地址空间。
5.1 内存管理方法
(1)分段式内存管理
将一个整体的地址空间划分为多个段,代码段、数据段、堆区、栈区等。
作用:利于编译器对于地址的管理。
其中有两个数值,段表和地址组成。
段表:一种数据结构,其中描述段号、物理内存的起始地址信息。
虚拟地址组成:段号,段内偏移量。
(2)分页式内存管理
将一个整体的地址空间划分为大量小的分页page,一般默认4096字节为一页。
作用:实现数据的离散存储,提高内存的利用率。
其中有两个数值,页表和虚拟地址组成。
页表:页号、物理内存块起始地址、缺页中断位、访问权限位等。
虚拟地址组成:页号,页内偏移量。
(3)段页式内存管理
先将地址进行分段,再将分段的地址进行分页进行管理,结合分段式和分页式的优点。
5.2 访问权限位
标记当前地址可以进行哪种操作。
实例:
- 0号地址:即NULL,该地址不可读也不可写,故一旦解引用或者修改就会发生内存访问错误
- const修饰的常变量:访问权限为只可读
- 代码段所有地址,均为只读
六、进程优先级
cpu资源分配的先后顺序,即指进程的优先级;进程优先级高的有优先执行权力,提高系统性能;还可以将进程运行到指定的cpu上,这样把不重要的进程安排到某个cpu上,可以大大改善系统性能。
★ 查看系统进程
ps -l
[dev@localhost ~]$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 2914 2605 0 80 0 - 29190 wait pts/0 00:00:00 bash
0 R 1000 3636 2914 0 80 0 - 37250 - pts/0 00:00:00 ps
- UID:表示执行者身份
- PID:表示该进程的代号
- PPID:表示该进程由哪个进程衍生而来,即父进程的代号
- PRI:表示该进程可被执行的优先级,值越小越先被执行
- NI:表示该进程的nice值
PRI表示一个进程被执行的优先级,值越小越先被执行;加入nice值后,PRI(new)=PRI(old)+nice
来表示进程优先级。
nice为负值时,该程序的优先级值将会变小,就会优先被执行,即其优先级就会变高,越快被执行;Linux下就是通过调整nice的值来调整进程的优先级;nice取值范围在-20 -> 19
,40个级别。
所以,nice并不是进程的优先级,只是进程nice值会影响到进程的优先级而已。
★ 查看进程优先级
top 命令更改已存在进程的nice值
进入top后按r,然后输入进程PID,然后输入nice值 --> 进行修改进程的优先级
总结
进程是系统进行资源分配的最小单位,每个进程都有自己独立的地址空间。