Linux0.11 管道(十一)

news2025/1/4 16:04:18

在这里插入图片描述

系列文章目录


Linux 0.11启动过程分析(一)
Linux 0.11 fork 函数(二)
Linux0.11 缺页处理(三)
Linux0.11 根文件系统挂载(四)
Linux0.11 文件打开open函数(五)
Linux0.11 execve函数(六)
Linux0.11 80X86知识(七)
Linux0.11 内核体系结构(八)
Linux0.11 系统调用进程创建与执行(九)
Linux0.11 进程切换(十)
Linux0.11 管道(十一)
Linux0.11 信号(十二)


文章目录

  • 系列文章目录
  • 前言
  • 一、说明
    • 1、应用接口说明
    • 2、实现说明
  • 二、应用代码
  • 三、系统背后行为
    • 1、file 结构体
    • 2、sys_pipe 函数
      • get_pipe_inode 函数
    • 3、管道操作
      • 源码分析
      • 图解


前言

  现在Linux进程间通信方式包括:匿名管道(pipe)及有名管道(fifo)、信号(signal)、消息队列(message queue)、共享内存(shared memory)、信号量(semaphore)、套接字(socket)。

管道:把一个程序的输出直接连接到另一个程序的输入。

匿名管道

  • 只能用于具有亲缘关系的进程之间的通信(父进程和子进程之间)
  • 单工通信模式,固定读端和写端
  • 特殊的文件,可以使用普通的read()、write()等函数,不属于文件系统,只存在于系统中

一、说明

1、应用接口说明

创建与关闭——pipe()、close()

 #include <unistd.h>
/**
* fd:包含两个元素的整型数组,存放管道对于文件描述符
* 返回值:成功:0,失败:-1
*/
int pipe(int pipefd[2]);

int close(int fd);

2、实现说明

  管道操作是进程间通信的最基本方式。本程序包括管道文件读写操作函数 read_pipe() 和 write_pipe(),同时实现了管道系统调用 sys_pipe()。这两个函数也是系统调用 read() 和 write() 的低层实现函数,也仅在 read_write.c 中使用。
  在创建并初始化管道时,程序会专门申请一个管道 i 节点 (参考:1、m_inode 结构体 ),并为管道分配一页缓冲区(4KB)。管道 i 节点的 i_size 字段中被设置为指向管道缓冲区的指针,管道数据头部指针存放在 i_zone[0] 字段中,而管道数据尾部指针存放在 i_zone[1] 字段中。对于读管道操作,数据是从管道尾读出,并使管道尾指针前移读取字节数个位置;对于往管道中的写入操作,数据是向管道头部写入,并使管道头指针前移写入字节数个位置。参见下面的管道示意图 12-28 所示。
在这里插入图片描述

  read_pipe() 用于读管道中的数据。若管道中没有数据,就唤醒写管道的进程,而自己则进入睡眠状态。若读到了数据,就相应地调整管道头指针,并把数据传到用户缓冲区中。当把管道中所有的数据都取走后,也要唤醒等待写管道的进程,并返回已读数据字节数。当管道写进程已退出管道操作时,函数就立刻退出,并返回已读的字节数。
  write_pipe()函数的操作与读管道函数类似。

  系统调用 sys_pipe() 用于创建无名管道。它首先在系统的文件表中取得两个表项,然后在当前进程的文件描述符表中也同样寻找两个未使用的描述符表项,用来保存相应的文件结构指针。接着在系统中申请一个空闲 i 节点,同时获得管道使用的一个缓冲块。然后对相应的文件结构进行初始化,将一个文件结构设置为只读模式,另一个设置为只写模式。最后将两个文件描述符传给用户。
  另外,以上函数中使用的几个与管道操作有关的宏(例如 PIPE_HEAD()、PIPE_TAIL() 等)定义在 include/linux/fs.h 文件第 57-64 行上。

二、应用代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    int fd[2];
    pid_t pid;
    char str1[] = "abcdeabcdeabcdeabcde";
    char str2[512];
    int i, j;

    if(pipe(fd) <0) {
        perror("fail to pipe");
        exit(-1);
    }
    if ((pid = fork()) < 0) {
    	return -1;
    } else if (pid > 0) {	// 父进程向管道中写入数据
    	close(fd[0]);
    	for (i = 0; i < 10000; i++) 
    		write(fd[1], str1, strlen(str1));
    } else {				// 子进程从管道中读取数据
    	close(fd[1]);
    	for (j = 0; j < 20000; j++) 
    		read(fd[0], str2, strlen(str2));
    }
    return 0;
}

