【读书笔记-《30天自制操作系统》-11】Day12

news2024/11/16 1:46:01

从本篇内容开始讲解定时器。本篇内容比较简单,首先介绍定时器的概念与设置方法,然后介绍超时的中断处理,并对中断处理函数进行了优化。
在这里插入图片描述

1. 定时器

定时器是操作系统中十分重要的功能。它的原理很简单,只是每隔一段时间发送一个中断给CPU。如果想要知道过了多长时间,只需要在中断处理程序中记录中断的次数就可以计算得到。

管理定时器也很简单,只需要对PIT(Programmable Interval Timer的缩写)进行设置即可。通过对PIT的设置,可以设置定时器每隔多长时间发送一次中断。PIT连接着IRQ的0号中断。设置PIT需要的命令如下:

  • AL = 0x34;OUT(0x43, AL);
  • AL = 中断周期的低8位;OUT(0x40, AL)
  • AL = 中断周期的高8位;OUT(0x40, AL)

实际设置的中断频率 = 单位时间时钟周期数(主频)/设定的数值
根据当前的主频,如果设定11932,则中断频率位100Hz,即10ms发生一次中断。把11932换算为16进制,则是0x2e9c。

设置PIT相关程序如下:

#define PIT_CTRL	0x0043
#define PIT_CNT0	0x0040

void init_pit(void)
{
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	return;
}

在主程序中调用init_pit函数:

struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	char s[40], keybuf[32], mousebuf[128];
	int mx, my, i;
	unsigned int memtotal, count = 0;
	struct MOUSE_DEC mdec;
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	struct SHTCTL *shtctl;
	struct SHEET *sht_back, *sht_mouse, *sht_win;
	unsigned char *buf_back, buf_mouse[256], *buf_win;

	init_gdtidt();
	init_pic();
	……

涉及到了中断,则需要将中断处理函数注册到IDT中,这些也是前面讲过的内容了。

	/* 注册IDT*/
	set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
	set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
_asm_inthandler20:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler20
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD
void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	/* 通知PIC, IRQ-00中断受理完毕 */
	/* 暂时无内容 */
	return;
}

这样定时器的中断处理就完成了。接下来就在中断处理中进行计时:

struct TIMERCTL {
	unsigned int count;
};

struct TIMERCTL timerctl;

void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	return;
}

新建了timerctl结构体,每次进入中断处理函数时就对其中的count变量增加1。在主程序中将count实时显示出来,根据之前的设置,每秒钟显示的数值会增加100。

	for (;;) {
		sprintf(s, "%010d", timerctl.count);
		boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43);
		putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s);
		sheet_refresh(sht_win, 40, 28, 120, 44);
		……

这样就可以测量一段确定的时间间隔了。
在这里插入图片描述
2. 超时与中断处理

2.1 超时处理

可以通过定时器记录一段时间间隔,那么操作系统就可以实现这样一种功能:
经过一段时间间隔后,进行某种特定的操作。这样的功能就被称为超时(timeout)。

为了实现这一功能,首先完善结构体TIMERCTL:

struct TIMERCTL {
	unsigned int count;
	unsigned int timeout;
	struct FIFO8 *fifo;
	unsigned char data;
};

其中timeout用来记录距离超时还有多长时间,这个时间达到0,则程序向缓冲区fifo中发送数据,这样来通知操作系统。
相关的函数修改如下:

void init_pit(void)
{
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	timerctl.count = 0;
	timerctl.timeout = 0;
	return;
}

void inthandler20(int *esp)
{
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	if (timerctl.timeout > 0) { /* 设置好了超时时间 */
		timerctl.timeout--;
		if (timerctl.timeout == 0) {
			fifo8_put(timerctl.fifo, timerctl.data);
		}
	}
	return;
}

void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
	int eflags;
	eflags = io_load_eflags();
	io_cli();
	timerctl.timeout = timeout;
	timerctl.fifo = fifo;
	timerctl.data = data;
	io_store_eflags(eflags);
	return;
}

在init_pit函数中首先将count和timeout变量初始化为0,而settimer函数用于给结构体其他成员赋值,尤其是设置好timeout。超时后的实际操作放在了inthandler20函数中实现。为了防止中断混乱,在settimer函数中还是先禁用了中断。
结合以上函数,在主程序中进行如下的实现:

void HariMain(void)
{
	……
	struct FIFO8 timerfifo;
	char s[40], keybuf[32], mousebuf[128], timerbuf[8];
	……
	fifo8_init(&keyfifo, 32, keybuf);
	settimer(1000, &timerfifo, 1);
	……
	for (;;) {
		……
		io_cli();
		if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) {
			io_sti();
		} else {
			if (fifo8_status(&keyfifo) != 0) {
			……
			} else if (fifo8_status(&mousefifo) != 0) {
				……
			} else if (fifo8_status(&timerfifo) != 0) {
				i = fifo8_get(&timerfifo); /* 首先读入,设定起始点 */
				io_sti();
				putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF, "10[sec]");
				sheet_refresh(sht_back, 0, 64, 56, 80);
			}
		}
	}
}

可以看出在10s后向timerfifo中写入了数据1,在主程序中,接收到数据时就在屏幕上显示10[sec]。

能够实现过10s显示一次,自然也可以调整时间的长度。并且操作系统中可能用到多个定时器,这样就可以将程序进行扩展:

#define MAX_TIMER		500
struct TIMER {
	unsigned int timeout, flags;
	struct FIFO8 *fifo;
	unsigned char data;
};
struct TIMERCTL {
	unsigned int count;
	struct TIMER timer[MAX_TIMER];
};

扩充了定时器的数量,其他程序也需要进行相应的修改:

#define TIMER_FLAGS_ALLOC		1	/* 已配置状态 */
#define TIMER_FLAGS_USING		2	/* 定时器运行中 */

void init_pit(void)
{
	int i;
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	timerctl.count = 0;
	for (i = 0; i < MAX_TIMER; i++) {
		timerctl.timer[i].flags = 0; /* ���g�p */
	}
	return;
}

struct TIMER *timer_alloc(void)
{
	int i;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == 0) {
			timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
			return &timerctl.timer[i];
		}
	}
	return 0; /* 无可用定时器 */
}

void timer_free(struct TIMER *timer)
{
	timer->flags = 0; /* 释放 */
	return;
}

void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
{
	timer->fifo = fifo;
	timer->data = data;
	return;
}

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout;
	timer->flags = TIMER_FLAGS_USING;
	return;
}

void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			timerctl.timer[i].timeout--;
			if (timerctl.timer[i].timeout == 0) {
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			}
		}
	}
	return;
}

类似的程序在内存分配与图层管理中都能看到,关于初始化、分配、释放等函数就不必细说了。在中断处理函数inthandler20中,每次进入中断函数,都会把所有运行中的定时器检查一遍,看是否超时。如果超时,则向fifo中写入数据。这样就可以设置多个定时器,设置不同的超时时间了。

2.2 加快中断处理

但这里有个问题。前面中断的内容中已经讲过,中断处理函数必须在短时间内完成,否则会耽误CPU的正常工作。而我们为了实现超时,每次进入inthandler20都要对所有活动中的定时器执行timeout–操作,即CPU要先读出变量的值,执行减法运算,再写回内存中,这样浪费了很多时间。是否可用对这种实现方式进行优化呢?

我们不用timeout来表示剩余多少时间超时,而是用来表示超时的时刻。因为当前的时刻可用通过count进行计数,每次中断时只需要比较count的值是否达到timeout,就可以判断是否超时了。这样就减少了每次中断CPU所需要做的减法运算,加快了中断的处理。

void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			if (timerctl.timer[i].timeout <= timerctl.count) {
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			}
		}
	}
	return;
}

相应地也需要修改timer_settime函数,将超时时刻设置为当前时刻加上超时时间:

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout + timerctl.count;
	timer->flags = TIMER_FLAGS_USING;
	return;
}

这样依赖程序确实得到优化了。但还没完,用来计时的变量count总有溢出的时候,0xffffffff表示的时间转换成天大约是497天。为了保证超时功能的正常,没过大约一年的时间需要把count重新调整归零:

int t0 = timerctl.count;/* 所有定时器的时刻都要减去这个值*/
io_cli();/* 调整时刻时需要禁止中断 */
timerctl.count -= t0;
for(i = 0;i < MAX_TIMERS; i++)
{
	if(timerctl.timer[i].flags == TIMER_FLAGS_USING)
	{
		timerctl.timer[i].timeout -= t0;
	}
}
io_sti();

