Linux学习笔记(三)----进程
简介
进程是系统资源分配的最小单位,它曾经也是CPU调度的最小单位,但后面被线程所取代。
进程树
Linux系统通过父子进程关系串联起来,所有进程之前构成了一个多叉树结构。

孤儿进程
孤儿进程是指父进程已经结束,子进程还在执行的进程。那么此时此刻,该进程就变成了孤儿进程。
当进程变成孤儿进程后,系统会认领该进程,并为他再分配一个父进程(就近原则,爸爸的爸爸,爸爸的爸爸的爸爸)。
》当孤儿进程被认领后,就很难再进行标准准入输出对其停止了。因为切断了跟终端的联系,所以编码过程中要尽量避免出现孤儿进程
进程通讯(inter -Process Communication,IPC)
多个进程之间的内存相互隔离,多个进程之间通讯方式有如下几种:
管道(pipe)

匿名管道
半双工通信,即数据只能在一个方向上流动。只能在具有父子关系的进程之间使用。
其原理为:内核在内存中创建一个缓冲区,写入端将数据写入缓冲区,读取端从缓冲区中读取数据
点击查看代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
int pipefd[2];
pid_t pid;
char buffer[100];
// 创建管道
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
// 创建子进程
pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子进程:关闭写端,从管道读取数据
close(pipefd[1]);
read(pipefd[0], buffer, sizeof(buffer));
printf("Child process received: %s\n", buffer);
close(pipefd[0]);
} else {
// 父进程:关闭读端,向管道写入数据
close(pipefd[0]);
const char *message = "Hello, child process!";
write(pipefd[1], message, strlen(message) + 1);
close(pipefd[1]);
}
return 0;
}
命名管道
可以在任意两个进程之间进行通信,不要求进程具有亲缘关系;遵循先进先出(FIFO)原则。
其原理为:在文件系统中创建一个特殊的文件,进程通过读写这个文件来进行通信,因此文件读取是从头开始读,所以先进先出。
点击查看代码
// 写进程
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#define FIFO_NAME "myfifo"
int main() {
int fd;
const char *message = "Hello, named pipe!";
// 创建命名管道
mkfifo(FIFO_NAME, 0666);
// 打开命名管道进行写操作
fd = open(FIFO_NAME, O_WRONLY);
if (fd == -1) {
perror("open");
return 1;
}
// 向命名管道写入数据
write(fd, message, strlen(message) + 1);
close(fd);
return 0;
}
// 读进程
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_NAME "myfifo"
int main() {
int fd;
char buffer[100];
// 打开命名管道进行读操作
fd = open(FIFO_NAME, O_RDONLY);
if (fd == -1) {
perror("open");
return 1;
}
// 从命名管道读取数据
read(fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
close(fd);
// 删除命名管道
// 理论上,命名管道是可以重复使用的,其本质就是操作文件而已。只是不建议这么操作。
unlink(FIFO_NAME);
return 0;
}
共享内存(Shared Memory)
多个进程共享同一块内存地址,是最快的IPC方式。
其原理为:内核在物理内存中分配一块内存区域,多个进程将内存地址映射到自己的虚拟空间内,从而可以直接读写该区域。

点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
//创建一个共享内存对象
char shm_name[100]={0};
sprintf(shm_name,"/letter%d",getpid());
int fd= shm_open(shm_name,O_RDWR|O_CREAT,0664);
if(fd<0){
perror("shm_open");
exit(EXIT_FAILURE);
}
//给共享内存对象设置大小
ftruncate(fd,1024);
//内存映射
char *share= mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(share==MAP_FAILED){
perror("mmap");
exit(EXIT_FAILURE);
}
//映射完成,关闭fd连接
close(fd);
//使用内存,完成进程通讯
pid_t pid=fork();
if(pid<0){
perror("fork");
exit(EXIT_FAILURE);
}
if(pid==0){
//子进程
strcpy(share,"子进程,嘿嘿嘿嘿。");
}
else{
//父进程
waitpid(pid,NULL,0);
printf("收到子进程的信息:%s",share);
}
//释放映射
munmap(share,1024);
//释放共享内存对象
shm_unlink(shm_name);
return 0;
}
临时文件系统
linux的临时文件系统是一种基于内存的文件系统,它将数据存储在RAM中或者SWAP中,共享对象同样也是挂在在临时文件系统中。
我们可以写一段不释放的代码,来眼见为实。
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char sh_name[100]={0};
sprintf(sh_name,"/letter%d",getpid());
int fd=shm_open(sh_name,O_CREAT,0664);
if(fd<0){
perror("shm open");
exit(EXIT_FAILURE);
}
while (1)
{
//代码空转,方便查看内存区域。
}
return 0;
}
代码执行中:生成的临时文件