三、系统背后行为

1、file 结构体

// include/linux/fs.h

// 文件结构(用于在文件句柄与 i 节点之间建立关系)
struct file {
	unsigned short f_mode;		// 文件操作模式 (RW 位)
	unsigned short f_flags;		// 文件打开和控制的标志
	unsigned short f_count;		// 对应文件引用计数值
	struct m_inode * f_inode;	// 指向对应 i 节点
	off_t f_pos;				// 文件位置(读写偏移值)
};

2、sys_pipe 函数

pipe 函数会调用系统实现的 sys_pipe 函数 。
  从技术上看,管道就是一页内存,但进程要以操作文件的方式对其进行操作,这就要求这页内存具备一些文件属性并减少页属性。
  具备一些文件属性表现为,创建管道相当于创建一个文件,如进程 task_struct 中 *filp[20]file_table[64] 挂接、新建 i 节点、file_table[64] 和文件 i 节点挂接等工作要在创建管道过程中完成,最终使进程只要知道自己在操作管道类型的文件就可以了,其他的都不用关心。
  减少页属性表现为,这页内存毕竟要当做一个文件使用,比如进程不能像访问自己用户。空间的数据一样访问它,不能映射到进程的线性地址空间内。再如,两个进程操作这个页面,一个读一个写,也不能产生页写保护异常把页面另复制一份,否则无法共享管道。下面我们来看管道的具体创建过程。

// fs/pipe.c

/// 创建管道系统调用。
// 在 fildes 所指的数组中创建一对文件句柄(描述符)。这对文件。柄指向一管道 i 节点。
// 参数:filedes -文件句柄数组。fildes[0]用于读管道数据,fildes[1]向管道写入数据。
// 成功时返回 0,出错时返回-1。
int sys_pipe(unsigned long * fildes)
{
	struct m_inode * inode;
	struct file * f[2];			// 文件结构数组。
	int fd[2];					// 文件句柄数组。
	int i,j;
	
// 首先从系统文件表中取两个空闲项(引用计数字段为 0 的项),并分别设置引用计数为 1。
// 若只有 1 个空闲项,则释放该项(引用计数复位)。若没有找到两个空闲项,则返回 -1。
	j=0;
	for(i=0;j<2 && i<NR_FILE;i++)
		if (!file_table[i].f_count)
			(f[j++]=i+file_table)->f_count++;
	if (j==1)
		f[0]->f_count=0;
	if (j<2)
		return -1;
// 针对上面取得的两个文件表结构项,分别分配一文件句柄号,并使进程文件结构指针数组的。
// 两项分别指向这两个文件结构。而文件句柄即是该数组的索引号。类似地,如果只有一个空
// 闲文件句柄,则释放该句柄(置空相应数组项)。如果没有找到两个空闲句柄,则释放上面。
// 获取的两个文件结构项(复位引用计数值),并返回 -1。
	j=0;
	for(i=0;j<2 && i<NR_OPEN;i++)
		if (!current->filp[i]) {
			current->filp[ fd[j]=i ] = f[j];
			j++;
		}
	if (j==1)
		current->filp[fd[0]]=NULL;
	if (j<2) {
		f[0]->f_count=f[1]->f_count=0;
		return -1;
	}
// 然后利用函数 get_pipe_inode() 申请一个管道使用的 i 节点,并为管道分配一页内存作为缓
// 冲区。如果不成功,则相应释放两个文件句柄和文件结构项,并返回-1。
	if (!(inode=get_pipe_inode())) {		// fs/inode.c,第 228 行开始处。
		current->filp[fd[0]] =
			current->filp[fd[1]] = NULL;
		f[0]->f_count = f[1]->f_count = 0;
		return -1;
	}

// 如果管道 i 节点申请成功,则对两个文件结构进行初始化操作,让它们都指向同一个管道 i 节
// 点,并把读写指针都置零。第 1 个文件结构的文件模式置为读,第 2 个文件结构的文件模式置
// 为写。最后将文件句柄数组复制到对应的用户空间数组中,成功返回 0, 退出。
	f[0]->f_inode = f[1]->f_inode = inode;
	f[0]->f_pos = f[1]->f_pos = 0;
	f[0]->f_mode = 1;		/* read */
	f[1]->f_mode = 2;		/* write */
	put_fs_long(fd[0],0+fildes);
	put_fs_long(fd[1],1+fildes);
	return 0;
}

