-进程
-概念
进程描述是一个程序执行过程,当程序执行后,执行过程开始,则进程产生;执行过程结束,则进程也就结束
-进程的特点
进程是一个独立的可调度的活动,由操作系统进行统一调度,相应的任务会被调度到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;
}
输出结果:
进程间的通信
进程间的通讯方式有管道,信号,消息队列,共享内存,网络
--管道
管道分为无名管道与有名管道
无名管道用于父子间通讯
有名管道用于任意进程之间通讯
管道的本质:在内存中建立一段缓冲区,由操作系统内核来负责创建和管理;如下图
-无名管道
特点:
无名管道属于单向通讯
无名管道只能用于父子进程进程通讯
无名管道发送端叫做写端,接收端叫做读端
无名管道将读端与写端抽象成俩个文件,在无名管道创建成功后,则会将读端与写端的文件描述符存入数组
pipe():创建无名管道(管道创建后,内核会将文件描述符存储到数组中)
总结:
1.当管道为空时,读进程会堵塞
2.当管道的写端关闭时,从管道中读取剩余数据,read函数返回0
3.管道的大小有限的,不能让父/子进程同时对管道进行读写操作
-有名管道
有名管道是文件系统中可见的文件,但是不占用磁盘空间,仍然在内存中。可以通过mkfifo命令创建有名管道(在共享内存中不能使用mkfifo)
有名管道与无名管道一样,在应用层是基于文件文件接口进行操作
有名管道用于任意进程之间的通讯,当管道为空时,读进程会阻塞
有名管道的优缺点
优点:
1.可以实现任意进程间通讯,操作起来和文件操作一样
缺点:
1.打开的时候需要读写一起进行否则就会阻塞,管道的大小时4096个字节
2.半双工的工作模式,如果和多个进程通讯则需要创建多个管道
-信号
-信号的定义
信号是在软件层面上是一种通讯机制,对中断机制的一种模拟,是一种异步通信方式
-信号的特点
1.进程在运行过程中,随时可能会被各种信号打断
2.进程可以忽略或者去调用相应的函数去处理信号
3.进程无法预测信号到达的精准时间
-信号的来源
在Linux中信号的来源如下:
程序执行错误,如内存访问越界,数学运算除0
有其他进程发送
通过控制终端发送,如ctrl+c
子进程结束时向父进程发送的SIGCHID信号
程序中设定的定时器产生的SIGALRM信号
-信号的种类
在Linux系统可以通过kill -l命令查看,常用的信号列举如下:
-信号处理流程
信号处理流程包含以下俩个方面:
信号的发送:可以有进程直接发送
信号的投递与处理:由内核进行投递给具体的进程并处理
在linux中对信号的处理方式如下:
忽略信号,即对信号不做任何处理,但是有俩个信号不能忽略,即SIGKILL及SIGSTOP
捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数
执行缺省操作,Linux对每种信号都规定了默认操作(缺省操作指在无决策者干预情况下,对于决策或应用软件、计算机程序的系统参数的自动选择)
-信号发送
当由进程来发送信号时,则可以调用kill()函数与raise()函数
kill函数:向指定的一个进程发送一个信号
raise函数:向指定的一个进程发送一个信号
-等待信号
在进程结束时,进程在任何时间点都可以接受到信号
需要阻塞等待信号时,则可以调用pause()函数
(注意:pause函数一定要在收到信号之前调用)
-信号处理
信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理
信号处理有三种方式:
忽略:不进行处理
默认:按照信号的默认方式处理
用户自定义:通过用户实现自定义处理函数来处理,由内核来调用
signal函数:设置函数的处理方式(如果是自定义处理方式,提供函数地址,注册到内核中)
alarm函数:设置定时器的秒数(定时器信号)
(定时器的定时任务由内核完成的,alarm函数负责设置定时时间,并告诉内核启动定时器)
(当定时时间超时后,内核会向进程发出SLGALRM信号)
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号
-消息队列
-IPC
IPC:inter-process communication(进程间通讯)
IPC对象共有三种:消息队列,共享内存,信号量
ipcs:查询IPC对象
fork函数:生成IPC对象唯一的键
-消息队列简介
消息队列就是一个消息的列表,进程可以在消息队列中添加消息和读取消息
消息队列具有FIFO的特性,具有无名管道与有名管道的优势,可以支持任何俩个进程的进程间通讯
(消息队列是属于IPC的一种,由内核维护与管理)
-消息队列相关命令
ipcs -q:查看消息队列
mesgget函数:创建消息队列
mesgctl函数:删除消息队列
msgsnd函数:发送消息
megrcv函数:接收消息
-共享内存
-共享内存简介
共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中,减少数据在内核空间缓存
共享内存是一种效率较高的进程间通讯的方式
-共享内存相关命令
ipcs -m:查看共享内存
shmget函数:创建共享内存
shmctl函数:删除共享内存
shmat函数:共享内存映射
shmdt函数:解除共享内存映射
-信号量
-资源竞争
资源竞争:当多个进程同时访问共享资源时,会产生资源竞争,导致数据混乱
临界资源:不允许同时有多个进程访问的资源,包括硬件资源与软件资源
(硬件资源包括CPU,内存,存储器,以及其他外围设备;软件资源包括共享数据结构,共享代码段)
临界区:访问临界资源代码
-同步与互斥
互斥:同一时刻只有一个进程访问临界资源
同步:在互斥的基础上增加了进程对临界资源的访问顺序
(进程主要的同步与互斥手段是信号量)
-信号量简介
信号量的使用类比红绿灯
信号量:由内核维护的整数,其值被限制为大于0或等于0
信号量可以执行如下操作:
1.将信号量设置成一个具体的值
2.在信号量当前值的基础上加(或减)上一个数
3.等待信号量的值为0
一般信号量分为二值信号量与计数信号量
二值信号量:一般指的是信号量的值为1,可以理解为值对应一个资源
计数信号量:一般指的是信号量的值大于等于2,可以理解为对应多个资源
-信号量相关命令
ipcs -s:查询信号量
semget函数:创建信号量集合
semctl函数:初始化信号量(信号量集合删除:调用量集合semctl函数,设置命令IPC_RMID)
semmop函数:操作信号量