已经做了以上的改进和优化,但其实中断处理程序还有改进的空间。
inthandler20其实是定时器的中断,每隔10ms就会产生一次中断并进入中断处理函数,这样每秒钟要进入100次中断函数。而每次中断函数都要执行500次的if判断检查运行中的定时器,一秒钟就要运行50000次。但真正产生超时,更改flags的值,执行fifo8_put函数,一秒钟最多也就2次,其他的大量if判断其实都是在做无用功。

为了避免这么多次无用且浪费时间的if判断,采用这种思路。超时时间有长短的不同,每次进入中断的时候,我们只需要关注当前最近的一个即将超时的定时器就可以了。我们把当前最近的一个超时时刻记录下来,每次进入中断的时候先与当前的时刻进行比较。如果最近的超时时刻没有达到,说明没有超时,这样就直接返回;如果发现达到了最近的超时时刻,再检查一遍定时器,找出这个超时的定时器,并且更新下一个最近的超时时刻。
中断处理的过程:

struct TIMERCTL {
	unsigned int count, next;
	struct TIMER timers0[MAX_TIMER];
};

void inthandler20(int *esp)
{
	int i;
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	if (timerctl.next > timerctl.count) {
		return; /* 最近的一个超时时刻还没有达到,直接返回 */
	}
	timerctl.next = 0xffffffff;
	for (i = 0; i < MAX_TIMER; i++) {
		if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
			if (timerctl.timer[i].timeout <= timerctl.count) {
				/* 找到了超时的定时器,设置flags */
				timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
				fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
			} else {
				/* 其他未超时的定时器中更新next */
				if (timerctl.next > timerctl.timer[i].timeout) {
					timerctl.next = timerctl.timer[i].timeout;
				}
			}
		}
	}
	return;
}

初始化与设置超时时间:

void init_pit(void)
{
	int i;
	io_out8(PIT_CTRL, 0x34);
	io_out8(PIT_CNT0, 0x9c);
	io_out8(PIT_CNT0, 0x2e);
	timerctl.count = 0;
	timerctl.next = 0xffffffff; /* 最初没有生效的定时器 */
	for (i = 0; i < MAX_TIMER; i++) {
		timerctl.timer[i].flags = 0; /* 没有使用中的定时器 */
	}
	return;
}

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	timer->timeout = timeout + timerctl.count;
	timer->flags = TIMER_FLAGS_USING;
	if (timerctl.next > timer->timeout) {
		/* 更新下一次的超时时刻 */
		timerctl.next = timer->timeout;
	}
	return;
}

其实到这里已经优化的不错了。但是吹毛求疵一点,未达到超时时刻,处理的速度很快;达到超时时刻了,处理的时间就明显增加,作者认为这样可能会导致偶尔很卡的问题。(这一点说实话自己完全想不到)

为了减少达到超时时刻后进行处理所用的时间,采用的措施是把定时器按照超时时间先后排好顺序,再记录下运行中的定时器个数,这样在查找的时候就不需要把所有的定时器都检查一遍了。

struct TIMERCTL {
	unsigned int count, next, using;
	struct TIMER *timers[MAX_TIMER];
	struct TIMER timers0[MAX_TIMER];
};

结构体中timers用于存储当前运行中的定时器,并且按照时间顺序排列好,using变量则用来存放当前运行中的定时器数量。

void inthandler20(int *esp)
{
	int i, j;
	io_out8(PIC0_OCW2, 0x60);	
	timerctl.count++;
	if (timerctl.next > timerctl.count) {
		return;
	}
	for (i = 0; i < timerctl.using; i++) {
		if (timerctl.timers[i]->timeout > timerctl.count) {
			break;
		}
		/* 达到超时时间,将flags清除 */
		timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
		fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
	}
	/* 有i个定时器超时,则timers中存放的其余定时器进行移位*/
	timerctl.using -= i;
	for (j = 0; j < timerctl.using; j++) {
		timerctl.timers[j] = timerctl.timers[i + j];
	}
	if (timerctl.using > 0) {
		timerctl.next = timerctl.timers[0]->timeout;
	} else {
		timerctl.next = 0xffffffff;
	}
	return;
}

