当前位置: 首页 > news >正文

Linux高性能服务器编程——ch6笔记

第6章 高级I/O函数

6.1 pipe函数

用于创建一个管道,以实现进程间通信。
int pipe(int fd[2]);

读端文件描述符fd[0]和写端文件描述符fd[1]构成管道的两端,默认是阻塞的,fd[0]读出数据,fd[1]写入数据。管道内部传输的数据是字节流。
如果fd[1]的引用计数减少至0,即没有任何进程需要往管道中写入数据,则针对f[0]的read操作将返回0,即读取到了文件结束标记(EOF);反之,如果fd[0]计数减少至 0,即没有任何进程需要从管道读取数据,则针对fd[1]的write操作将失败,并引发SIGPIPE信号。
socketpair函数:双向管道,但仅能在本地使用。

6.2 dup函数和dup2函数

用于复制文件描述符,但不继承原文件描述符的属性。
int dup(int file_descriptor);
int dup2(int file_descriptor_one, int file_descriptor_two);

dup函数创建一个新的文件描述符(系统当前可用的最小整数),与原有file_descriptor指向相同文件、管道或者网络连接。
dup2函数类似,但返回第一个不小于file_descriptor_two的整数。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>int main(int argc, char *argv[])
{if (argc < 2){printf("usage: %s ip_address port_number\n", basename(argv[0]));return 1;}const char *ip = argv[1];int port = atoi(argv[2]);struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET, ip, &address.sin_addr);address.sin_port = htons(port);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock > 0);int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));assert(ret != -1);ret = listen(sock, 5);assert(ret != -1);struct sockaddr_in client;socklen_t client_addrlength = sizeof(client);int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);if (connfd < 0){printf("errno is : %d\n", errno);}else{close(STDOUT_FILENO);  //关闭标准输出文件描述符,值为1dup(connfd);  //返回最小可用文件描述符,返回1
// 服务器输出到标准输出的内容就会直接发送到与客户连接对应的 socket 上
// 因此 printf 的输出将被客户端获得(而不是显示在服务器程序的终端上)printf("abcd\n");close(connfd);}close(sock);return 0;
}

6.3 readv函数和writev函数

ssize_t readv(int fd, const struct iovec* vector, int count);
ssize_t writev(int fd, const struct iovec* vector, int count);

readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。
当Web服务器解析完一个HTTP请求之后,如果目标文档存在,且客户具有读取该文档的权限,那么它就需要发送一个HTTP应答来传输该文档。这个HTTP应答包含1个状态行、多个头部字段、1个空行和文档的内容。前3部分的内容可能被Web服务器放置在一块内存中,而文档的内容则通常被读到另外一块单独的内存中(通过read函数或mmap函数)。我们并不需要把这两部分内容拼接到一起再发送,而是可以使用writev函数将它们同时写出。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>#define BUFFER_SIZE 1024/* 定义两种HTTP状态吗和状态信息 */
static const char *status_line[2] = {"200 OK", "500 Internal server error"};int main(int argc, char *argv[])
{if (argc < 3){printf("usage: %s ip_address port_number filename\n", basename(argv[0]));return 1;}const char *ip = argv[1];int port = atoi(argv[2]);/* 将目标文件作为程序的第三个参数传入 */const char *file_name = argv[3];struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET, ip, &address.sin_addr);address.sin_port = htons(port);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock >= 0);int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));assert(ret != -1);ret = listen(sock, 5);assert(ret != -1);struct sockaddr_in client;socklen_t client_addrlength = sizeof(client);int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);if (connfd < 0){printf("errno is : %d\n", errno);}else{/* 用于保存HTTP应答的状态行、头部字段和一个空行的缓冲区 */char header_buf[BUFFER_SIZE];memset(header_buf, '\0', BUFFER_SIZE);/* 用于存放目标文件内容的应用程序缓存 */char *file_buf = NULL;/* 用于获取目标文件的属性,比如是否为目录,文件大小等 */struct stat file_stat;/* 记录目标文件是否是有效文件 */bool valid = true;/* 缓冲区header_buf目前已经使用了多少字节的空间 */int len = 0;if (stat(file_name, &file_stat) < 0) /* 目标文件不存在 */{valid = false;}else{if (S_ISDIR(file_stat.st_mode)) /* 目标文件是一个目录 */{valid = false;}else if (file_stat.st_mode & S_IROTH) /* 当前用户有读取目标文件的权限 */{/* 动态分配缓存区file_buf, 并制定其大小为目标文件的大小* file_stat.st_size 加1, 然后将目标文件读入缓存区file_buf中 */int fd = open(file_name, O_RDONLY);file_buf = new char [file_stat.st_size + 1];memset(file_buf, '\0', file_stat.st_size + 1);if (read(fd, file_buf, file_stat.st_size) < 0){valid = false;}}else{valid = false;}}/* 如果目标文件有效,则发送正常的HTTP应答 */if (valid){/* 下面这部分内容将HTTP应答的状态行、“Content-Length”头部字段和一个空行* 依次加入header_buf中 */ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n","HTTP/1.1", status_line[0]);len += ret;ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len,"Content-Length: %d\r\n", file_stat.st_size);len += ret;ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len,"%s", "\r\n");/* 利用writev将header_buf和file_buf的内容一并写出 */struct iovec iv[2];iv[0].iov_base = header_buf;iv[0].iov_len = strlen(header_buf);iv[1].iov_base = file_buf;iv[1].iov_len = file_stat.st_size;ret = writev(connfd, iv, 2);}else /* 如果目标文件无效,则通知客户端服务器发生了"内部错误" */{ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s %s\r\n","HTTP/1.1", status_line[1]);len += ret;ret = snprintf(header_buf + len, BUFFER_SIZE - 1 - len, "%s","\r\n");send(connfd, header_buf, strlen(header_buf), 0);}close(connfd);delete[] file_buf;}close(sock);return 0;
}

省略了HTTP请求的接收及解析,该代码只关注HTTP应答的发送。

6.4 sendfile函数

在内核中操作,在两个文件描述符之间直接传递数据。避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。
ssize_t sendlile(int out_fd, int in_fd, off_t* offset, size_t count);

in_fd(待读出)必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;而out_fd(待写入)则必须是一个socket。由此可见,sendfile几乎是专门为在网络上传输文件而设计的。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>int main(int argc, char *argv[])
{if (argc <= 3){printf("usage: %s ip_address port_numberr filename\n", basename(argv[0]));return 1;}const char *ip = argv[1];int port = atoi(argv[2]);const char *file_name = argv[3];int filefd = open(file_name, O_RDONLY);assert(filefd > 0);struct stat stat_buf;fstat(filefd, &stat_buf);struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET, ip, &address.sin_addr);address.sin_port = htons(port);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock > 0);int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));assert(ret != -1);ret = listen(sock, 5);assert(ret != -1);struct sockaddr_in client;socklen_t client_addrlength = sizeof(client);int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);if (connfd < 0){printf("errno is : %d\n", errno);}else{sendfile(connfd, filefd, NULL, stat_buf.st_size);close(connfd);close(filefd);}close(sock);return 0;
}

没有为目标文件分配任何用户空间的缓存,也没有执行读取文件的操作,但同样实现 了文件的发送。

6.5 mmap函数和munmap函数

mmap函数用于申请一段内存空间。可以将这段内存作为进程间通信的共享(也可为调用进程所私有)内存,
也可以将文件内接映射到其中。munmap函数则释放由mmap创建的这段内存空间。

void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);

6.6 splice函数

用于在两个文件描述符之间移动数据,也是零拷贝。

ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags);