get_pipe_inode 函数

// include/linux/fs.h

#define PIPE_HEAD(inode) ((inode).i_zone[0])	// 写管道
#define PIPE_TAIL(inode) ((inode).i_zone[1])	// 读管道
#define PIPE_SIZE(inode) ((PIPE_HEAD(inode)-PIPE_TAIL(inode))&(PAGE_SIZE-1))
#define PIPE_EMPTY(inode) (PIPE_HEAD(inode)==PIPE_TAIL(inode))
#define PIPE_FULL(inode) (PIPE_SIZE(inode)==(PAGE_SIZE-1))
#define INC_PIPE(head) \
__asm__("incl %0\n\tandl $4095,%0"::"m" (head))


// fs/inode.c


 获取管道节点。
// 首先扫描 i 节点表,寻找一个空闲 i 节点项,然后取得一页空闲内存供管道使用。然后将得
// 到的 i 节点的引用计数置为 2(读者和写者),初始化管道头和尾,置 i 节点的管道类型表示。
// 返回为 i 节点指针,如果失败则返回 NULL。
struct m_inode * get_pipe_inode(void)
{
	struct m_inode * inode;
	
// 首先从内存 i 节点表中取得一个空闲 i 节点。如果找不到空闲 i 节点则返回 NULL。然后为该
// i 节点申请一页内存,并让节点的 i_size 字段指向该页面。如果已没有空闲内存,则释放该
// i 节点,并返回 NULL。
	if (!(inode = get_empty_inode()))
		return NULL;
	if (!(inode->i_size=get_free_page())) {
		inode->i_count = 0;
		return NULL;
	}
// 然后设置该 i 节点的引用计数为 2,并复位复位管道头尾指针。i 节点逻辑块号数组 i_zone[]
// 的 i_zone[0] 和 i_zone[1]中分别用来存放管道头和管道尾指针。最后设置 i 节点是管道 i 节。
// 点标志并返回该 i 节点号。
	inode->i_count = 2;	/* sum of readers/writers */ /* 读/写两者总计 */
	PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;		// 复位管道头尾指针。
	inode->i_pipe = 1;								// 置节点为管道使用的标志。
	return inode;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、管道操作

源码分析

// include/linux/fs.h

#define PIPE_HEAD(inode) ((inode).i_zone[0])	// 写管道
#define PIPE_TAIL(inode) ((inode).i_zone[1])	// 读管道
#define PIPE_SIZE(inode) ((PIPE_HEAD(inode)-PIPE_TAIL(inode))&(PAGE_SIZE-1))

// fs/pipe.c

#include <signal.h>			// 信号头文件。定义信号符号常量,信号结构及操作函数原型。
#include <linux/sched.h>	// 调度程序头文件,定义了任务结构 task_struct、任务 0 数据等。
#include <linux/mm.h>		/* for get_free_page */ /* 使用其中的 get_free_page */
							// 内存管理头文件。含有页面长度定义和一些页面释放函数原型。
#include <asm/segment.h> 	// 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。

