1.什么是进程
概念
进程是程序的一次动态执行过程,包括创建、调度、消亡
进程和程序的区别
程序(a.out)是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
进程(./a.out)是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡
进程是程序执行和资源(内存)管理的最小单位
如何区分不同的进程-->PID
进程是由进程创建的,我们有父进程和子进程
进程的类型
(1)交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。
(2)批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
(3)守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
进程运行状态
(1)运行态:此时进程或者正在运行,或者准备运行。
(2)等待态:此时进程在等待一个事件的发生或某种系统资源。
可中断
不可中断
(3)停止态:此时进程被中止
(4)僵死态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构体。
为了更好的管理Linux所访问的资源,系统在内核头文件include/linux/sched.h定义了进程控制块(PCB)结构体task_struct来管理每个进程的资源
内核空间进程资源即PCB相关的信息,包括进程控制块本身,打开的文件表项、当前目录、当前终端信息、线程基本信息、可以访问内存地址空间、PID、PPID、UID、EUID等,也就是说,内核通过PCB结构体可以访问到进程的所有资源信息
(5)死亡态
进程的模式
进程相关的的系统调用
fork
头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t fork(void);
返回值
![](https://i-blog.csdnimg.cn/blog_migrate/28c346454e528fa15d8dfac0b5027cb0.png)
![](https://i-blog.csdnimg.cn/blog_migrate/050d1505524c7d9b5343a0b11ef07bb8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/3ede730fc0700f8e58a288668fe989f1.png)
注意:fork之后,父进程和子进程存在资源竞争的关系,谁先调度由调度算法来决定
getpid、getppid
#include stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
//创建进程
pid t pid = fork();
if(pid > 0)
{
//父进程
while(1)
{
printfC "I am parent! ,pid is %d\n" ,getpid());
sleep(i);
}
}
else if(0 == pid)
{
//子进程
while(1)
{
printf("I an child! my id is %d ,ny parent id is xd\n",getpid(),getppid());
sleep(1);
}
}
return o;
}
进程退出
exit、_exit、return(主函数的return)
孤儿进程
父进程先于子进程退出,子进程会被systemd进程收养,子进程会变成后台进程
僵尸进程
子进程先退出,父进程没有及时回收子进程的资源(PCB结构体),此时就成为了僵尸进程
注意:父进程不退出,子进程会一直保持僵死状态,直到父进程退出,被systemd回收
wait、waitpid
如何避免僵尸进程,子进程退出时父进程及时回收子进程的资源
wait
waitpid
exec函数族
l:list 以列表的形式传参
v:vector 数组
p:path 系统会自动从环境变量“$PATH”所包含的路径中进行查找。
守护进程
守护进程是什么
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。
特点
(1)从系统启动开始运行,系统关闭时停止运行
(2)后台进程,与终端无关
进程与终端之间的关系
想要实现守护进程:
(1)不能和终端具有亲缘关系
(2)与终端无关,也就是要把控制终端变成 ?
进程组
进程组是一个或者多个进程的集合。进程组由进程组ID来唯一标识
每个进程组都有一个组长进程,进程组ID就是组长进程的进程号
会话期
会话期是一个或者多个进程组的集合
守护进程的创建步骤
(1)创建子进程,父进程退出
在父进程中执行fork并exit推出;
(2)在子进程中创建新会话
在子进程中调用setsid函数创建新的会话
(3)在子进程中调用chdir函数,让根目录 ”/” 成为子进程的工作目录;
(4)重设文件权限掩码
在子进程中调用umask函数,设置进程的umask为0;
(5)关闭文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);
通信的相关概念
通信的模式
单工:A-->B (键盘输入)
半双工:A--->B 或者 B--->A (对讲机)
双工:A--->B 同时 B-->A(打电话)
同步:(走路)
有规律
有顺序
有节奏
异步:(外卖)
没有规律
没有顺序
没有节奏
通信协议
双方约定好的规则
三要素
语法:即如何通信,包括数据的格式、编码和信号等级(电平的高低)等
语义:即通信内容,包括数据内容、含义以及控制信息等。
定时规则(时序):即何时通信,明确通信的顺序、速率匹配和排序。
常用协议
局域网中常用的通信协议主要包括TCP/IP、NETBEUI和IPX/SPX三种协议,每种协议都有其适用的应用环境
通信方式
常用的进程间的通信方式
传统的进程间的通信方式
无名管道
有名管道
信号
System V IPC对象
BSD
套接字(socket)
无名管道
1. 相关概念
- 管道的特点
- 半双工,数据在同一时刻只能在一个方向上流动
- 数据只能从管道的一端写入,另一端读出
- 写入管道中的数据遵循先进先出的规则
- 管道所传送的数据是无格式的,要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
- 管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
- 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
- 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。
- 管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。
2.创建无名管道
为什么无名管道只能用于具有亲缘关系间的进程的通信?
- 无名管道没有名字,不同的进程无法获取同一个文件描述符,具有亲缘关系的进程之间可以继承,从而得到同一个文件描述符。
注意
- 当管道中无数据时,读操作会阻塞
- 向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。
- 只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误) --> 管道破裂
有名管道(FIFO)
概念:
有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的。这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信。因此,通过 FIFO 不相关的进程也能交换数据。
区别(与无名管道的区别):
- 无名管道只能用于具有亲缘关系的进程之间,这限制了无名管道的使用范围。
- 有名管道可以是互不相关的两个进程互相通信。有名管道可以通过路径名来指出,并且在文件系统中可见。
特点:
- 进程通过文件IO来操作有名管道。
- 有名管道遵循先进先出的规则。
- 不支持像lseek()这样的操作。
创建有名管道:
1. 在程序内创建:使用mkfifo("管道名", 权限(八进制))函数。
2. 在终端创建:使用mkfifo命令 + 管道名。
注意:
- 只有当有名管道的读端或写端存在时,系统会阻塞,直到有另一个进程(包括自己)以另一种方式打开管道。
信号
概念:
信号是用于进程间通信和处理异步事件的一种机制。当某个事件发生时,操作系统会向进程发送一个信号,进程可以选择忽略、捕捉或按照默认方式处理该信号。
1. 进程信号的生命周期:
- 在进程收到信号前,进程内部可以识别信号,并对应信号做出相应的措施。进程信号属于进程内部的特有属性。
- 当信号到来时,进程可能不会立即处理信号,信号暂时被进程保存起来。
- 进程开始处理信号,进程处理信号有三种处理方法:
- 默认行为:按照信号内置的处理方案处理信号。
- 自定义行为:信号捕捉,按照自己设定的方法处理信号。
- 忽略信号:不做任何处理。
2. 列出所有的信号:
- 可以使用命令`kill -l`来列出系统中支持的所有信号。
3. 信号安装、信号注册函数:
- 信号安装是指为某个信号指定处理函数,当该信号到来时,执行相应的处理函数。
- 信号注册函数是指将信号与其处理函数进行关联,使得当信号到来时,执行对应的处理函数。常用的函数有signal()和sigaction()。
4. alarm:
- alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALRM信号。
5. pause:
- pause()函数用于将调用进程挂起,直到收到信号为止。
6. kill:
- kill函数用于向指定的进程或进程组发送信号。
7. raise:
- raise函数允许进程向自己发送信号。