fd_in和fd_out必须至少有一个是管道文件描述符。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>int main(int argc, char *argv[])
{if (argc <= 2){printf("usage: %s ip_address port_number\n", basename(argv[0]));return 1;}const char *ip = argv[1];int port = atoi(argv[2]);struct sockaddr_in address;bzero(&address, sizeof(address));address.sin_family = AF_INET;inet_pton(AF_INET, ip, &address.sin_addr);address.sin_port = htons(port);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock >= 0);int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));assert(ret != -1);ret = listen(sock, 5);assert(ret != -1);struct sockaddr_in client;socklen_t client_addrlength = sizeof(client);int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);if (connfd < 0){printf("errno is : %d\n", errno);}else{int pipefd[2];ret = pipe(pipefd); /* 创建管道 */assert(ret != -1);/* 将connfd上流入的客户数据定向到管道中 */ret = splice(connfd, NULL, pipefd[1], NULL, 32768,SPLICE_F_MORE | SPLICE_F_MOVE);assert(ret != -1);/* 将管道的输出定向到connfd客户连接文件描述符 */ret = splice(pipefd[0], NULL, connfd, NULL, 32768,SPLICE_F_MORE | SPLICE_F_MOVE);assert(ret != -1);close(connfd);}close(sock);return 0;
}

通过splice函数将客户端的内容读入到pipefd[1]中,然后再使用splice函数从pipefd[0]中读出该内容到客户端,从而实现了简单高效的回射服务。整个过程未执行recv或send操作,因此也未涉及用户空间和内核空间之间的数据拷贝。

6.7 tee函数

在两个管道文件描述符之间复制数据,也是零拷贝操作。它不消耗数据,因此源文件描述符上的数据仍然可以用于后续的读操作。

ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>int main(int argc, char *argv[])
{if (argc != 2){printf("usage: %s <file>\n", basename(argv[0]));return 1;}int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);assert(filefd > 0);int pipefd_stdout[2];int ret = pipe(pipefd_stdout);assert(ret != -1);int pipefd_file[2];ret = pipe(pipefd_file);assert(ret != -1);/* 将标准输入内容输入管道pipefd_stdout */ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL,32768, SPLICE_F_MORE | SPLICE_F_MOVE);assert(ret != -1);/* 将管道pipefd_stdout 的输出复制到管道pipefd_file的输入端 */ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK);assert(ret != -1);/* 将管道pipefd_file的输出定向到文件描述符filefd上,从而将标准输入的内容写入文件 */ret = splice(pipefd_file[0], NULL, filefd, NULL,32768, SPLICE_F_MORE | SPLICE_F_MOVE);assert(ret != -1);/* 将管道pipefd_stdout 的输出定向到标准输出,其内容和写入文件的内容完全一致 */ret = splice(pipefd_stdout[0], NULL, STDOUT_FILENO, NULL,32768, SPLICE_F_MORE | SPLICE_F_MOVE);assert(ret != -1);close(filefd);close(pipefd_stdout[0]);close(pipefd_stdout[1]);close(pipefd_file[0]);close(pipefd_file[1]);return 0;
}

6.8 fcntl函数

提供了对文件描述符的各种控制操作,另外一个常见的控制文件描述符属性和行为的系统调用是ioctl,而且ioctl比fcntl能够执行更多的控制。但是,对于控制文件描述符常用的属性和行为,fcntl函数是由POSIX规范指定的首选方法。
网络编程中,fcntl函数通常用来将一个文件描述符设置为非阻塞的。
SIGIO和SIGURG这两个信号与其他Linux信号不同,它们必须与某个文件描述符相关联方可使用:当被关联的文件描述符可读或可写时,系统将触发 SIGIO信号;当被关联的文件描述符(而且必须是一个socket)上有带外数据可读时,系统将触发 SIGURG信号。将信号和文件描述符关联的方法,就是使用fcntl函数为目标文件描述符指定宿主进程或进程组,那么被指定的宿主进程或进程组将捕获这两个信号。使用 SIGIO时,还需要利用fcntl设置其O_ASYNC标志(异步I/O标志,不过SIGIO信号模型并非真正意义上的异步I/O模型)。

相关文章:

Linux高性能服务器编程——ch6笔记

第6章 高级I/O函数 6.1 pipe函数 用于创建一个管道&#xff0c;以实现进程间通信。 int pipe(int fd[2]); 读端文件描述符fd[0]和写端文件描述符fd[1]构成管道的两端&#xff0c;默认是阻塞的&#xff0c;fd[0]读出数据&#xff0c;fd[1]写入数据。管道内部传输的数据是字节…...

