248|2

473

帖子

0

TA的资源

纯净的硅(初级)

楼主
 

《Linux内核深度解析》--进程管理与应用 [复制链接]

本帖最后由 dirty 于 2025-1-1 16:16 编辑

     本篇讲述进程相关知识点。

      Linux 内核把进程称为任务(task),进程的虚拟地址空间分为用户虚拟地址空间和内核虚拟地址空间,所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟地址空间。进程有两种特殊形式:没有用户虚拟地址空间的进程称为内核线程,共享用户虚拟地址空间的进程称为用户线程,通常在不会引起混淆的情况下把用户线程简称为线程。共享同一个用户虚拟地址空间的所有用户线程组成一个线程组。

      进程指正在运行的程序, 如下图示, 是资源分配的最小单位, 可以通过“ps ” 或“top” 等命令查看正在运行的进程, 线程是系统的最小调度单位, 一个进程可以拥有多个线程, 同一进程里的线程可以共享此进程的同一资源。

      每个进程都有一个唯一的标识符, 既进程 ID, 简称 pid。进程间的通信的几种方法:

① 管道通信: 有名管道, 无名管道
②信号通信: 信号的发送, 信号的接受, 信号的处理
③ IPC 通信: 共享内存, 消息队列, 信号灯
④ Socket 通信

      进程的三种基本状态以及转换:


创建进程

      在 Linux内核中,新进程是从一个已经存在的进程复制出来的。内核使用静态数据构造出0号内核线程,0号内核线程分叉生成1号内核线程和2号内核线程(kthreadd线程)。1号内核线程完成初始化以后装载用户程序,变成1号进程,其他进程都是1号进程或者它的子孙进程分叉生成的;其他内核线程是 kthreadd 线程分叉生成的。

      三个系统调用可以用来创建新的进程。
(1)fork(分叉):子进程是父进程的一个副本,采用了写时复制的技术。
(2)vfork:用于创建子进程,之后子进程立即调用execve 以装载新程序的情况。为了避免复制物理页,父进程会睡眠等待子进程装载新程序。现在fork采用了写时复制的技术,vfork 失去了速度优势,已经被废弃。

(3)clone(克隆):可以精确地控制子进程和父进程共享哪些资源。这个系统调用的主要用处是可供pthread库用来创建线程。
      clone是功能最齐全的函数,参数多,使用复杂,fork是clone的简化函数。fork是比较常见用法。

      在内核底层文件kernel/fork.c、arch/arm64/kernel/process.c、kernel/sched/core.c等内核实现创建新进程及其实现、唤醒进程、装载进程。

      下面在父进程创建子进程

#include <stdio.h>
#include <unistd.h>
int main(void)
{
	pid_t pid;
	pid = fork();
	if (pid < 0)
	{
		printf("fork is error \n");
		return -1;
	} 
	//父进程
	if (pid > 0)
	{
		printf("This is parent,parent pid is %d\n", getpid());
	} 
	//子进程
	if (pid == 0)
	{
		printf("This is child,child pid is %d,parent pid is %d\n", getpid(), get
		ppid());
	} 
	return 0;
}

装载 ELF 程序

      ELF文件:ELF(Executable and Linkable Format)是可执行与可链接格式,主要有以下4种类型。
●目标文件(object fle),也称为可重定位文件(relocatable fle),扩展名是“.o”,多个目标文件可以链接生成可执行文件或者共享库。
●可执行文件(executable fle)。0
●共享库(sharedobject fle),扩展名是“.so”。
●核心转储文件(core dump fle)。

      ELF 文件分成4个部分:ELF首部、程序首部表(program header table)节(section)和节首部表(sectionheader table)。只有ELF 首部的位置是固定的,其余各部分的位置和大小由 ELF首部的成员决定。ELF文件格式如下图所示组成:

进程退出

      进程退出分两种情况:进程主动退出和终止进程。exit用来使一个线程退出。Linux私有的系统调用exit_group用来使一个线程组的所有线程退出。

      终止进程是通过给进程发送信号实现的,kil用来发送信号给进程或者进程组。tkill用来发送信号给线程,参数tid是线程标识符。tgkill用来发送信号给线程,参数tgid 是线程组标识符,参数tid是线程标识符。

 

进程调度

      Linux内核支持的调度策略如下,调度进程的核心函数是schedule()。

●限期进程使用限期调度策略(SCHED DEADLINE)。
●实时进程支持两种调度策略:先进先出调度(SCHED_FIFO)和轮流调度(SCHED_RR)。

●普通进程支持两种调度策略:标准轮流分时(SCHED_NORMAL)和空闲(SCHEDIDLE)。

   

 

SMP调度

      SMP调度(Symmetric Multi-Processing调度)是指在对称多处理器(SMP)系统中,操作系统如何管理和调度多个处理器上的进程,以确保高效和公平的资源利用。在SMP系统中,进程调度器必须支持以下特性,
