进程线程的安排:
DAY1-DAY2:1、什么是进程?
2、进程间通信:传统的unix通信方式:1.无名管道;2.有名管道;3.信号
DAY3-DAY4:1、systemV提供的通信方式:1.共享内存;2.消息队列;3.信号量
2、线程
DAY1:
1.什么是进程?
在Windows下,任务管理器中可以看到进程,图中,应用中的进程,我们随时可以关闭,但后台进程是开机则打开,关机则关闭。
在Linux下,我们可以通过以下快捷键看相关进程。如:ps -aux、ps -ef、ps -aux、ps -axj、top、pstree等操作。(希望读者自己私下进程相关操作)
到底什么是进程呢?
程序的一次动态执行过程,包括创建、调度和消亡。
1.1 进程和程序的区别
程序(a.out)是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何任何执行的概念。
进程(./a.out)是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡。
1.2 进程是程序执行和资源(内存)管理的最小单位
从图我们可以看出,进程与进程之间存在“竞争”关系。
为什么说进程是资源管理的最小单位?
因为每一个进程都会有0~4G的虚拟内存。
1.3 如何区分不同的进程-->PID(子进程号)、PPID(父进程号)
进程是由进程创建的,有父进程和子进程。
2.进程的类型
(1)交互进程
交互进程是由shell控制和运行的。交互进程既可以在前台运行,又可以在后台运行。
(2)批处理进程
该类进程不属于某个终端。它被提交到一个队列中以便执行。
(3)守护进程
该类进程在后台运行。没有亲缘关系,一般在Linux启动时开始执行,系统关闭时才结束。
3、进程的状态
(1)运行态:此时进程正在运行,或者准备运行。
(2)等待态:此时进程在等待一个时间的发生或某种系统资源。
(3)停止态:此时进程被中断。
(4)死亡态:这是一个已终止的进程,但还在进程向量数组中占已有一个task_struct(PCB)结构。PCB结构(内核空间):包括进程控制块本身、打开的文件表象、当前目录、当前终端信息、线程基本信息、可访问的内存地址空间、PID、PPID、UID、EUID等,也就是说,内核通过PCB可以访问到进程的所有资源。
4、进程的模式
进程的执行模式分为用户模式和内核模式。
调度进程:nice-----按用户指定的优先级运行进程。
renice---改变正在运行进程的优先级。
优先级范围:-20~19,值越小,优先级越高。
5、进程相关的系统调用
5.1 创建进程
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
//创建pid
pid_t pid = fork();
//判断pid的大小
if(pid < 0)
{
perror("pid create error!\n");
return -1;
}
if(pid > 0)
{
//父进程
}
else
{
//子进程
}
return 0;
}
子进程会赋值父进程的几乎所有信息,在用户空间将赋值父进程用户空间的所有数据,复制父进程内核空间PCB中绝大部分信息。
注意:fork()函数是父进程调用的,在父进程返回值之前,子进程已经创建好了,父进程和子进程的返回值是不一样的。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
//创建pid
pid_t pid = fork();
//判断pid的大小
if(pid < 0)
{
perror("pid create error!\n");
return -1;
}
if(pid > 0)
{
//父进程
while(1)
{
printf("I am your father!my son !\n");
sleep(1);
}
}
else
{
//子进程
while(1)
{
printf("Yes,you are my father,but i want to be stronger!\n");
sleep(1);
}
}
return 0;
}
在子进程创建成功之后,它会和父进程抢占资源,存在竞争关系。谁先调度,有调度算法去决定。
5.2 getpid()和getppid()
5.2.1 getpid()
//头文件
#include<sys/types.h>
#include<unistd.h>
//函数原型
pid_t getpid(void);
/*
函数功能:获取进程的PID号
返回值:返回调用进程的PID号
*/
pid_t getppid(void);
/*
函数功能:获取进程的PPID号
返回值:返回调用进程的PPID号
*/
进程的后台顺序:./a.out(子进程)---->./a.out(父进程)---->bash---->terminal---->systemd---->init 。此过程可以用ps -axj去查看他们的子进程号和父进程号。
5.3 进程退出
进程的退出有exit()/_exit()以及mian函数中的return
注意:(1)exit会刷新缓冲区(2)_exit不会刷新缓冲区。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
//创建pid
pid_t pid = fork();
//判断pid的大小
if(pid < 0)
{
perror("pid create error!\n");
return -1;
}
if(pid > 0)
{
int i = 0;
//父进程
while(1)
{
if(i>4)
{
printf("hello process!\n");
//exit(0);
//_exit(1);
return 0;
}
printf("I am your father!my son !\n");
sleep(1);
i++;
}
else
{
//子进程
while(1)
{
printf("Yes,you are my father,but i want to be stronger!\n");
sleep(1);
}
}
return 0;
}
5.4 wait和waitpid
5.4.1 wait函数
调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。
5.4.2 waitpid函数
5.4.2 waitpid函数
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
//创建pid
pid_t pid = fork();
//判断pid的大小
if(pid < 0)
{
perror("pid create error!\n");
return -1;
}
if(pid > 0)
{
//wait(NULL);//阻塞函数,等待子进程退出
wait(-1,NULL,0);//同wait
//父进程
while(1)
{
int ret = waitpid(-1,NULL,WNOHANG);
printf("ret = %d\n",ret);
printf("I am your father!my son !\n");
sleep(1);
}
else
{
int i = 0;
//子进程
while(1)
{
if(i > 3)
{
exit(0);
}
printf("Yes,you are my father,but i want to be stronger!\n");
sleep(1);
i++:
}
}
return 0;
}
获取子进程退出的状态:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
//创建pid
pid_t pid = fork();
//判断pid的大小
if(pid < 0)
{
perror("pid create error!\n");
return -1;
}
if(pid > 0)
{
//wait(NULL);//阻塞函数,等待子进程退出
wait(-1,NULL,0);//同wait
//父进程
while(1)
{
int ret = waitpid(-1,NULL,WNOHANG);
if(WIFEXITED(status))
{
printf("该进程正常退出,退出状态为:%d\n",ret);
}
else if(WIFSIGNALED(status))
{
printf("退出信号为:%d\n",ret);
}
printf("I am your father!my son !\n");
sleep(1);
}
else
{
int i = 0;
//子进程
while(1)
{
if(i > 3)
{
exit(0);
}
printf("Yes,you are my father,but i want to be stronger!\n");
sleep(1);
i++:
}
}
return 0;
}
6、孤儿进程
父进程先退出,子进程被systemd进程收养,变为后台进程。
7、僵尸进程
子进程先于父进程退出,父进程没有回收子进程的资源(task_struct),此时子进程就变为僵尸进程。
注意:如果父进程一直不退出,子进程就一直保持僵死状态,直到父进程退出,task_struct就会被systemed回收。
8、exec函数族
不想让子进程执行父进程的代码段。
l:表示列表
v:向量(数组)
p:系统会自动从环境变量“$PATH”所包含的路径中进行查找(不用添加路径)。
DAY2:
1、守护进程
1.1特点
(1)在后台运行,与终端无关。
(2)在系统不够启动时运行,系统关闭时停止运行。
1.2进程与终端的关系
区别:前台:进程与终端具有血缘关系;控制终端一样。
后台:与终端无关。
想要实现守护进程:
(1)不能与终端具有血缘关系。(2)守护进程没有控制终端(备注:?表示没有控制终端)
1.3进程组
进程组是一个或多个进程的集合。进程组由进程组ID来唯一识别。
每一个进程组都有一个组长进程,进程组ID就是组长进程的进程号。
1.4会话组
会话组就是一个或者多个进程组的集合。
1.5实现效果
1. 守护进程自成立一个进程组和会话组,也就是PID、PGID、SID是一样的,而且该进程的父进程只能是systemd,不能是终端。
2. 没有控制终端,也就是说TTY变成?
1.6守护进程的创建步骤
1.创建子进程,父进程退出
备注: 第一步完成之后,子进程就在形式上做到了与终端脱离关系。
2.在子进程中创建新的会话
创建之后,PID、PGID和SID都一样了。
3.改变当前目录为根目录
4.重设文件权限掩码
为啥要设置文件掩码?文件掩码是指文件权限中被屏蔽掉对应位,把文件掩码设置为0,可以增加该守护进程的灵活性。
5.关闭文件描述符
整体代码
2、UNIX平台进程通信方式
常用的进程间通信方式:
传统的进程间通信方式:有名管道、无名管道和信号
System V IPC对象:共享内存、消息队列和信号灯
BSD:套接字(socket)
2.1 无名管道
特点:
(1)只能用于具有亲缘关系(父子进程、兄弟进程)的进程之间的通信
(2)半双工的通信方式,具有固定的读端和写端
(3)管道可以看成一种特殊的文件(内核中的一块文件)
管道是基于文件描述符的通信方式。当一个管道建立时,他会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,fd[1]固定用于写管道。
为什么无名管道只能用于具有亲缘关系之间的通信?
父进程建立了管道,获得了文件描述符,子进程继承了父进程的文件描述符(fd[0]、fd[1])
注意:(1)当管道中无数据时,读操作会阻塞;(2)向管道中写入数据时,Linux将不保证写入的原则性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞;(3)只有在管道的读端存在时,向管道中写入数据才有意义,否则向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)。
完成代码:
测试管道破裂:
2.2有名管道
特点:(1)无名管道只能用于具有亲缘关系的进程之间,这就限制了无名管道的使用范围。(2)有名管道可以使互不相关的两个进程相互通信。有名管道可以通过路径名来指出,并切在文件系统中可见。(3)进程通过文件IO来操作有名管道。(4)有名管道遵循先进先出的规则(5)不支持如lseek()操作。
写端:
读端:
总结:
管道文件的存在的意义
进程1创建并打开有名管道得到了一个内存文件(管道文件)+文件描述符。
进程2通过进程1创建的管道文件就可以得到一个同样的文件描述符,最终进程1和进程2就可以通信了。
无名管道:因为父进程创建并打开管道,得到了管道文件描述符,而子进程可以继承父进程的文件描述符,所以就可以进行通信了。
注意:当只有读端的时候,系统会阻塞,直到一个进程(包括自己)以另一种方式打开管道