【C语言进阶】文件操作

文件操作 1. 为什么使用文件2. 什么是文件2.1程序文件2.2 数据文件2.3 文件名 3. 文件的打开和关闭3.1 文件指针3.2 文件的打开和关闭 4. 文件的顺序读写4.1 对比一组函数 5. 文件的随机读写5.1 fseek5.2 ftell5.3 rewind 6. 文本文件和二进制文件7. 文件读取结束的判定7.1 被错…...

Redis学习(第八章缓存策略)

目录 RdisExample 课程介绍 1.Redis介绍 2.Redis 安装 3. Redis的数据结构 4. Redis缓存特性 5. Redis使用场景 6. Redis客户端-Jedis 7. Jedis Pipeline 8. Redis缓存策略 学习资料 QA 相关问题 http, socket ,tcp的区别 RdisExample 项目代码地址&#xff1a;htt…...

springboot+vue开发的视频弹幕网站动漫网站

springbootvue开发的视频弹幕网站动漫网站 演示视频 https://www.bilibili.com/video/BV1MC4y137Qk/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae139b 功能&#xff1a; 前台&#xff1a; 首页&#xff08;猜你喜欢视频推荐&#xff09;、轮播图、分类…...

【CSS】常见 CSS 布局

1. 响应式布局 <!DOCTYPE html> <html><head><title>简单的响应式布局</title><style>/* 全局样式 */body {font-family: Arial, sans-serif;margin: 0;padding: 0;}/* 头部样式 */header {background-color: #333;color: #fff;padding: …...

数据结构---HashMap和HashSet

HashMap和HashSet都是存储在哈希桶之中&#xff0c;我们可以先了解一些哈希桶是什么。 像这样&#xff0c;一个数组数组的每个节点带着一个链表&#xff0c;数据就存放在链表结点当中。哈希桶插入/删除/查找节点的时间复杂度是O(1) map代表存入一个key值&#xff0c;一个val值…...

SLAM中相机姿态估计算法推导基础数学总结

相机模型 基本模型 内参 外参 对极几何 对极约束 外积符号 基础矩阵F和本质矩阵E 相机姿态估计问题分为如下两步: 本质矩阵 E t ∧ R Et^{\wedge}R Et∧R因为 t ∧ t^{\wedge} t∧其实就是个3x3的反对称矩阵&#xff0c;所以 E E E也是一个3x3的矩阵 用八点法估计E…...

【RS】遥感影像/图片64位、16位(64bit、16bit)的意义和区别

在数字图像处理中&#xff0c;我们常常会听到不同的位数术语&#xff0c;比如64位、16位和8位&#xff08;64bit、16bit、8bit&#xff09;。这些位数指的是图像的深度&#xff0c;也就是图像中每个像素可以显示的颜色数。位数越高&#xff0c;图像可以显示的颜色数就越多&…...

【单元测试】--基础知识

一、什么是单元测试 单元测试是软件开发中的一种测试方法&#xff0c;用于验证代码中的单个组件&#xff08;通常是函数、方法或类&#xff09;是否按预期工作。它旨在隔离和测试代码的最小单元&#xff0c;以确保其功能正确&#xff0c;提高代码质量和可维护性。单元测试通常…...

golang 反射机制

在 go 语言中&#xff0c;实现反射能力的是 reflect包&#xff0c;能够让程序操作不同类型的对象。其中&#xff0c;在反射包中有两个非常重要的 类型和 函数&#xff0c;两个函数分别是&#xff1a; reflect.TypeOfreflect.ValueOf 两个类型是 reflect.Type 和 reflect.Value…...

【Javascript】创建对象的几种方式

通过字面量创建对象 通过构造函数创建对象 Object()-------------构造函数 通过构造函数来实例化对象 给person注入属性 Factory工厂 this指向的是对象的本身使⽤new 实例化⼀个对象&#xff0c;就像⼯⼚⼀样...

深度学习_3_实战_房价预测

梯度 实战 代码&#xff1a; # %matplotlib inline import random import torch import matplotlib.pyplot as plt # from d21 import torch as d21def synthetic_data(w, b, num_examples):"""生成 Y XW b 噪声。"""X torch.normal(0,…...

HCIA -- 动态路由协议之RIP

一、静态协议的优缺点&#xff1a; 缺点&#xff1a; 1、中大型网络配置量过大 2、不能基于拓扑的变化而实时的变化 优点&#xff1a; 1、不会额外暂用物理资源 2、安全问题 3、计算路径问题 简单、小型网络建议使用静态路由&#xff1b;中大型较复杂网络&#xff0c;建议使用…...

JS常用时间操作moment.js参考文档

Moment.js是一个轻量级的JavaScript时间库&#xff0c;它方便了日常开发中对时间的操作&#xff0c;提高了开发效率。日常开发中&#xff0c;通常会对时间进行下面这几个操作&#xff1a;比如获取时间&#xff0c;设置时间&#xff0c;格式化时间&#xff0c;比较时间等等。下面…...

基于 FFmpeg 的跨平台视频播放器简明教程(九):Seek 策略

系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程&#xff08;一&#xff09;&#xff1a;FFMPEG Conan 环境集成基于 FFmpeg 的跨平台视频播放器简明教程&#xff08;二&#xff09;&#xff1a;基础知识和解封装&#xff08;demux&#xff09;基于 FFmpeg 的跨平台视频…...

设计模式_备忘录模式

备忘录模式 介绍 设计模式定义案例问题堆积在哪里解决办法备忘录模式行为型模式&#xff0c; 保存了数据某一个时间点的状态 在需要的时候进行回档单机游戏的角色 数据保存并且回档保存和回档加一个状态管理类 类图 代码 MomentData using UnityEngine;public class MomentD…...

双势阱模型

双势阱模型 原子钟 传统的原子钟利用氨分子 由于隧道效应&#xff0c;上顶点的氮原子可以贯穿三个氢原子形成的势垒&#xff0c;到达下顶点对体系注入微波能量后&#xff0c;氮原子在上下定点之间振荡&#xff0c;体系的能量在两个稳定态之间交替变换&#xff0c;其振荡频率决…...

文献阅读:The Reversal Curse: LLMs trained on “A is B” fail to learn “B is A”

文献阅读&#xff1a;The Reversal Curse: LLMs trained on “A is B” fail to learn “B is A” 1. 文章简介2. 实验 & 结果考察 1. finetune实验2. 真实知识问答 3. 结论 & 思考 文献链接&#xff1a;https://arxiv.org/abs/2309.12288 1. 文章简介 这篇文章是前…...

真实感受:是智能家居在选择合适的技术!

科技从来都是为了让我们的生活更加的简单、舒适&#xff0c;而智能家居的智能&#xff0c;体现在如何更更更方便的使用我需要控制的家居。 例如&#xff1a;下班躺在床上想休息&#xff0c;房间和大厅的灯还开着&#xff0c;这时你会选择什么产品躺着解决问题&#xff1f; 红外…...

前端 TS 快速入门之二:接口

1. 接口有什么用 通过 interface 定义接口。 检测对象的属性&#xff0c;不会去检查属性的顺序&#xff0c;只要相应的属性存在并且类型也是对的就可以。 interface IPerson {name: string;age: number; } function say(person: IPerson): void {console.log(my name is ${pers…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分&#xff1a;派生类构造函数与析构函数 当创建一个派生类对象时&#xff0c;基类成员是如何初始化的&#xff1f; 1.当派生类对象创建的时候&#xff0c;基类成员的初始化顺序 …...

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)

cd /home 进入home盘 安装虚拟环境&#xff1a; 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境&#xff1a; virtualenv myenv 3、激活虚拟环境&#xff08;激活环境可以在当前环境下安装包&#xff09; source myenv/bin/activate 此时&#xff0c;终端…...

如何做好一份技术文档?从规划到实践的完整指南

如何做好一份技术文档&#xff1f;从规划到实践的完整指南 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...