进程的概念以及应用
占用内存空间的正在运行的程序即为进程
从操作系统的角度看,进程是程序流的基本单位,若创建多个进程,则操作系统将同时运行。有时一个程序运行过程中也会产生多个进程。
CPU核的个数与进程数 :一个CPU中可能包含多个运算设备(核)。核的个数与可同时运行的进程数相同。
若进程数超过了核数,进程将分时使用CPU资源。
进程ID:1 要分配给操作系统启动后的(用于协助操作系统)首个进程,因此用户进程无法得到ID值1
创建进程
通过调用fork函数创建进程
1 | pid_t pid = fork(); |
fork函数将创建调用的进程副本,也就是说,是复制正在运行的、调用fork函数的进程。另外两个进程都将执行fork调用后的语句(fork函数返回后)。同一个进程、复制相同的内存空间,之后的程序要根据fork函数的返回值加以区分。即利用fork函数的如下特点区分程序执行流程。
父进程:fork函数返回子进程ID.
子进程:fork函数返回0
僵尸进程
产生僵尸进程的原因:
调用fork函数产生子进程的终止方式。
传递参数并调用exit函数。
main函数中执行return语句并返回值。
向exit函数传递的参数值和main函数的return语句返回值都是会传递给操作系统。而操作系统不会销毁子进程,直到这些值传递给产生改子进程的父进程。处在这种状态下的进程就是僵尸进程。将子进程变成僵尸进程的正是操作系统。
僵尸进程怎么销毁呢? 应该向创建子进程的父进程传递子进程的exit参数值或return语句的返回值。
操作系统不会主动把这些值传递给父进程。只有父进程主动发起请求(函数调用)时,操作系统才会传递该值。如果父进程未主动要求获得子进程的结束状态值,操作系统将一直保存,并让子进程长时间处于僵尸进程状态。也就是说,父母要负责收回自己生的孩子。我们来创建一个僵尸进程。
1 | void lession82() |
信号处理函数
1 | void signal_func(int sig) |
进程通信:为了方便进程交换信息,在内核提供缓冲区进行数据交换的机制。
交换数据时基于开发 / 权限 的
缓冲区两边像是有俩齿轮一样,如果是比作商店的话,一边是放进去东西,一边是拿出去东西,形成了消息处理的流水线。
进程间通信:管道
单管道
为了完成进程间通信,需要创建管道。管道并非属于进程的资源,而是和套接字一样,属于操作系统(也就不是fork函数的复制对象)。所以,两个进程通过操作系统提供的内存空间进行通信。
父进程调用该函数时将创建管道,同时获取对应于出入口的文件描述符号
Filedes[0]
通过管道接收数据时使用的文件描述符,即管道出口。
Fledes[1]
通过管道传输数据时使用的文件描述符,即管道入口。
父进程创建子进程的时候,自己的资源镜像复制到子进程里面,所以父进程创建了管道子进程是有这个信息的。
1 | void singlePipe() |
双管道
1个管道无法完成双向通信任务,有时候需要创建2个管道,各自负责不同的数据流动即可。
1 | void doublePipe() |
进程间通信:FIFO
对比pipe管道,他已经可以完成在两个进程之间通信的任务,不过它似乎完成的不够好,也可以说是不够彻底。它只能在两个有亲戚关系的进程之间进行通信,这就大大限制了pipe管道的应用范围。
fifo
管道的本质是操作系统中的命名文件,当然Linux的理念就是万物皆文件,它在操作系统中以命名文件的形式存在,我们可以在操作系统中看见fifo
管道,在你有权限的情况下,甚至可以读写他们。
内核会针对fifo
文件开辟一个缓冲区,操作FIFO文件,可以操作缓冲区,实现进程通信。一旦使用mkfifo
创建了一个FIFO,就可以使用open打开它,常见的文件IO函数都可以用于FIFO
。如:close
、read
、write
、unlink
等 .
这样的话 一个进程对应一个管道,大大减少了管道的数量。
打开FIFO文件的时候,read端会阻塞等待write端打开open,write端同理,也会阻塞等待另外一端打开。
进程间通信:共享内存
共享内存允许不同进程之间共享同一段逻辑内存,对于这段内存,它们都能访问,或者修改它,没有任何限制。所以它是进程间传递大量数据的一种非常有效的方式。“共享内存允许不同进程之间共享同一段逻辑内存”,这里是逻辑内存。也就是说共享内存的进程访问的可以不是同一段物理内存,这个没有明确的规定,但是大多数的系统实现都将进程之间的共享内存安排为同一段物理内存。
使用共享内存的步骤通常是:
1)创建或获取一段共享内存;
2)将上一步创建的共享内存映射到该进程的地址空间;
3)访问共享内存;
4)将共享内存从当前的进程地址空间分离;
5)删除这段共享内存;
1 | void lession90() |
进程间通信:信号量
- 假设没有信号量,父子进程可能会同时访问共享内存,导致数据不一致。例如,父进程可能正在写入数据,而子进程同时尝试读取尚未完全写入的数据。通过信号量的 P 操作(
semop
函数,sem_op
为 - 1)和 V 操作(semop
函数,sem_op
为 1),可以确保在某一时刻只有一个进程能够访问共享内存中的关键区域。比如,子进程在读取共享内存中的学生信息之前,先对信号量进行 P 操作,等待父进程完成写入并执行 V 操作释放资源后,子进程才能读取,从而避免了数据竞争和不一致性。
原来共享内存有很严重的时间差,降低了效率。
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
1 | void lession91() |
进程间通信:消息队列
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道FIFO一样,每个数据块都有一个最大长度的限制。
1️⃣ msgget
函数 创建和访问一个消息队列 在示例中,子进程和父进程都有
2️⃣ msgsnd
函数 用来把消息添加到队列
3️⃣ msgrcv
函数 用来从一个消息队列获取消息
4️⃣ msgctl
函数 用来控制消息队列,它与共享内存的shmctl
函数类似,删除消息队列
1 | void lession92() |
待续。。。。