执行后,临时文件也不会消失,因为没有释放。
消息队列(message queue)
消息队列是消息的链表,存放在内核中,由消息队列标识符标识。进程可以向队列中添加消息,也可以从队列中读取消息。
其原理为:内核为每个消息队列维护一个消息链表,消息可以按照不同的类型进行分类,进程可以根据消息类型有选择地读取消息。

生产者
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
struct mq_attr attr;
attr.mq_flags=0;
attr.mq_maxmsg=10;
attr.mq_msgsize=100;
attr.mq_flags=0;
attr.mq_curmsgs;
struct timespec time_info;
//创建消息队列
char * mq_name="/p_c_mq";
mqd_t mqdes=mq_open(mq_name,O_RDWR|O_CREAT,0664,&attr);
if(mqdes==(mqd_t)-1){
perror("mq_open");
exit(EXIT_FAILURE);
}
//从控制台接受数据,并发送给消费者
char write_buff[100];
while (1)
{
memset(write_buff,0,100);
ssize_t read_count= read(STDIN_FILENO,write_buff,100);
clock_gettime(0,&time_info);
time_info.tv_sec+=5;
if(read_count==-1){
perror("read");
continue;
}
else if(read_count==0)
{
printf("控制台停止发送消息");
char eof=EOF;
if(mq_timedsend(mqdes,&eof,1,0,&time_info)==-1){
perror("mq_timedsend");
}
break;
}
else{
if(mq_timedsend(mqdes,write_buff,strlen(write_buff),0,&time_info)==-1){
perror("mq_timedsend");
}
printf("从命令行接受到数据,并发送给消息的队列。");
}
}
//关闭资源
close(mqdes);
//由消费者关闭更加合适,否则消费者可能无法接收到最后一条消息。
//unlink(mq_unlink);
return 0;
}
消费者
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
struct mq_attr attr;
attr.mq_flags=0;
attr.mq_maxmsg=10;
attr.mq_msgsize=100;
attr.mq_flags=0;
attr.mq_curmsgs;
struct timespec time_info;
//创建消息队列
char * mq_name="/p_c_mq";
mqd_t mqdes=mq_open(mq_name,O_RDWR|O_CREAT,0664,&attr);
if(mqdes==(mqd_t)-1){
perror("mq_open");
exit(EXIT_FAILURE);
}
//从控制台接受数据,并发送给消费者
char read_buff[100];
while (1)
{
memset(read_buff,0,100);
clock_gettime(0,&time_info);
time_info.tv_sec+=5;
//读取数据
if(mq_timedreceive(mqdes,read_buff,100,NULL,&time_info)==-1){
perror("mq_timedreceive");
}
//判断是否结束
else if(read_buff[0]==EOF){
printf("接收到结束信息,准备退出....");
break;
}
else{
printf("接受到来自生产者的信息%s\n",read_buff);
}
}
//关闭资源
close(mqdes);
unlink(mq_unlink);
return 0;
}
临时文件
与内存共享类似,Linux底层也会生成一个临时文件来代表队列。