 管道读操作函数。
// 参数 inode 是管道对应的 i 节点,buf 是用户数据缓冲区指针,count 是读取的字节数。
int read_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, read = 0;

// 如果需要读取的字节计数 count 大于 0,我们就循环执行以下操作。在循环读操作过程中,
// 若当前管道中没有数据(size=0),则唤醒等待该节点的进程,这通常是写管道进程。如果
// 已没有写管道者,即 i 节点引用计数值小于 2,则返回已读字节数退出。否则在该 i 节点上
// 睡眠,等待信息。宏 PIPE_SIZE 定义在 include/linux/fs.h 中。
	while (count>0) {
		while (!(size=PIPE_SIZE(*inode))) {		// 取管道中数据长度值。
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) /* are there any writers? */
				return read;
			sleep_on(&inode->i_wait);
		}
		
// 此时说明管道(缓冲区)中有数据。于是我们取管道尾指针到缓冲区末端的字节数 chars。
// 如果其大于还需要读取的字节数 count,则令其等于 count。如果 chars 大于当前管道中含
// 有数据的长度 size,则令其等于 size。 然后把需读字节数 count 减去此次可读的字节数
// chars,并累加已读字节数 read。
// 即使管道(缓冲区)中的数据大于需要读取的数(count),但写指针到置缓冲区末端的字节数少于 count,
// 那本次也只写到缓冲区未端,剩下的数据待下一次写入。
		chars = PAGE_SIZE-PIPE_TAIL(*inode);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;
		count -= chars;
		read += chars;
// 再令 size 指向管道尾指针处,并调整当前管道尾指针(前移 chars 字节)。若尾指针超过
// 管道末端则绕回。然后将管道中的数据复制到用户缓冲区中。对于管道 i 节点,其 i_size。
// 字段中是管道缓冲块指针。
		size = PIPE_TAIL(*inode);
		PIPE_TAIL(*inode) += chars;
		PIPE_TAIL(*inode) &= (PAGE_SIZE-1);
		while (chars-->0)
			put_fs_byte(((char *)inode->i_size)[size++],buf++);
	}
// 当此次读管道操作结束,则唤醒等待该管道的进程,并返回读取的字节数。
	wake_up(&inode->i_wait);
	return read;
}


 管道写操作函数。
// 参数 inode 是管道对应的 i 节点,buf 是数据缓中区指针,count 是将写入管道的字节数。
int write_pipe(struct m_inode * inode, char * buf, int count)
{
	int chars, size, written = 0;

// 如果要写入的字节数 count 还大于 0,那么我们就循环执行以下操作。在循环操作过程中,
// 若当前管道中没有已经满了(空闲空间 size = 0),则唤醒等待该节点的进程,通常唤醒。
// 的是读管道进程。 如果已没有读管道者,即 i 节点引用计数值小于 2,则向当前进程发送
// SIGPIPE 信号,并返回已写入的字节数退出;若写入 0 字节,返回 -1。否则让当前进程。
// 在该 i 节点上睡眠,以等待读管道进程读取数据,从而让管道腾出空间。宏 PIPE_SIZE()、
// PIPE_HEAD()等定义在文件 include/linux/fs.h 中。
	while (count>0) {
		while (!(size=(PAGE_SIZE-1)-PIPE_SIZE(*inode))) {
			wake_up(&inode->i_wait);
			if (inode->i_count != 2) { /* no readers */
				current->signal |= (1<<(SIGPIPE-1));
				return written?written:-1;
			}
			sleep_on(&inode->i_wait);
		}
// 程序执行到这里表示管道缓冲区中有可写空间 size。于是我们取管道头指针到缓冲区末端空
// 间字节数 chars。写管道操作是从管道头指针处开始写的。如果 chars 大于还需要写入的字节
// 数 count,则令其等于 count。 如果 chars 大于当前管道中空闲空间长度size,则令其等于
// size。然后把需要写入字节数 count 减去此次可写入的字节数 chars,并把写入字节数累加到
// written 中。
// 即使管道(缓冲区)空闲缓存大小大于所需要写入的数(count),但置缓冲区末端的字节数少于 count,
// 那本次也读到缓冲区未端,剩下的数据待下一次读取。
		chars = PAGE_SIZE-PIPE_HEAD(*inode);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;
		count -= chars;
		written += chars;
// 再令 size 指向管道数据头指针处,并调整当前管道数据头部指针(前移 chars 字节)。若头
// 指针超过管道末端则绕回。然后从用户缓冲区复制 chars 个字节到管道头指针开始处。 对于
// 管道 i 节点,其 i_size 字段中是管道缓冲块指针。
		size = PIPE_HEAD(*inode);
		PIPE_HEAD(*inode) += chars;
		PIPE_HEAD(*inode) &= (PAGE_SIZE-1);
		while (chars-->0)
			((char *)inode->i_size)[size++]=get_fs_byte(buf++);
	}
// 当此次写管道操作结束,则唤醒等待管道的进程,返回已写入的字节数,退出。	
	wake_up(&inode->i_wait);
	return written;
}

