华清远见25072班I/O学习day3
重点内容:
一、多进程引入
1.1 引入目的
程序员写程序时,一个程序可能由多个任务组成,如果使用的是单进程,或单任务,那么当该任务执行阻塞时,其他任务就无法执行,必须等到该任务解除阻塞后,才能去执行其他任务。
多进程或多线程,可以解决同一个程序中多个任务并发执行的情况
1.2 进程的概念
1> 进程是程序的一次执行过程,是有生命周期的:创建态、就绪态、运行态、阻塞态、死亡态
2> 进程是程序资源分配的基本单位,系统会给每个进程分配4G的虚拟内存,分为0--3G的用户空间和3--4G的内核空间
多个进程共享内核空间,用户空间相互独立
3> 进程是一个动态的过程,有生命周期的概念,分为创建态、就绪态、运行态、阻塞态、死亡态
4> 进程在内核空间中存储在一个名为task_struct的结构体中(PCB)
5> 单核cpu处理多任务是,一般使用的是时间片轮询机制
6> 进程与程序的区别:程序是静态的,是存储在磁盘上的二进制代码。进程是动态的,是有生命周期的
7> 进程的组成:进程控制块(PCB)、数据段、程序段
1.3 进程的种类
进程一共分为三大类:交互进程、批处理进程、守护进程
1> 交互进程:他是由shell控制,用于直接跟用户进行交互的进程。例如:vim编辑器、文本编辑器
2> 批处理进程:本质上维护了一个队列,被放入队列中的进程会统一被调度。例如gcc编译器的一步到位的编译
3> 守护进程:脱离了终端而存在的进程,随着系统的启动而开始,随着系统的结束而终止。例如:服务进程
1.4 进程号的概念
每个进程在系统中都有一个唯一的标识位,用一个整数表示,这就是该进程的进程号(PID)
1> PID(process ID):当前进程的进程号
2> PPID(parent process ID):当前进程的父进程的进程号
1.5 特殊的进程
1> 0号进程:也成为 idel 进程,他是操作系统启动后执行的第一个进程,这个进程也叫空闲进程,当没有其他进程执行时,系统会默认执行该进程。1号进程和2号进程都是由0号进程创建出来的。
2> 1号进程:也称 init 进程,该进程由0号进程产生,主要完成系统创建时一些软件硬件的初始化工作。当其他进程的父进程死亡后,会托管其子进程
3> 2号进程:也称 kthreadd,该进程由0号进程产生,也成为调度进程,当某个就绪进程时间片轮到时,该进程负责进程的调度工作
4> 孤儿进程:当前进程的父进程死亡后,但是当前进程还没有结束,那么当前进程称为孤儿进程,孤儿进程会由1号进程收养
5> 僵尸进程:当前进程已经死亡,但是其父进程没有为其收尸,那么该进程为僵尸进程
1.6 进程的相关指令
1> 查看进程信息的命令:ps ,跟不同的选项,执行不同的操作
ps -ef:显示进程之间的关系
ps -ajx:可以显示进程的状态
ps -aux:可以查看进程资源使用情况
2> top或htop
可以动态展示进程的占用情况
3> pstree:展示进程树,可以显示进程的父子关系
4> 查看给定进程的进程号:pidof 进程名
4> kill : 向进程发送信号,发信号的格式
kill -信号名(号) 进程号
能够发送的信号号有:可以通过指令 kill -l查看
1.7 进程的状态
1> 主要状态一共有五个:创建态、就绪态、运行态、阻塞态、死亡态
2> 程序中的进程的状态显示:可以通过指令 man ps查看进程的状态
进程的状态由两部分组成:主状态和附加态
主状态:
D 不可中断的休眠态 (usually IO)
R 运行态 (on run queue)
S 可中断的休眠态 (waiting for an event to complete)
T 暂停态,会给出作业号进行控制 t 程序调试时的暂停态 W 已经弃用 X 死亡态 (should never be seen)
Z 僵尸态
附加态:
< 高优先级的进程 (not nice to other users)
N 低优先级的进程 (nice to other users)
L 锁到内存中的进程 (for real-time and custom IO)
s 会话组组长,默认为当前终端
l 包含多线程的进程 (using CLONE_THREAD, like NPTL pthreads do)
+ 表示是前台运行的进程
二、多进程编程
2.1 进程的创建函数 fork
pid_t fork(void);
功能:创建一个子进程,并返回相关进程号
参数:无
返回值:对于父进程而言,得到的是子进程的pid号,对于子进程而言,得到的是0
注意:当fork执行后,子进程会完全拷贝父进程的所有资源,并且父子进程的用户空间完全独立 父子进程执行是没有先后顺序的,遵循时间片轮询机制
2.2 进程号的获取(getpid() / getppid())
pid_t getpid(void); //获取当前进程的pid号,返回值就是当前进程的id号,不会失败
pid_t getppid(void); //获取当前进程的父进程的pid号,返回值就是父进程id,不会失败
2.3 进程的退出(exit/ _exit)
void exit(int status);
功能:是一个库函数,会导致调用该函数的进程结束
参数:退出时的状态,会将该值和0377进行位与运算后的值返回给其父进程 EXIT_SUCCESS(0):表示正常退出
EXIT_FAILURE(1):表示异常退出
返回值:无
注意:对于标准库提供的该函数而言,退出进程之前,会自动将所有的已经打开的标准IO缓冲区刷新,并关闭文件指针后结束进程
void _exit(int status);
功能:直接退出当前进程(不会刷新标准IO的缓冲区)
参数:退出时的状态,会将该值和0377进行位与运算后的值返回给其父进程 EXIT_SUCCESS(0):表示正常退出
EXIT_FAILURE(1):表示异常退出
返回值:无
2.4 进程资源的回收
pid_t wait(int *wstatus);
功能:阻塞等待当前进程的任意一个子进程的退出,并回收该子进程的资源给系统
参数:用于接受子进程退出时的状态,一般不接受,填NULL即可
返回值:成功回收返回值回收的那个子进程的pid号,失败返回pid_t(-1)并置位错误码
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:以阻塞或者非阻塞的方式回收子进程资源
参数1:要回收的进程id号
>0:指定要回收的子进程pid号
=0:回收父进程所在的进程组中的任意一个子进程
-1:回收任意一个子进程
<-1:表示去回收给定的pid的绝对值进程号所在的进程组中的任意一个子进程
参数2:用于接受子进程退出时的状态,一般不接受,填NULL即可
参数3:是否阻塞的选项
0:表示阻塞回收
WNOHANG:表示非阻塞回收
返回值:
>0:表示返回的是成功回收的子进程的pid号
=0:表示以非阻塞形式回收资源时,但是没有回收到子进程时返回0
-1:函数调用出错,并置位错误码
作业:
1> 使用多进程完成两个文件的拷贝:父进程拷贝前一半内容,子进程拷贝后一半内容
#include <25072head.h>
int main(int argc, const char *argv[])
{
pid_t pid = -1; //定义变量记录进程号
int fd=-1;
if((fd = open(argv[1], O_RDONLY)) == -1)
{
perror("open errpr:");
return -1;
}
int fd1=-1;
if((fd1 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0664)) == -1)
{
perror("open error");
return -1;
}
int len=lseek(fd,0,SEEK_END);
lseek(fd,0,SEEK_SET);
char buf;
pid=fork();
if(pid>0)
{
for(int i=0;i<len/2;i++)
{
read(fd,&buf,1);
write(fd1,&buf,1);
}
wait(NULL);
exit(EXIT_SUCCESS);
}
else if(pid==0)
{
for(int i=len/2;i<len;i++)
{
read(fd,&buf,1);
write(fd1,&buf,1);
}
exit(EXIT_SUCCESS);
}
return 0;
}
2> 父进程创建两个子进程,子进程1负责拷贝文件前一半内容,子进程2负责拷贝文件后一半内容,父进程阻塞回收两个子进程资源
#include <25072head.h>
int main(int argc, const char *argv[])
{
pid_t pid = -1; //定义变量记录进程号
pid_t pid1=-1;
int fd=-1;
if((fd = open(argv[1], O_RDONLY)) == -1)
{
perror("open errpr:");
return -1;
}
int fd1=-1;
if((fd1 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0664)) == -1)
{
perror("open error");
return -1;
}
int len=lseek(fd,0,SEEK_END);
lseek(fd,0,SEEK_SET);
char buf;
pid=fork();
if(pid==0)
{
for(int i=0;i<len/2;i++)
{
read(fd,&buf,1);
write(fd1,&buf,1);
}
exit(EXIT_SUCCESS);
}
pid1=fork();
if(pid1==0)
{
lseek(fd,len/2,SEEK_SET);
for(int i=len/2;i<len;i++)
{
read(fd,&buf,1);
write(fd1,&buf,1);
}
exit(EXIT_SUCCESS);
}
if(pid>0)
{
sleep(5);
waitpid(pid, NULL, 0); // 等待第一个子进程
sleep(5);
waitpid(pid1, NULL, 0); // 等待第二个子进程
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
3> 详细绘制思维导图
4> 牛客网刷题