进程与线程(一)
- 理解什么是并发编程
- 进程的相关概念
- 什么是进程
- 对比进程和程序
- 理解进程是一个独立的可调度的任务
- 理解进程是程序执行和资源管理的最小单位
- 进程状态转换图
- 进程的种类
- 进程相关命令
- 进程状态标志
- ps命令
- -aux:
- -axj:(可以查看到进程的PPID)
- pstree
- kill命令
- 进程相关的系统调用
- 进程创建
- fork()
- vfrok()
- fork和vfork的区别
- 获取进程ID和父ID的方法
- 进程的退出
- 僵尸进程和孤儿进程
- 僵尸进程
- 孤儿进程
- 回收僵尸进程的方法
- wait阻塞回收子进程的退出资源
- waitpid非阻塞回收子进程的退出资源
理解什么是并发编程
并发是什么?
—》多个任务同时运行
任务是什么?
—》就是一个应用程序
思考:程序是如何产生的?
—》程序:一组能被计算机直接识别的有序指令的集合
所以:程序是被编译生成的 eg:gcc test.c -o App (App就是一个程序)
程序在外存(U盘,硬盘)上存储(静态的概念)
一个程序一旦被运行起来,就会产生一个进程(动态的:被创建,被调度,被消亡)
总结:Linux下的多任务编程就是多进程/多线程去协同工作,将某一个事情搞定的过程该过程避免不了多进程/多线程之间数据交互的问题(进程间通信的技术)
进程的相关概念
什么是进程
概念:进程就是程序的一次动态执行过程
对比进程和程序
程序:一组存储在磁盘上的有序指令的集合,是一个静态且没有任何执行的概念
进程:运行一个程序,就会拥有一个进程,它是程序的一次动态执行过程,动态性:创建,调度,消亡
理解进程是一个独立的可调度的任务
一个进程是一个独立的个体,意味着拥有自己专属的地址空间
地址空间图解:
调度:调用进程/线程,调用动作是由CPU来完成的!
再次理解:
CPU调用进程/线程本质:执行它!
思考:Linux下如何实现多任务(并发编程)的?(借助于CPU调度)
注意:单核的CPU同一时刻只能处理一个进程/线程,并发如何实现?
—》Linux下实现并发的思想:引入“时间片”的概念时间片?
–》每一个任务,操作系统为其分配了一段执行的时间,在这段时间之内处理你,超时则下次再处理,时间片很小,小到用户都可能感知不到时间片的概念,因此每一个任务都会有一个时间片,他们之间采用时间片轮转的概念,被加入到一个循环队列中(注意:该资源队列并不遵循先进先出的原则,多个任务的调度的是随机的,且存在优先级的概念!),多个任务(程序的执行过程)存在状态的切换。
理解进程是程序执行和资源管理的最小单位
程序执行的最小单位:每运行一个程序,就会产生一个进程
资源管理的最小单位:每一个进程都会拥有一个独立的地址空间(资源)
进程状态转换图
进程的状态:运行态,就绪态,等待态,停止态,死亡态,僵尸态
图解:运行态,等待态,死亡态
进程的种类
(1)交互进程:该进程需要人机交互,用户需要给该进程一个信息的反馈。(shell终端)
(2)批处理进程:也称批处理文本或者批处理脚本,他们采用轮询的概念去依次执行。
(3)守护进程:也称“精灵进程”,“监测进程”,”后台进程“,不受终端的控制!运行在后台。
进程相关命令
进程状态标志
ps命令
作用:打印出当前运行的进程
使用ps命令时,一般都会搭配选项来使用:
常用的选项
-aux:
-axj:(可以查看到进程的PPID)
pstree
作用:以树形关系呈现当前终端所有进程的所属关系
kill命令
作用:发送信号
常用的:杀死进程
方法:kill -9 pid; //pid为需要杀死的进程ID号
Linux下共有64个信号:
进程相关的系统调用
明确:进程都是被进程创建出来的
1号进程:init进程
init进程创建出一个systemd进程!
终端也是一个进程,也是被创建出来的
故:想要得到一个子进程,就必须去创建!!!
进程创建
fork()
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
功能:创建一个进程
参数:无参
返回值:pid_t是int的别名,含义:
小于0,代表创建子进程失败,可以打印错误码查看原因
等于0,是在子进程中返回的
大于0,是在父进程中返回的,且含义为子进程的ID号(PID)
验证子进程的数据段是也是拷贝于父进程的(fork()):
验证fork之前,子进程会悉数拷贝父进程的所有内容:fork之前父进程有的,子进程都会拷贝一份
fork()的原理:
调用进程(称为父进程)通过复制自己,创建出一个新进程(称为子进程),子进程获得父进程的数据空间、堆和栈的副本,即父子进程的空间是独立的,创建出的子进程从fork返回处开始运行。
总结fork():
fork创建的子进程:
1、会完美拷贝父进程中的所有资源(堆栈段,代码段,数据段…)
2、子进程从fork的下一句话开始,会存在父子进程,为了区分两个进程到底谁在执行?
—》通过返回值来判断:等于0(子进程在执行)。大于0(父进程在执行)
3、子进程永远都是在fork的下一句开始执行的!(会用到fork之前父进程定义过的一些资源。)、
4、父子进程的执行次序不确定的!
5、父子进程的地址空间独立,互不影响,父子进程可以被CPU进行调度。
vfrok()
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
用法同fork()!!!
vfork()的原理
vfork()函数创建新进程时并不复制父进程的地址空间,刚开始是运行在父进程的地址空间上的,且保障了子进程会优先被执行,当子进程执行了exec或者exit之后,父进程才有可能被调度。如果在调用exec或者exit之前,子进程依赖于父进程的进一步动作,则会导致死锁!
注意:使用vfork函数之后,子进程内部必须使用exec或者exit函数,否则会造成死锁的现象。
fork和vfork的区别
1、
fork():子进程拷贝父进程的数据段,代码段
vfork():子进程与父进程共享数据段
2、
fork()父子进程的执行次序不确定
vfork()保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
3、
vfork()保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁,所以使用vfork创建的子进程必须使用exec重新启动一个新的程序或者exit()使得子进程退出
获取进程ID和父ID的方法
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);//获取当前进程的ID号
pid_t getppid(void);//获取当前进程的父进程ID号
以上两个函数的返回值就是获取的ID号的结果!!!
进程的退出
_exit()函数的作用最为简单:直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。
exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的"清理I/O缓冲"一项。
僵尸进程和孤儿进程
僵尸进程
子进程先于父进程退出,而父进程没有回收子进程的退出资源(一小部分的PCB:struct_task的结构体),此时的子进程就会沦为僵尸进程!
僵尸进程的危害:内存泄漏,因此需要避免僵尸进程的产生!
如何避免僵尸进程产生?
----》子进程的退出资源本应该是父进程来回收,因此将回收子进程的退出资源这件事让父进程使用某种手段进行处理即可!
孤儿进程
父进程先于子进程退出,此时的子进程会沦为孤儿进程,会被systemd进程收养,孤儿进程没有任何坏处
回收僵尸进程的方法
wait阻塞回收子进程的退出资源
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
//定义局部变量
int a = 0;
//使用fork来创建一个子进程
pid_t pid = fork();
if(pid < 0)
{
perror("fork child process error");
return -1;
}
else if(0 == pid)
{
//子进程在执行代码
while(1)
{
a++;
printf("子进程:a = %d\n",a);
printf("I am child process!\n");
sleep(1);
}
}
else
{
//使用wait阻塞回收子进程的退出资源
printf("等待回收子进程中......\n");
pid_t exitPID = wait(NULL);//阻塞
printf("exitPID = %d的子进程已被回收!\n", exitPID);
//父进程在执行代码
while(1)
{
a++;
printf("父进程:a = %d\n",a);
printf("I am parent process!\n");
sleep(1);
}
}
printf("test!!!\n");
return 0;
}
waitpid非阻塞回收子进程的退出资源
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
//定义局部变量
int a = 0;
//使用fork来创建一个子进程
pid_t pid = fork();
if(pid < 0)
{
perror("fork child process error");
return -1;
}
else if(0 == pid)
{
//子进程在执行代码
while(1)
{
a++;
printf("子进程:a = %d\n",a);
printf("I am child process!\n");
sleep(1);
if(a >= 5)
{
exit(0);//子进程主动退出 返回-1此时对方查看的是255
}
}
}
else
{
//父进程在执行代码
while(1)
{
printf("等待回收资源中...\n");
//引入waitpid回收子进程的退出资源,关心子进程的退出状态
int status;
pid_t exitPID = waitpid(-1, &status, WNOHANG);
if(exitPID < 0)
{
perror("waitpid error");
}
else if(0 == exitPID)
{
printf("未检测到有子进程结束...\n");
}
else
{
//判断子进程是正常退出还是异常退出
if(WIFEXITED(status))
{
//进入if则代表正常退出
printf("正常退出!\n");
}
else
{
printf("非正常退出!\n");
}
printf("已成功回收exitPID = %d的子进程资源!\n", \
exitPID);
//获取子进程的退出状态数值
printf("退出状态数值:%d\n",WEXITSTATUS(status));
}
a++;
printf("父进程:a = %d\n",a);
printf("I am parent process!\n");
sleep(1);
}
}
printf("test!!!\n");
return 0;
}