图解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/425225.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

fMRI研究 | 社交情境下的混合情绪

导读 背景&#xff1a;神经科学通常都是单独研究各种情绪&#xff0c;而混合的情绪状态&#xff08;例如愉悦和厌恶、悲伤和快乐的共存&#xff09;在日常生活中很常见。心理生理学和行为学证据表明&#xff0c;混合情绪可能具有不同于其组成情绪的反应特征。然而&#xff0c;…

什么是JWT?

起源 需要了解一门技术&#xff0c;首先从为什么产生开始说起是最好的。JWT 主要用于用户登录鉴权&#xff0c;所以我们从最传统的 session 认证开始说起。 session认证 众所周知&#xff0c;http 协议本身是无状态的协议&#xff0c;那就意味着当有用户向系统使用账户名称和…

RocketMQ源码分析之监控指标分析

这里是weihubeats,觉得文章不错可以关注公众号小奏技术&#xff0c;文章首发。拒绝营销号&#xff0c;拒绝标题党 Rocketmq版本 version: 5.1.0 背景 继续上次的高可用topic二开已经有了一段时间&#xff0c;现在我们需要对我们的限流数据进行监控&#xff0c;所以现在我们来…

Qt中英文切换(涉及多种场景)

qt中英文切换涉及到一个软件两个文件&#xff0c;分别是QtLinguist、.ts文件和.qm文件。 1、在Pro中添加 TRANSLATIONS en.ts \ch.ts添加这个文件后qmake&#xff0c;然后如下操作点击更新&#xff1a; 这个时候会生成2两个文件en.ts和ch.ts。 2、将这两个文件添加到项目中…

C++ : 构造函数 析构函数

&#x1f535;前提引入 &#xff1a; 1如果一个类中什么成员都没有&#xff0c;称为空类&#xff0c;但空类并非什么都没有&#xff0c;在我们没有写任何东西时&#xff0c;编译器会自动生成6个默认成员函数。 2.默认成员函数 &#xff1a; 用户没有显式实现&#xff0c;编译器…

Redis快速上手

Redis快速上手 OVERVIEWRedis快速上手1.redis数据类型2.redis常用命令StringListSetSortedSetHashKey相关3.redis配置文件4.redis数据持久化5.hiredis使用连接数据库执行redis命令函数释放资源程序实例1.redis数据类型 key: 必须是字符串 - “hello” value: 可选的 String类型…

核心业务5:充值业务实现

核心业务5:我要充值 1.充值业务流程图 2.充值业务流程逻辑 3.数据库表 4.前端逻辑代码 5.汇付宝代码逻辑 6.尚融宝代码逻辑 7.幂等性判断原理和解决方案 8.代码规范和原理了解 核心业务5:我要充值 1.充值业务流程图

基于springboot的在线考试系统源码数据库论文

目 录 目 录 第一章 概述 1.1研究背景 1.2 开发意义 1.3 研究现状 1.4 研究内容 1.5论文结构 第二章 开发技术介绍 2.1 系统开发平台 2.2 平台开发相关技术 2.2.1 Java技术 2.2.2 mysql数据库介绍 2.2.3 MySQL环境配置 2.2.4 B/S架构 2.2.5 Spr…

如何在Linux系统中使用 envsubst 命令替换环境变量?

在Linux系统中&#xff0c;环境变量是非常常见的一种机制&#xff0c;它们被用于存储重要的系统信息&#xff0c;比如用户的登录名、路径等等。当在脚本中需要使用这些变量时&#xff0c;可以使用envsubst命令&#xff0c;该命令可以将环境变量的值替换到文本文件中。 本文将介…

低静态电流-汽车电池反向保护系统的方法

低静态电流-汽车电池反向保护系统的方法 背景 车辆中电子电路数量不断增加&#xff0c;使得需要消耗的电池电量也随之大幅增长。为了支持遥控免钥进入和安全等功能&#xff0c;即使在汽车停车或熄火时&#xff0c;电池也要持续供电。 由于所有车辆都使用有限的电池供电&…

三轴XYZ平台生成gcode文件