另外还需要修改的是timer_settime函数,在设置的时候需要将timer注册到timers数组中的正确位置。同样在注册的时候也要先禁用中断。

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
	int e, i, j;
	timer->timeout = timeout + timerctl.count;
	timer->flags = TIMER_FLAGS_USING;
	e = io_load_eflags();
	io_cli();
	/* 搜索注册的位置 */
	for (i = 0; i < timerctl.using; i++) {
		if (timerctl.timers[i]->timeout >= timer->timeout) {
			break;
		}
	}
	/* 从i号之后全部后移一位,腾出一个空位 */
	for (j = timerctl.using; j > i; j--) {
		timerctl.timers[j] = timerctl.timers[j - 1];
	}
	timerctl.using++;
	/* 插入到空位上 */
	timerctl.timers[i] = timer;
	timerctl.next = timerctl.timers[0]->timeout;
	io_store_eflags(e);
	return;
}

到这里本篇的内容就完成了。本篇的内容还是比较简单的,也可以看出作者对与程序的运行有种执念,不只是要程序能够正常运行,还要进行种种优化。下一篇的内容仍然是定时器,可以看出作者对定时器内容的重视。敬请期待。

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

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

相关文章

接口自动化测试框架:SoapUI

SoapUI是一个非常流行的用于Web服务测试的工具。它允许你对SOAP和RESTful Web服务进行测试。在本篇文章中&#xff0c;我们将介绍SoapUI的背景、好处以及企业实际使用该工具的干货。 一、背景 在过去的几年中&#xff0c;Web服务变得越来越流行。由于不同的应用程序可以通过W…

新材料正在加速推动压铸领域3D打印技术应用

3D打印技术&#xff0c;以其独特的逐层累加粉末材料成型方式&#xff0c;正逐步解锁模具制造的无限可能&#xff0c;尤其在实现复杂几何构型与内部结构优化方面展现出非凡潜力。这一技术革新不仅提升了模具制造的精度与效率&#xff0c;还通过随形水路、随形透气钢等创新设计&a…

利润暴涨507%的携程,做对了什么?

关于旅行&#xff0c;1500年前&#xff0c;古罗马思想家圣奥古斯丁曾在《忏悔录》这样评价&#xff1a; “世界是一本书&#xff0c;而不旅行的人只读了其中的一页。” 如今&#xff0c;旅行更是承载了人们逃离工作、抛开忧虑的祈望&#xff0c;成为了理想中的“诗和远方”。…

Google play应用老包突然被暂停和删除了,什么原因?

相信不少开发者都遇到应用突然被暂停和删除的情况&#xff0c;被谷歌判为应用存在欺骗行为&#xff0c;还会在某种程度上“明确”指出应用违规的原因&#xff1a;“您的应用包含可能使用户、用户数据或设备面临风险的代码&#xff0c;以及/或您的应用未能兑现对用户的承诺。” …

治愈系风景视频素材下载网站有哪些?令人治愈美景素材库网站分享

当我们谈到视频制作时&#xff0c;风景视频素材总是能为作品带来生动的视觉效果和震撼的感官体验。无论是用于旅游宣传、自然纪录片&#xff0c;还是日常生活记录&#xff0c;优质的风景视频素材都是不可或缺的。尽管高清美丽的风景素材并不容易获取&#xff0c;特别是那些既免…

Runway删库跑路,真的run away了!

没有任何通知&#xff0c;Runway在Hugging Face上的内容全部删除了&#xff01; 目前具体原因不明。Runway的主页只留下了一句话&#xff1a; 我们不再对HuggingFace账号进行维护。 据悉&#xff0c;Runway在Hugging Face上&#xff0c;最火的、也是争议最大的项目&#xff0c;…

成为优秀自动化测试工程师的7个步骤!

成为优秀自动化测试工程师的7个步骤 自动化测试是软件开发过程中至关重要的一环&#xff0c;它可以提高测试效率、减少人工测试的错误率&#xff0c;并且可以随时重复执行以确保软件质量。成为一名优秀的自动化测试工程师需要不断学习和提升自己的技能。以下是从0到1的详细步骤…

注册免费的vps:infinityfree

首先是注册网址 https://dash.infinityfree.com/login 各位自行注册就好了 注册好后创建账户&#xff0c;选择最左边这个免费的就可以了 然后可以创建一个子域名&#xff0c;各位自行选择后缀和填写前面就好了 然后拉下来选择 I approve 然后创建账户 然后就可以打开控制面…

机器视觉--光源打光技巧

1.高角度环光与低角度环光? 这里的角度指的是与水平线之间的夹角,夹角介于0和90之间。一般夹角大于45度的是高角度光源。90度光源就是垂直往下面打光了,如图所示,这种打光的结果就是突出平面,有倒角的边缘部分可能不明显。呈现出中间亮,边缘暗; 低角度光,呈现的效果是…