三者之间的演化关系
在操作系统的发展过程中,最早出现的是管道通信,用于父子进程之间的信息通信。
不足:
- 单向且受限于亲缘关系限制
只能在父子进程之间传递,后面又演化出了命名管道,解决了亲缘关系的限制。 - 传输效率低
依赖内核缓冲区,且内核空间有限,无法传输大数据。 - 缺乏消息分类与异步支持
然后又演化出共享内存,解决了管道在大数据传输的效率问题。成为现在操作系统中最高效的IPC方式,
不足:
- 需要开发者自行处理同步逻辑。
经常听到的Zero Copy也是这个原理。
与共享内存同时推出的还有消息队列,它解决了管道/共享内存在功能上的局限性。
消息可以按照分组来选择性的接收,由内核来处理同步逻辑,并对外提供原子操作。
不足:
- 性能不如共享内存
因为消息队列与管道一样,需要从内核复制数据。
消息队列是对管道的功能增强,共享内存是对管道的性能增强。
| 机制 | 是否复制内核 | 存储位置 | 描述 |
|---|---|---|---|
| 管道 | 是(两次复制) | 内核缓冲区 | 基于字节流,需内核复制数据,效率较低,但实现简单 |
| 共享内存 | 否 | 内核物理地址 | 直接访问,无数据复制,效率最高,但需同步机制 |
| 消息队列 | 是(两次复制) | 内核物理地址 | 消息按类型组织,支持异步通信,但需内核复制数据,适合中小数据量传输 |
进程模型
内核会为每一个进程创建并保存一个名为PCB(Process Control Block)的数据结构,来维护进程运行过程中的一些关键信息。
- PID
- 进程状态
- 进程切换时需要保存和回复的寄存器的值
- 内存管理
当前进程所属哪一块内存,如页表,段表等 - 当前工作目录
- 进程调度信息
比如优先级,进程调度指针 - I/O状态信息
最常见的就是文件描述符表(struct fdtable),I/O设备列表 - 同步和通讯信息
比如信号量,信号等。 - 权限管理
比如所属id与所属group
struct task_struct {
/* 进程状态 */
volatile long state;
/* 进程标识符 */
pid_t pid;
/* 指向父进程的指针 */
struct task_struct __rcu *parent;
/* 进程的用户和组信息 */
uid_t uid, euid, suid, fsuid;
gid_t gid, egid, sgid, fsgid;
/* 其他成员... */
};
内存模型
内存模型,基本都大同小异,不再赘述.
以C语言程序为例:

栈指针 SP,表示栈顶
帧指针 BP,表示栈底
命令指针 IP,命令指针,指向下一条要运行的命令
进程状态模型
对于进程状态,有一个抽象的定义。
- 初始状态(initial)
进程刚被创建的初始态,该阶段,操作系统会为进程分配资源 - 就绪态(ready)
进程已经准备就绪,当并未被CPU所调度。 - 运行态(Running)
正在CPU上执行代码 - 阻塞态(Blocked)
进程等待其他事件完成,比如网络I/O,磁盘I/O而无法继续时,就处于阻塞状态 - 终止态(Final)
进程执行完毕,准备释放其占用的资源,PCB信息依旧存在。该状态理论上非常短暂。 - 僵尸态(Zombie)
与终止态非常相似,唯一的区别就是因为未知原因PCB长期不释放。

而在Linux中,并未完全遵循上述抽象概念。
不过总体类似,状态主要有如下几种。
- D
不可中断的睡眠状态,比如执行IO操作时,不可中断。 - I
空闲的内核线程 - R
Runnig/Read 运行中或者可以运行的状态 - S
可中断的睡眠状态,比如等待唤醒 - T
由工作控制信号停止 - t
由调试器停止 - W
分页,从2.6内核版本后就不再有效 - X
死亡状态,理论上不会看到,因为相当于整个进程都被回收了,包括PCB,是现实不了的。你如何显示一个不存在的进程? - Z
僵尸进程,已经终止但PCB尚未被回收
| 抽象 | Linux实现 |
|---|---|
| 初始态 | N/A |
| 就绪态 | R |
| 运行态 | R |
| 阻塞态 | D,S,T,t |
| 僵尸态 | Z |
进程状态控制
当一个进程状态变换的时候,通常需要三步。
- 找到PCB
- 设置PCB状态信息
- 将PCB移到响应的队列
比如进程从阻塞态变成就绪态,状态变化后,CPU的调度队列也要变化。
思考一个问题,如果在第二步的时候,突然来了一个中断,导致第三步没有执行。破坏了原子性,从而使得程序出现异常,这时候应该怎么处理?
汇编每执行一行代码,CPU都会检查一次有没有中断。
这个时候,就要依靠特权指令来实现原子性了
cpu提供两个特权指令
- 关中断指令
- 开中断指令
因此,在开/关中断指令中间的汇编代码,CPU不会再检查有没有中断,从而实现操作原子性。
Linux学习笔记(三)----进程的更多相关文章
- Linux学习笔记(六) 进程管理
1.进程基础 当输入一个命令时,shell 会同时启动一个进程,这种任务与进程分离的方式是 Linux 系统上重要的概念 每个执行的任务都称为进程,在每个进程启动时,系统都会给它指定一个唯一的 ID, ...
- 嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发
在成功构建了一个能够运行在开发板平台的系统后,下一步就要正式开始应用的开发(这里前提是有一定的C语言基础,对ARM体系的软/硬件,这部分有疑问可能要参考其它教程),根据需求仔细分解任务,可以发现包含的 ...
- Linux学习笔记24——进程管道
一 管道的作用 通常把一个进程的输出通过管道连接到另一个进程的输入. 二 popen和pclose函数 #include <stdio.h> FILE *popen(const char ...
- linux学习笔记之进程
一.基础知识 1:进程. 1,进程ID: 非负整数,具有唯一性. 1)ID=0的进程:调度进程/交换进程.内核的一部分.不执行任何磁盘上的程序. 2)ID=1的进程:init进程. 1-自举结束时,由 ...
- Linux学习笔记(三):系统执行级与执行级的切换
1.Linux系统与其它的操作系统不同,它设有执行级别.该执行级指定操作系统所处的状态.Linux系统在不论什么时候都执行于某个执行级上,且在不同的执行级上执行的程序和服务都不同,所要完毕的工作和所要 ...
- linux学习笔记-13.进程控制
1.查看用户最近登录情况 lastlastlog 2.查看硬盘使用情况 df 3.查看文件大小 du 4.查看内存使用情况 free 5.查看文件系统 /proc 6.查看日志 ls /var/log ...
- linux学习笔记三:防火墙设置
请注意:centOS7和7之前的版本在防火墙设置上不同,只有正确的设置防火墙才能实现window下访问linux中的web应用. centOS6添加端口: vi /ets/sysconfig/ipta ...
- Linux学习笔记<三>
<1>查看本机的IP地址 命令:ifconfig -a 机器的ip地址是:(inet 地址:172.16.163.57 ) <2>单独查看内存使用情况的命令:free -m 查 ...
- linux 学习笔记三
用户管理篇章 useradd 建立用户 一般用法 #useradd mysql 含义 创建 mysql用户 特殊用户 > #useradd -d /usr/cjh -m cjh 含义 创建 cj ...
- 【linux学习笔记三】链接命令
链接命令:ln link =============华丽的分割线============= ln又有软链接和硬链接 //硬链接特征(不建议创建硬链接) 1.拥有相同的i节点和存储block块,可以看做 ...
随机推荐
- biancheng-Python机器学习算法
http://c.biancheng.net/ml_alg/ Python机器学习 就当下而言,Python 无疑是机器学习领域最火的编程语言,这得益于 Python 对科学计算的强大支持.因此,本套 ...
- 分库分表(1) --- ShardingSphere(理论)
ShardingSphere---理论 ShardingSphere在中小企业需要分库分表的时候用的会比较多,因为它维护成本低,不需要额外增派人手;而且目前社区也还一直在开发和维护,还算是比较活跃. ...
- C#遍历获取文件夹下所有文件
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using ...
- “翼”鸣惊人,天翼云两篇论文被ACM ICPP 2024收录!
*日,由天翼云科技有限公司弹性计算产品线天玑实验室撰写的两篇论文<PheCon: Fine-Grained VM Consolidation with Nimble Resource Defra ...
- Django Rest Framework的使用
Django Rest Framework 一.Rest Framework的基本介绍 程序的客户端有很多:硬件设备,游戏,APP,软件,其他的外部服务端. 1. Web应用模式 在开发Web应用中, ...
- Q:oracle解锁用户
怎么查看oracle用户是否被锁 1.一般oracle数据库默认是10次尝试失败后锁住用户 1.查看FAILED_LOGIN_ATTEMPTS的值 select * from dba_profiles ...
- 基于deepseek模型知识库,Cherry Studio和AnythingLLM使用效果对比
基于deepseek模型知识库,Cherry Studio和AnythingLLM使用效果对比 目 录 1. 使用效果对比基础 2. Cherry Studio和Any ...
- ORACLE11g数据中创建DB Link方法,用于跨oracle数据库查询数据
---查看该用户下已建立的DB link链接 SELECT * FROM DBA_DB_LINKS --创建语句 CREATE DATABASE LINK 连接名CONNECT TO 登录名 ID ...
- mybatis之xml简单映射,解决实体类属性字段与数据库表字段不一致问题
当实体类属性字段与数据库表字段不一致时该怎么办? 方法一:起别名 <select id="getUserList" resultType="RealUser&quo ...
- windows10专业版代码永久激活
1."Win+R"打开运行对话框,输入命令slmgr.vbs -xpr 可以查看当前系统的激活信息 2. 在电脑图标右键,打开属性,查看自己win10系统版本 3. 在开始菜单右键 ...