1. 生成gcode坐标文件 gcode文件中保存的是需要绘制图形的路径信息&#xff0c;这里我们采用开源矢量图形编辑软件 Inkscape并通过Unicorn G-Code插件来生成 gcode坐标文件。 将软件资料包\Inkscape.rar 压缩文件解压到电脑上任意磁盘&#xff0c;软件内已安装 Unicorn G-Code插…

【花雕学AI】深度挖掘ChatGPT角色扮演的一个案例—CHARACTER play : 莎士比亚

CHARACTER play : 莎士比亚 : 52岁&#xff0c;男性&#xff0c;剧作家&#xff0c;诗人&#xff0c;喜欢文学&#xff0c;戏剧&#xff0c;爱情 : 1、问他为什么写《罗密欧与朱丽叶》 AI: 你好&#xff0c;我是莎士比亚&#xff0c;一位英国的剧作家和诗人。我很高兴你对我的…

【论文速览】图像分割领域的通用大模型SegGPT - Segmenting Everything in Context

文章目录研究背景解决思路PainterSegGPT实验效果&#xff08;部分&#xff09;思考参考资料代码地址&#xff1a;https://github.com/baaivision/Painter Demo地址&#xff1a;https://huggingface.co/spaces/BAAI/SegGPT 研究背景 图像分割一直是计算机视觉领域的一项基础研究…

Free container identify , CIMCAI container detect cloud service

集装箱箱号识别API免费&#xff0c;中国上海人工智能企业CIMCAI飞瞳引擎™集装箱人工智能平台全球近4千企业用户&#xff0c;全球领先的飞瞳引擎™AI集装箱识别云服务&#xff0c;集装箱残损识别箱况检测缺陷检验&#xff0c;小程序拍照检测或支持API接口二次开发&#xff0c;应…

数据结构初阶(算法的复杂度 + 包装类 + 泛型)

文章目录一、算法复杂度1. 算法效率2. 时间复杂度&#xff08;1&#xff09; O的渐进表示法3. 空间复杂度二、包装2.1 为什么会出现包装2.2 分类2.3 装箱和拆箱&#xff08;1&#xff09;装箱/装包&#xff08;2&#xff09;拆箱/拆箱三、泛型3.1 泛型的基本概念3.2 泛型的使用…

2023 年 3 月 GameFi 月度报告

作者&#xff1a;danielfootprint.network 数据来源&#xff1a;Monthly GameFi Report 三月的 GameFi 世界相对沉寂&#xff0c;没有重大的消息公开&#xff0c;没有亮眼的游戏出现&#xff0c;也没有死亡螺旋的发生。 GameFi 领域的重要名字 Splinterlands 和 Hive 开始面…

Redis一主二从搭建

Redis一主二从环境搭建 一主二从 准备工作 安装VMWare 下载镜像 创建下面的目录 Redis-Cluster master mastervmdk slave00 slave00vmdk slave01 slave00vmdk VMWare中安装CentOS7 自定义(高级) 默认 安装程序光盘映像文件 命名虚拟机&#xff0c;选择我们刚才创建的…

ASEMI代理ADAU1961WBCPZ-R7原装ADI车规级ADAU1961WBCPZ-R7

编辑&#xff1a;ll ASEMI代理ADAU1961WBCPZ-R7原装ADI车规级ADAU1961WBCPZ-R7 型号&#xff1a;ADAU1961WBCPZ-R7 品牌&#xff1a;ADI/亚德诺 封装&#xff1a;LFCSP-32 批号&#xff1a;2023 引脚数量&#xff1a;32 安装类型&#xff1a;表面贴装型 ADAU1961WBCPZ-…

通过简单demo让你秒懂Python的编译和执行全过程

基本说明 python 是一种解释型的编程语言&#xff0c;所以不像编译型语言那样需要显式的编译过程。然而&#xff0c;在 Python 代码执行之前&#xff0c;它需要被解释器转换成字节码&#xff0c;这个过程就是 Python 的编译过程。 DEMO演示讲解 假设我们有以下 Python 代码&…

常见安全设备

文章目录前言安全厂商安全设备种类拓扑图防火墙IDSIPSwaf上网行为管理器数据库审计系统全流量设备蜜罐态势感知前言 最近在了解安全设备的基本原理&#xff0c;简单做一下笔记。 安全厂商 深信服、浪潮、奇安信、绿盟、山石网科、启明星辰、安恒、360、新华3 安全设备种类 …