css中变量

1. 定义变量 在 CSS 中&#xff0c;使用–前缀来定义变量。变量的命名可以由字母、数字、破折号和下划线组成&#xff0c;但必须以字母开头。变量的定义通常放在选择器的规则集内&#xff0c;或者在根元素&#xff08;:root&#xff09;中定义&#xff0c;以便全局使用。例如&a…

PHP智能化排队叫号系统微信小程序源码

&#x1f525;智能化排队叫号系统&#xff0c;让等待也变得优雅✨ &#x1f680;【告别冗长等待&#xff0c;拥抱智能时代】&#x1f680; 你还在为在银行、医院或政务大厅的长队而烦恼吗&#xff1f;智能化排队叫号系统&#xff0c;让这一切成为过去&#xff01;只需轻轻一扫…

第22周:调用Gensim库训练Word2Vec模型

目录 前言 一、Word2vec基本知识 1.1 Word2Vec是什么 1.2 Word2Vec两种主要模型架构 1.2.1 CBOW模型 1.2.2 Skip-gram模型 1.3 实例说明 1.4 调用方法 二、准备工作 2.1 安装Gensim库 2.2 对原始语料分词 2.2 添加自定义停用词 三、训练Word2Vec模型 四、模型应用…

XT4077 1.0A 具有 USB 接口兼容的线性电池管理芯片

产品概述 XT4077 是可以对单节可充电锂电池进行恒流/恒压充电的充电器电路元器件。该器件内部包括功率品体管&#xff0c;应用时不需要外部的电流检测电阻和阻流二极管。XT4077 只需要极少的外围元器件&#xff0c;并且符合 USB 总线技术规范&#xff0c;非常适合于便携式…

C语言模拟考试

目录 函数题求两个形参的乘积和商统计专业人数字符串指定位置插入求字符串中整数字符之和 编程题这是一道计算题买复印纸(误差)求给定数组元素的最大值统计数字字符分段函数求值逆序的三位数三天打鱼两天晒网计算天数 输出个等腰梯形吧幸运数字学生的平均成绩 找最长的字符串 函…

Vue3安装Element Plus

文章目录 安装使用包管理器安装配置&#xff1a;完整引入按需引入&#xff1a; 使用&#xff1a; 以下将参考Element Plus官网 一个 Vue 3 UI 框架 | Element Plus (element-plus.org)进行 安装 在创建好的项目文件控制台下安装&#xff1a; 使用包管理器 我们建议您使用包…

设计模式结构型模式之代理模式

结构型模式之代理模式 一、概念和使用场景1、概念2、核心思想3、java实现代理模式的方式4、使用场景 二、示例讲解1. 静态代理2. 动态代理 三、总结1、使用规则2、代理模式的优点包括&#xff1a;3、代理模式的缺点包括&#xff1a; 一、概念和使用场景 1、概念 代理模式是一…

低配电脑也能玩《黑神话:悟空》,上Finovy Cloud白嫖4090云桌面!

猿神&#xff01;启动&#xff01; 各位天命人&#xff0c;大家都玩上《黑神话&#xff1a;悟空》了吗&#xff1f;玩上的友友&#xff0c;又几周目了呢&#xff1f; 20号刚上线&#xff0c;《黑神话&#xff1a;悟空》火速攻上了微博热搜第一&#xff0c;网上的评论也层出不…

家里养宠物空气净化器有用吗?哪款最值得推荐?

家里养了一只猫和一条狗&#xff0c;幸福感翻倍上升。首先就是它能在这座城市给我极大的安慰&#xff0c;每次都不知道应该向谁诉说难过的时候&#xff0c;它们总能给我极大的安慰。它们除了给我安慰&#xff0c;还会给我带来新的朋友&#xff0c;毕竟自己一个人来到一座城&…

封装信号灯集相关API

由信号灯实现通信。 发送端send.c代码&#xff1a; #include <myhead.h> #include "./sem.h" #define PAGE_SIZE 4096 int main(int argc, const char *argv[]) {int semid semID_get(2);//创建2个信号灯key_t key ftok("./",U);if(key-1){perro…

书籍销售系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;书籍分类管理&#xff0c;书籍信息管理&#xff0c;订单管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;书籍信息&#xff0c;书籍资讯&#xff0…