●需要使每个处理器的负载尽可能均衡。
●可以设置进程的处理器亲和性(affinity),即允许进程在哪些处理器上执行。
●可以把进程从一个处理器迁移到另一个处理器。

 

发散与应用:

      在我们实际应用中,经常会使用到进程间通讯。Linux 进程间通信机制分三类: 数据交互, 同步, 信号。

      无名管道是最古老的进程通信方式, 有如下两个特点:
●只能用于有关联的进程间数据交互, 如父子进程, 兄弟进程, 子孙进程, 在目录中看不到文件节点, 读写文件描述符存在一个 int 型数组中。
●只能单向传输数据, 即管道创建好后, 一个进程只能进行读操作, 另一个进程只能进行写操作,读出来字节顺序和写入的顺序一样。

      无名管道使用步骤:
1. 调用 pipe()创建无名管道;

2.fork()创建子进程, 一个进程读, 使用 read(), 一个进程写, 使用 write()

使用示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
	char buf[32] = {0};
	pid_t pid;
	// 定义一个变量来保存文件描述符
	// 因为一个读端, 一个写端, 所以数量为 2 个
	int fd[2];
	// 创建无名管道
	pipe(fd);
	printf("fd[0] is %d\n", fd[0]);
	printf("fd[2] is %d\n", fd[1]);
	// 创建进程
	pid = fork();
	if (pid < 0)
	{
		printf("error\n");
	} 
	if(pid > 0)
	{
		int status;
		close(fd[0]);
		write(fd[1], "hello", 5);
		close(fd[1]);
		wait(&status);
		exit(0);
	} 
	if (pid == 0)
	{
		close(fd[1]);
		read(fd[0], buf, 32);
		printf("buf is %s\n", buf);
		close(fd[0]);
		exit(0);
	}
	return 0;
}

      有名管道在一些专业书籍中叫做命名管道, 它的特点是
1.可以使无关联的进程通过 fifo 文件描述符进行数据传递;
2.单向传输有一个写入端和一个读出端, 操作方式和无名管道相同。

      有名管道使用步骤:
1. 使用 mkfifo()创建 fifo 文件描述符。
2. 打开管道文件描述符。
3. 通过读写文件描述符进行单向数据传输。
      有名管道代码示例

/* fifo_write.c */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
	int ret;
	char buf[32] = {0};
	int fd;
	if (argc < 2)
	{
		printf("Usage:%s <fifo name> \n", argv[0]);
		return -1;
	} 
	if (access(argv[1], F_OK) == 1)
	{
		ret = mkfifo(argv[1], 0666);
		if (ret == -1)
		{
			printf("mkfifo is error \n");
			return -2;
		} 
		printf("mkfifo is ok \n");
	}
	fd = open(argv[1], O_WRONLY);
	while (1)
	{
		sleep(1);
		write(fd, "hello", 5);
	} 
	close(fd);
	return 0;
}

/* fifo_read.c */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
	char buf[32] = {0};
	int fd;
	if (argc < 2)
	{
		printf("Usage:%s <fifo name> \n", argv[0]);
		return -1;
	} 
	fd = open(argv[1], O_RDONLY);
	while (1)
	{
		sleep(1);
		read(fd, buf, 32);
		printf("buf is %s\n", buf);
		memset(buf, 0, sizeof(buf));
	} 
	close(fd);
	return 0;
}

 

      信号是 Linux 系统响应某些条件而产生的一个事件, 接收到该信号的进程会执行相应的操作。

信号发送
信号的产生有三种方式:
1)由硬件产生, 如从键盘输入 Ctrl+C 可以终止当前进程
2)由其他进程发送, 如可在 shell 进程下, 使用命令 kill -信号标号 PID, 向指定进程发送信号。
3)异常, 进程异常时会发送信号

信号发送函数有:kill、raise、alarm,具体API可查阅资料使用

接收信号

      如果要让我们接收信号的进程可以接收到信号, 那么这个进程就不能停止。 让进程不停止有三种方法:
1) while
2) sleep
3) pause

信号处理

      信号有三种处理方式:
1.默认方式(通常是终止进程) ,
2.忽略, 不进行任何操作。
3.捕捉并处理调用信号处理器(回调函数形式) 。
      这里第三种处理方式示例代码如下:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void myfun(int sig)
{
	if(sig == SIGINT)
	{
		printf("get sigint\n");
	}
} 

int main(void)
{
	signal(SIGINT,myfun);
	while(1){
		sleep(1);
		printf("wait signal\n");
	} 
	return 0;
}

      此外还有共享内存、消息队列、信号量,这里再讲讲消息队列。消息队列是类 unix 系统中一种数据传输的机制, 其他操作系统中也实现了这种机制, 可见这种通信机制在操作系统中有重要地位。Linux 内核为每个消息队列对象维护一个 msqid_ds, 每个 msqid_ds 对应一个 id, 消息以链表形式存储,并且 msqid_ds 存放着这个链表的信息。
      消息队列的特点:
1.发出的消息以链表形式存储, 相当于一个列表, 进程可以根据 id 向对应的“列表” 增加和获取消息。
2.进程接收数据时可以按照类型从队列中获取数据。
      消息队列的使用步骤:
1. 创建 key;
2. msgget()通过 key 创建(或打开) 消息队列对象 id;
3. 使用 msgsnd()/msgrcv()进行收发;
4. 通过 msgctl()删除 ipc 对象
 

/*  a.c 向消息队列里面写  */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
	long mtype;
	char mtext[128];
};
int main(void)
{
	int msgid;
	key_t key;
	struct msgbuf msg;
	//获取 key 值
	key = ftok("./a.c", 'a');
	//获取到 id 后即可使用消息队列访问 IPC 对象
	msgid = msgget(key, 0666 | IPC_CREAT);
	if (msgid < 0)
	{
		printf("msgget is error\n");
		return -1;
	} 
	printf("msgget is ok and msgid is %d \n", msgid);
	msg.mtype = 1;
	strncpy(msg.mtext, "hello", 5);
	//发送数据
	msgsnd(msgid, &msg, strlen(msg.mtext), 0);
	return 0;
}

/*  b.c 从消息队列里面读 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
	long mtype;
	char mtext[128];
};

int main(void)
{
	int msgid;
	key_t key;
	struct msgbuf msg;
	key = ftok("./a.c", 'a');
	//获取到 id 后即可使用消息队列访问 IPC 对象
	msgid = msgget(key, 0666 | IPC_CREAT);
	if (msgid < 0)
	{
		printf("msgget is error\n");
		return -1;
	} 
	printf("msgget is ok and msgid is %d \n", msgid);
	//接收数据
	msgrcv(msgid, (void *)&msg, 128, 0, 0);
	printf("msg.mtype is %ld \n", msg.mtype);
	printf("msg.mtext is %s \n", msg.mtext);
	return 0;
}

 

      本篇对Linux内核进程管理有了一些认识,根据以往经验拓展发散讲了些进程方面的使用。学以致用,可以拿适合的开发板及其配套资料实践使用,会有不一样的收获感。

 

最新回复

有关Linux内核进程管理的认识还挺多,想问信号发送,信号接受,信号处理各有三种方式,是一一对应的么   详情 回复 发表于 前天 07:28
点赞 关注

回复
举报

6749

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

有关Linux内核进程管理的认识还挺多,想问信号发送,信号接受,信号处理各有三种方式,是一一对应的么

点评

没有说一一对应,就是各个的用法函数这些选项  详情 回复 发表于 前天 09:23
 
 

回复

473

帖子

0

TA的资源

纯净的硅(初级)

板凳
 
Jacktang 发表于 2025-1-2 07:28 有关Linux内核进程管理的认识还挺多,想问信号发送,信号接受,信号处理各有三种方式,是一一对应的么

没有说一一对应,就是各个的用法函数这些选项

 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
推荐帖子
我现在在日本,我是汉奸吗?

我正在日本某公司就职,从事硬件工作。我不关心政治,我是个技术者,我想从技术面 儿谈谈看法。先从手机说起,国内研发手机也 ...

【拆检】温度校验仪

故障现象:6A保险丝熔断 初步判断:发热单元有短路 设备外观: 36357 揭开接线后盖: 36358 固态继电器: 36359 ...

留住身边的美景,2015年春天

本帖最后由 mmmllb 于 2015-4-9 12:28 编辑 不知道说现在是春天是否合适:Sweat:,先这么叫着。 话说最近各路的花都开了,有 ...

开关电源的电感选择和布局布线

开关电源(SMPS, Switched-Mode Power Supply)是一种非常高效的电源变换器,其理论值更是接近100%,种类繁多。按拓扑结构分, ...

TI C2000系列 280049芯片SPI初始化需要注意的问题

280049芯片使用TI的库函数的时候,SPIA初始化之后寄存器设置没有改变,全为0,这个时候需要去使能SPIA或者SPIB,就是这个问题困 ...

蓝牙的性能指标,怎么去判断是否可以

蓝牙的性能指标,怎么去判断是否可以

Make 杂志:在硬件上开始使用 Python

Make杂志提供了一篇关于硬件上Python入门的新文章。通过一些示例讨论了MicroPython和CircuitPython。最后,介绍了在硬件上使用Py ...

40“万里”树莓派小车——ROS学习(C语言编程控制小乌龟)

本帖最后由 lb8820265 于 2022-11-1 14:52 编辑 前面介绍了运行“turtle_teleop_key”节点,然后通过键盘来控 ...

MOS管工作状态如何判断?

MOS管工作状态如何判断? 欧若奇科技 专业电路设计,PCB复制,原理图反推,电子产品优化设计等 MOS管 ...

【入围名单】2024 DigiKey “感知万物,乐享生活”创意大赛

【大赛详情】>> 2024 DigiKey “感知万物,乐享生活”创意大赛 感谢小伙伴们对 2024 DigiKey “感知 ...

关闭
站长推荐上一条 1/5 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表