-进程
-概念
进程描述是一个程序执行过程,当程序执行后,执行过程开始,则进程产生;执行过程结束,则进程也就结束
-进程的特点
进程是一个独立的可调度的活动,由操作系统进行统一调度,相应的任务会被调度到cpu中进行执行
进程一旦产生,则需要分配相关资源,同时进程是资源分配的最小单位
-进程与程序的区别
1.程序是静态的,它是保存在磁盘上的指令的有序集合,没有任何执行的概念
2.进程是一个动态的概念,它是程序执行的过程,包括了动态创建,调度和销毁的整个过程
-并发与并行
并行执行:表示多个任务可以同时执行,依赖于物理支持
并发执行:在同一时间段由多个任务在同时进行,由操作系统的调度算法实现;如:时间片轮转
时间片轮转
特点:
1.一个时间片的时间非常短,由操作系统调度算法来实现每个时间片所执行的任务,一旦一个任务的时间片消耗完,则操作系统会切换到下个任务到CPU中执行
2.如果没有执行结束,则等待下一次分配
-Linux进程管理
1.在Linux系统中,管理进程使用树型管理方式
2.每一个进程都需要与其他某一个进程建立父子关系,对应的进程叫做父进程
3.Linux系统会为每个进程分配id,这个id作为当前进程的唯一标识,当进程结束时,则会被回收
4.进程的id与父进程的id分别通过getpid()与getppid()来获取
-进程的空间分配
32位Linux系统中,会为每一个进程分配4G的空间,分为俩部分,高位1G为内核空间,低位3G为用户空间(4G空间为虚拟空间)
tips:
1.当用户进程需要通过内核获取资源时,会切换到内核态运行,此时当前进程会使用内核空间资源
2.用户切换的内核态运行时,主要是通过系统调用
虚拟地址与物理地址
虚拟地址:虚拟地址并不代表真实的内存空间,,而是一个用于寻址的编号
物理地址:是指内存设备中真实存在的存储空间的编号
关系:虚拟地址通过映射的方式建立与物理地址的关联,从而达到访问虚拟地址就可以访问到对应的物理地址
使用虚拟地址的原因:
1.直接访问物理地址,会导致地址空间没有隔离,很容易导致数据被修改
2.通过虚拟地址可以实现每个进程空间都是独立的,操作系统会映射到不用的物理地址区间,在访问时互不干扰
tips:1.进程栈的默认大小是8M,可以通过ulimit -s查看
2.进程堆的大小小于3G
3.通过ulimit -u查看系统的最大进程数
-进程的状态管理
三态模型:运行态,就绪态,阻塞态
三态模型图示:
五态模型:新建态,终止态,运行态,就绪态,阻塞态
五态模型图示
经常使用的进程状态:
1.运行态:此时进程正在运行或者准备运行都属于运行态
2.睡眠态:此时进程在等待一个事件的发生或某种系统资源
可中断睡眠:可以被信号唤醒或者等待事件或者资源就绪
不可中断睡眠:只能等待特定的事件或者资源就绪
3.停止态:进程停止接受某种处理,例如:gdb调试断点信息处理
4.僵尸态:进程已经结束但是还没有释放进程资源
--进程的相关命令
ps -ef:列出所有进程
top:实时显示进程的信息
top -i:不显示任何闲置或无用的进程
pstree:将所有的进程以树型结构的方式进行展示
kill -9:终止进程
--创建进程
通过调用fork()函数,则会产生一个新的进程,调用fork()函数的进程叫做父进程,产生的新的进程则为子进程
通过fork()函数创建子进程,有如下特点:
1.父子进程并发执行,子进程从fork()函数之后开始执行
2.父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定
3.子进程会拷贝父进程地址空间的内容,包括缓冲区,文件描述符等
(当子进程拷贝了父进程描述符后,则会共享文件状态与文件偏移量等信息)
(子进程创建会拷贝父进程的地址空间,但是修改数据只会生效在子进程地址空间,说明父子空间的地址空间是相互独立的地址空间)
示例代码:
int main()
{
pid_t pid = fork();
if(pid==-1)
{
perror("fork");
return -1;
}
printf("Hello fork.\n");
return 0;
}
(注:子进程:pid==0;父进程:pid>0)
--进程多任务
-父子进程执行不同的任务
使用fork()函数之后,会创建子进程,fork()之后的代码会在父子进程中都执行
1.如果父子进程执行相同的任务,则正常执行
2.如果父子进程执行不同的任务,则需要利用fork()函数返回值
-创建多个进程
在创建多个进程时,最主要的原则是由父进程统一创建,统一管理,不能递归创建
--进程的退出
在进程结束时,需要释放分配给进程的地址空间以及内核中产生的各种数据结构
1.资源的释放需要通过exit函数或者_exit函数来实现
2.在程序结束时,会自动调用exit函数
-exit和_exit
exit:结束进程,并刷新缓冲区
_exit:结束进程,但不刷新缓冲区
tips:
在系统中定义了俩个状态值:EXIT_SUCCESS正常退出,EXIT_FAILURE异常退出
-exit与_exit的不同
1._exit()属于系统调用,能够是进程停止运行,并释放空间以及销毁内核中的各种数据
2.exit()基于_exit()函数实现,属于库函数,会自动刷新I/O缓冲区
--进程的等待
在子进程运行结束后(调用exit或_exit),进程进入僵死状态,并释放资源,子进程在内核中的数据结构依然保留
父进程调用wait()与waitpid()函数等待子进程退出后,释放子进程遗留的资源
-wait与waitpid
wait:让函数调用者进程进入睡眠状态,等待子进程进入僵死状态后释放资源并返回
waitpid:功能与wait函数一样,但比wait函数更加强大,可以理解为wait()底层调用waitpid函数
waitpid(-1,&staus,0); :waitpid使用阻塞的方式等待任意子进程退出
waitpid(-1,NULL,0); :不关心状态值,子进程退出状态值的指针为NULL
--进程替换
概念
创建一个进程后,pid以及在内核中的信息保持不变,但进程所执行的代码进行替换
应用场景:
Linux终端应用程序,在执行命令时,通过创建一个进程,然后替换成对应命令的可执行程序
-exec函数族
在Linux系统中提供了一组用于进程替换的函数,共有6个:
1.int execl(conest char* pathname,conest char* arg, .../*(char*),NULL*/);
2.int execlp(const char *file, const char *arg, .../* (char *) NULL */);
3.int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char* const envp[ ] */);
4.int execv(const char *pathname, char *const argv[ ]);
5.int execvp(const char *file, char *const argv[ ]);
6.int execvpe(const char *file, char *const argv[ ],char *const envp[ ]);
示例代码:(通过execl函数执行ls -l命令)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int ret;
ret = execl("/bin/ls","ls","-l",NULL);
if(ret == -1)
{
perror("execl");
return -1;
}
return 0;
}
输出结果: