基于DOS的多任务系统的实现

2024-08-29 15:08
文章标签 实现 系统 多任务 dos

本文主要是介绍基于DOS的多任务系统的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一, 课程设计的目的

通过对线程(和进程)的创建和撤销,CPU的调度,同步机制,通信机制的实现,以达到一下目的:
1, 加深对线程和进程概念的理解,明确进程和程序的区别。
2, 加深对CPU调度过程(现场保护,CPU的分派和现场的恢复)的理解。
3, 进一步认识并执行的概念,明确顺序执行和并发执行的区别。
4, 加深对临界资源,临界区,信号量以及同步机制的理解。
5, 加深对消息缓冲通信的理解。

二, 设计要求

1, 用C语言完成线程的创建和撤销,并按优先权加时间片轮转算法对多线程进行调度。
2, 改变时间片的大小,观察结果的变化,
3, 假设两个线程共用同一软件资源(如某以变量,或者某以数据结构),请用记录型信号量来实现对它的互斥访问。
4, 假设有两个线程共享一个可以存放5个整数的缓冲,一线程不停地计算1至50的平方,并将结构放入缓冲中,另一个线程不断地从缓冲中取出结果,并将它们打印出来,请用记录型信号量实现这一生产者和消费者的同步问题。
5, 实现消息缓冲通信,并与3,4中的简单通信进行比较。

三, 程序设计思想以及总流程图

1, 程序的设计思想:

该程序主要是分5大块内容:线程的创建和撤销,线程的调度,线程的同步与互斥,线程的阻塞与唤醒,利用消息缓冲队列的线程间的通信。由这五大块功能来完成的基于DOS的多任务系统的实现。在这个系统中,首先先由main函数进行一些初始化工作,然后直接创建0#线程对应于main函数,再由0#线程调用create创建1#,2#线程分别对应与函数f1(),f2(),最后将系统的中断服务程序设置为new_int8,并把控制交给1#线程,启动多个线程的并发执行。
0#线程是一个比较特殊的线程,它在创建的时候没有使用create来创建,而是在系统初始化后直接创建的,因它对应的程序段为main函数中的一段,所以也直接使用整个系统的堆栈,而不再创建时为私有堆栈分配额外的空间;同样,撤销的时也不需要释放私有堆栈的空间,所以也没有over()函数而是直接撤销,从这方面来看,它是一个系统线程。
此外,在启动多个线程并发执行过程后,0#线程将系统控制权转交出去,直至系统中其他进程都不具备执行条件时,它才有可能重新得到CPU,从这方面看,0#线程相当于是一个空转线程,最后,0#线程还担负着一个特别的使命:等待系统中所有其他的线程的完成,此时,它将直接撤销自己并恢复原来的时钟中断服务程序,从此终止整个多任务系统。


2, 系统的总流程图 

 


四, 系统各个功能的实现思想

1, 线程的创建和撤销

线程的创建过程关键就是对私有堆栈和TCB初始化的过程,其过程如下:
i, 为新线程分配一空闲的线程控制块
ii, 为新线程的私有堆栈分配内存空间(因为对等线程共享程序段和数据段空间,所以创建线程时不必像创建进程那样再为程序段和数据段分配内存空间)
iii, 初始化新线程的私有堆栈,即按CPU调度时现场信息的保存格式布置堆栈。
iv, 初始化线程控制块,即填入线程的外部标识符,设置好线程私有堆栈的始址,段址和栈顶指针,将线程的状态置为就绪状态。
v, 最后哦返回新线程的内部标识符

vi, 线程的内存映像如下:


 线程的撤销过程中,一个关键的地方是在初始化线程私有堆栈时 需要将over()的入口地址压入线程的私有堆栈中,这样做的好处是:当线程所对应的函数正常结束时,over()函数的入口地址将最为函数的返回地址被弹出至CS,IP寄存器,那么控制将自动转向over(),从而使对应的线程被自动撤销,并重新进行CPU调度。

2, 线程的调度

引起CPU调度原因主要是有三种情况:时间片到时,线程执行完毕或正在执行的线程因等待某种事件而不能继续执行。
由这些原因,调度程序可以通过两个函数分别处理不同原因引起的调度:
New_int8()函数主要是处理因时间片到时引起的调度该调度可以通过截取时钟中断(int 08)来完成;
Swtch()函数主要是处理因其他原因引起的调度;
New_int8()函数因为是通过截取时钟中断来实现,可以知道其是属于系统调度,由于涉及到系统调度的函数 都是需要对DOS状态进行判断,以防止出现系统数据混乱等情况的发生(从Dos的不可重入性来得出),而Swtch()函数是处理因其他原因引起的调度,所以它所涉及到的仅仅是用户级的函数调度,没有涉及到系统级的函数调度,因此Swtch()函数不需要对Dos状态进行判断。对于线程的两种调度函数的过程,因其相似,给出New_int8()函数的执行过程图,如下:
 
需要主要的是:新的时钟中断处理程序不能太长,否则系统效率将大大下降甚至使系统无法正常工作;在新的时钟中断处理程序必须调用系统原来的INT 08H,否则将影响磁盘马达的关闭和系统的计时,另外,我们还主要依赖原来的INT 08H向中断控制器发中断结束指令(EOI);

3, 线程的阻塞与唤醒

线程的阻塞:主要是当某一线程需要阻塞的时候,将其插入阻塞队列中,等待唤醒进程唤醒,所以其过程为:首先,将线程的状态置为阻塞态,然后将线程插入指定的阻塞队列末尾,并重新进行CPU调度。
线程的唤醒:主要是唤醒阻塞队列里面的线程,所以其过程是:把阻塞队列头上的第一个线程的TCB取下来,并将其状态改为就绪状态,等待CPU调度

4, 线程的同步与互斥

在这个系统中是采用记录型信号量机制来实现同步与互斥的,实现的方法:
采用P ,V操作,设置两个信号量:一个为互斥信号量,一个为临界资源数目;

5, 利用消息缓冲队列的线程间通信

线程间的通信,关键采用send()与receive()来实现,通过发送一个文本信息来显示通信的过程,其过程为:
send()函数:消息的发送者需要提供接收者的标识符,消息的长度以及消息正文的起始地址等信息,然后在发送原语里申请一空闲的消息缓冲区,用相应的信息来装配该消息缓冲区,并把它插入到接收者的消息队列中去。

Receive()函数:消息的接受者必须给出发送者的标识符,接受区的起始地址等信息,然后从自己的消息队列中取得相应的发送者发送来的消息缓冲区,将消息正文复制到接受区中,并释放相应的消息缓冲区。

五、详细程序设计

#include <stdlib.h>
#include <stdio.h>
#include <dos.h>
/***********************定义***********************/
#define GET_INDOS 0x34
#define GET_CRIT_ERR 0x5d06/*定义四个状态*/
#define finished 0
#define running 1
#define ready 2
#define blocked 3#define TL 3			/*设置TL(时间片)时间为3*/
#define NTCB 10		/*NTCB是系统允许的最多任务数也就是进程数*/
#define NBUF 5					
#define NTEXT 30/**********************声明变量********************/
char far *indos_ptr=0;
char far *crit_err_ptr=0;
int current;			/*全部变量,始终等于正在执行的线程的内部标识符*/
int timecount=0;		/*全局变量,等于上次调度至今的时间,在每次时钟中断发生时,timecount+1,通过它与TL课判断时间片是否到时,从而决定是否进行CPU调度*//********************定义数据结构********************/typedef int (far *codeptr)(void);/*定义codeptr函数指针*/
/*定义记录型信号量的数据结构*/
typedef struct
{int value;struct TCB *wq;}semaphore;
semaphore mutexfb={1,NULL};		/*互斥信号量*/
semaphore sfb={NBUF,NULL};		/*空闲缓冲队列的计数信号量*//*消息缓冲区的数据结构*/
struct buffer
{int sender;					/*消息发送者的标识数*/int size;						/*消息长度<=NTEXT个字节*/char text[NTEXT];				/*消息正文*/struct buffer *next;				/指向下一个消息缓冲区的指针*/
};
struct buffer *freebuf;			/*空闲消息缓冲队列,是临界资源,由NBUF个空闲的消息缓冲区组成*//*定义TCB数据结构*/
struct TCB{unsigned char *stack;		/*堆栈的起始地址*/unsigned ss;				/*堆栈的段址*/unsigned sp;				/*堆栈的栈指针*/char state;				/*线程的状态*/char name[10];			/*线程的外部标示符*/struct TCB* next;			/*链接字段,把所有就绪的线程按某种方式排成一显式队列,如优先权从高到底的队列*/struct buffer *mq;			/*消息队列队首指针*/semaphore mutex;			/*消息队列的互斥信号量*/semaphore sm;			/*消息队列计数信号量*/int value;
} tcb[NTCB];					/*NTCB是系统允许的最多任务数*/
/*现场保护和恢复中要用到的一个数据结构*/
struct int_regs{unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flags,off,seg;};
/**************************声明函数*************************/
int DosBusy(void);
void InitInDos(void);
void InitTcb(void);					/*对TCB的初始化*/
int create(char *name,codeptr code,int stacklen);
void over(void);					/*撤销线程,归还所占资源*/
void interrupt(*old_int8)(void);	    /*原来的时间中断程序,需要先声明*/
void interrupt new_int8(void);		/*因时间片到时而引起的调度由new_int8()函数来完成*/
void interrupt swtch(void);	/*其他原因引起的CPU调度由函数swtch()完成*/
void tcb_state(void);		/*输出所有线程的状态信息*/
int all_finished(void);
void p(semaphore *sem);	/*信号量P操作*/
void v(semaphore *sem);	/*信号量V操作*//*********************函数的实现*********************//*******InitInDos函数的实现********/
void InitInDos(void){union REGS regs;struct SREGS segregs;/*获得INDOS flag 的地址*/regs.h.ah=GET_INDOS;intdosx(&regs,&regs,&segregs),indos_ptr=MK_FP(segregs.es,regs.x.bx);/*get the address of CRIT_ERR flag*/if(_osmajor<3)crit_err_ptr=indos_ptr+1;else if(_osmajor==3 && _osminor==0)crit_err_ptr=indos_ptr-1;else{regs.x.ax=GET_CRIT_ERR,intdosx(&regs,&regs,&segregs);crit_err_ptr=MK_FP(segregs.ds,regs.x.si);}
}/*************DosBusy函数的实现************/
int DosBusy(void){if(indos_ptr&&crit_err_ptr)return (*indos_ptr|| *crit_err_ptr);elsereturn (-1);
}/************InitTcb函数的实现*************/
/*对TCB进行初始化*/
void InitTcb(void){int i;for(i=1;i<NTCB;i++){tcb[i].stack=NULL;tcb[i].state=finished;strcpy(tcb[i].name,'\0');tcb[i].mq=NULL;tcb[i].sm.value=0;				/*消息队列计数信号量*/tcb[i].mutex.value=1;			/*缓冲区的互斥信号量*/}
}/*************create函数的实现****************/
/*创建一对应于函数name(外部标识符)的线程*/
int create(char *name,codeptr code, int stacklen){int i;char *p;struct int_regs *pt;/*第一步:寻找空白的TCB*/for(i=1;i<NTCB;i++){if(tcb[i].state==finished)break;}/*第二步:申请线程的私有堆栈内存空间,分配stacklen个字节长度的内存空间,利用malloc函数返回内存地址指针指向该内存空间,所返回的值是该内存空间的起始地址*/p=(char *)malloc(stacklen*sizeof(char));/*获得堆栈的内存空间的高地址指针*/p=p+stacklen;/*对地址进行类型转换*/pt=(struct int_regs*)p;pt--;/*第三步:对线程的私有堆栈进行初始化;用FP_SEG和FP_OFF分别获得栈顶指针和栈顶指针的偏移量,此外系统对线程撤销的工作,需要在该步骤完成:通过在堆栈初始化时 原先将over()函数的入口地址压入线程的私有堆栈中;那么当线程所对应的函数正常结束时,over()函数的入口地址将作为函数的返回地址被弹出到CS,IP寄存器,控制自动转向over()函数执行*/pt->flags=0x200;			/*flags寄存器的允许中断位*/pt->cs=FP_SEG(code);		/*代码段的段地址*/pt->ip=FP_OFF(code);		/*代码段的段内偏移地址*/pt->ds=_DS;					/*数据段的段地址*/pt->es=_ES;					/*附加数据段的段地址*/pt->off=FP_OFF(over);		/*撤销线程代码的偏移地址*/pt->seg=FP_SEG(over);		/*撤销线程代码的段址*//*第四步:初始化线程的控制块TCB*/strcpy(tcb[i].name,name);	/*填入线程的外部标识符*/tcb[i].state=ready;			/*将线程的状态置成就绪态*/tcb[i].stack=p-stacklen;		/*私有堆栈的起始地址*/tcb[i].ss=FP_SEG(pt);			/*当前线程的段地址*/tcb[i].sp=FP_OFF(pt);			/*当前线程的栈顶指针*/return i;					/*返回线程的内部标示符*/
}/************new_int8函数的实现***************/
/*系统调度,即时间中断到达后,判断时间片到后才运行,调用老的时钟中断*/
void interrupt new_int8(void){int i;(*old_int8)();				/*调用原来的时钟中断服务程序*/timecount++;				/*每次发生中断时加1*/if(timecount>=TL){			/*时间片到时*/if(DosBusy())			/*如果Dos忙*/return;disable();				/*关中*//*保护正在执行的线程current的现场,暂停它的执行*/tcb[current].ss=_SS;tcb[current].sp=_SP;if(tcb[current].state==running)	/*将执行状态变为就绪状态,暂停执行*/tcb[current].state=ready;/*找到以新的就绪线程*/for(i=1;i<NTCB;i++){if(tcb[i].state==ready && i!=current)	/*找到除了当前线程的其他就绪线程*/break;		}/*如果没有找到就绪线程,那么就回复当前线程,继续执行*/if(i>=NTCB){if(tcb[current].state==ready)tcb[current].state=running;enable();return;			/*如果超出了NTCB则恢复现场然后返回*/}/*如果找到就绪线程,那么恢复线程i的现场,把CPU分配给它*/_SS=tcb[i].ss;_SP=tcb[i].sp;tcb[i].state=running;/*置线程i为现有线程,并且重新开始计时*/current=i;timecount=0;enable();							/*开中*/}return;}/*********swtch函数的实现************/
/*针对Swtch()函数的实现:由于它是解决由其他因素所引起的CPU调度,在这个实现过程,只需要判断线程的执行状态即可,其他阻塞等状态不需要进行判断,或者可以直接对当前线程的现场进行保护,然后寻找就绪线程,分配CPU以及现场进行执行*/
/*Find()函数是为了寻找就绪线程而且是优先权大的线程(根据优先数越大,优先权越小的思想,在TCB设置以优先数,然后进行选择)*/
int Find()
{int i,j;for(i=0;i<NTCB;i++)if(tcb[i].state==ready&&i!=current)break;if(i==NTCB)return -1;for(j=i+1;j<NTCB;j++){if(tcb[j].state==ready&&j!=current)if(tcb[j].value>tcb[i].value)i=j;}return i;
}
/*swtch()调度,手工调度才能运行,处理因其他因素引起的中断*/void interrupt swtch(void)
{int i;i=Find();if(i<0)i=0;disable();tcb[current].ss=_SS;tcb[current].sp=_SP;if(tcb[current].state==running)tcb[current].state=ready;_SS=tcb[i].ss;_SP=tcb[i].sp;tcb[i].state=running;current=i;enable();}
/****************线程的阻塞和唤醒的实现****************//**(阻塞)block函数的实现**/
void block(struct TCB **qp){struct TCB *tp;			disable();tp=*qp;tcb[current].state=blocked;	/*首先要将当前线程的状态置为阻塞状态*//*需要将线程插入到指定的阻塞队列未尾,并重新进行CPU调度*/(*qp)->next=NULL;					if(tp==NULL)tp=&tcb[current];		/*由于tp是一个指针,所以操作的是指针*/else{while(tp->next!=NULL)tp=tp->next;tp->next=&tcb[current];	 /*将阻塞线程插入到队尾*/}enable();swtch();					/*并重新进行CPU调度*/
}/**(唤醒)wakeup_first函数的实现**/
void wakeup_first(struct TCB **qp){int i;struct TCB *tp;disable();tp=*qp;
/*寻找阻塞线程,因为线程状态的改变需要在TCB中修改,所以需要知道阻塞队列里面需要唤醒的线程对应TCB数组里面的哪一个*/for(i=1;i<NTCB;i++){if(strcmp(tcb[i].name,(*tp->next).name)==0){		/*如果两个外部标示符一样 说明找到需要唤醒的线程*/break;}tcb[i].state=ready;					/*将其状态改为就绪状态*/enable();}
}/***************线程的同步和互斥的实现****************/
/*用记录型信号量机制实现同步与互斥*/
/**对信号量的P操作**/
void p(semaphore *sem){struct TCB **qp;			/*设置一个指向TCB链表的二级指针*/disable();					/*关中断*/sem->value=sem->value-1;	/*记录型信号量的value值减1*/if(sem->value<0){		/*如果记录型信号量的值小于0*/qp=&(sem->wq);	/*那么将qp指针指向sem信号量的阻塞队列*/block(qp);			/*阻塞相应进程到阻塞队列*/}enable();
}
/**对信号量的V操作**/
void v(semaphore *sem){struct TCB **qp;disable();qp=&(sem->wq);sem->value=sem->value+1;if(sem->value<=0){wakeup_first(qp);}enable();
}/***************消息缓冲队列的线程间的通信*************/
/**初始化消息队列**/
void InitBuf(void){int i;struct buffer *freebuf,*temp;freebuf=(struct buffer*)malloc(sizeof(struct buffer));		/*申请空间*/temp=freebuf;for(i=1;i<=NBUF;i++){temp=(struct buffer*)malloc(sizeof(struct buffer));temp=temp->next;}}
/**从空闲消息缓冲队列头上取下一缓冲区,返回指向该缓冲区的指针**/
struct buffer *getbuf(void){struct buffer *buff;buff=freebuf;						/*空闲消息缓冲头*/freebuf=freebuf->next;return buff;
}/**将buff所指的缓冲区插到*mq所指的缓冲队列尾**/
void insert (struct buffer **mq,struct buffer *buff){struct buffer *temp;if(buff==NULL)return;buff->next=NULL;if(*mq==NULL)*mq=buff;else{temp=*mq;while(temp->next!=NULL)temp=temp->next;temp->next=buff;}
}
/***将地址a开始的size个字节发送给外部标示符为receiver的线程***/
void send(char *receiver,char *a,int size){struct buffer *buff;int i,id=-1;disable();		/*原语要关中断*/
/*首先需要进行搜索接受进程*/for(i=0;i<NTCB;i++){if(strcmp(receiver,tcb[i].name)==0){id=i;break;}}/*如果没有收到,那么就显示错误,没有接收进程*/if(id==-1){printf("Error:Receiver not exist!\n");enable();return ;}printf("\n%s send %s a message:",tcb[current].name,receiver);printf("%s\n",a);p(&sfb);			/*sfb为空闲缓冲区队列的计数信号量,为全局变量*/p(&mutexfb);		/*mutexfb为互斥信号量*/buff=getbuf();	/*取一缓冲区*/v(&mutexfb);		/*释放互斥信号量,表示用完缓冲区*/buff->sender=current;	/*将发送方的内容加入缓冲区*/buff->size=size;buff->next=NULL;for(i=0;i<buff->size;i++,a++)	/*取得消息正文*/buff->text[i]=*a;p(&tcb[id].mutex);				/*互斥使用接收者线程的消息队列*/insert(&(tcb[id].mq),buff);	/*将消息缓冲区插入消息队列*/v(&tcb[id].mutex);				/*撤销线程id消息队列互斥信号,接收者线程多了个消息*/v(&tcb[id].sm);					/*消息队列计数信号量加1*/enable();
}
/*****释放缓冲区函数*****/
struct buffer *payback(struct buffer *bq){struct buffer *temp;temp=freebuf;while(temp->next!=NULL)temp=temp->next;temp->next=bq;return freebuf;}
/****接收者函数receiver****/
void receive(char *sender){int i,j,id=-1;struct buffer *buff;disable();for(i=1;i<NTCB;i++){						/*搜索发送进程的ID*/if(strcmp(sender,tcb[i].name)==0){id=i;break;		}	}if(id==-1){									/*如果发送线程已经终止*/printf("Error:Sender not exist!\n");tcb[current].state=blocked;tcb[current].ss=_SS;tcb[current].sp=_SP;for(j=1;j<NTCB;j++)						/*寻找新的线程*/{								if(tcb[i].state==ready){			/*恢复新线程的现场*/_SS=tcb[i].ss;_SP=tcb[i].sp;tcb[i].state=running;			}}enable();return;}p(&tcb[current].sm);		/*当前进程消息队-1,取第一个缓冲区*/p(&tcb[current].mutex);		/*当前线程消息队列的互斥信号量*/buff=tcb[current].mq;	/*消息队列的首指针*/tcb[current].mq=tcb[current].mq->next;		/*下移一位*/v(&tcb[current].mutex);			/*释放当前线程消息队列的互斥信号*//*将消息缓冲区中信息复制到接受区*/printf("\n%s receiver a message:",tcb[current].name);for(i=0;i<buff->size;i++)					printf("%c",buff->text[i]);			/*将消息正文复制出去*/printf("\n");p(&mutexfb);							/*缓冲区信号量*//*释放缓冲区*/buff->sender=-1;						buff->size=0;strcpy(buff->text,'\0');buff->next=NULL;/*将当前进程的消息缓冲区内的第一个消息删除,并且将缓冲量收回缓冲区*/payback(buff);		/*释放缓冲区信号量*/v(&mutexfb);		
/*空闲缓冲区数+1*/						
v(&sfb);								printf("f1 and f2 is finished!");enable();
}	
/************over函数的实现************/
/*撤销线程,收回被撤销线程的资源*/
void over(void){disable();/*通过disable()与enable()来实现在执行该代码段时防止中断*//*撤销当前线程所申请的TCB内存空空间,因为一个线程在它执行完毕之后就需要撤销自己,所以是要用到current(当前)线程*/free(tcb[current].stack);		/*堆栈指针的释放*/strcpy(tcb[current].name,'\0');	/*将线程的外部标示符置空*/tcb[current].state=finished;	/*将状态置为终止态*/swtch();				/*在线程撤销后,需要重新进行CPU调度*/enable();
}
/************tcb_state函数的实现**************/
/*输出所有线程的状态*/
void tcb_state(void){int i;for(i=1;i<NTCB;i++){switch(tcb[i].state){case 0:printf("The state of tcb[%d](%s) is finished\n",i,tcb[i].name);break;case 1:printf("The state of tcb[%d](%s) is running\n",i,tcb[i].name);break;case 2:printf("The state of tcb[%d](%s) is ready\n",i,tcb[i].name);break;case 3:printf("The state of tcb[%d](%s) is blocked\n",i,tcb[i].name);break;}}
}
/**************f1函数的实现****************/
void f1 (void){int i,j,k;char c[NTEXT]="you received it,f2?";for(i=0;i<10;i++){putchar('a');for(j=0;j<100000;j++){for(k=0;k<5000;k++);}}send("f2",c,20);printf("f2 is waken up!\n");
}
/***************f2函数的实现**************/
void f2(void){long i,j,k;for(i=0;i<10;i++){putchar('b');for(j=0;j<100000;j++)for(k=0;k<5000;k++);}receive("f1");
}
/*************all_finished函数的实现*****************/
/*判断是否全部线程都已经完成*/
int all_finished(void){int i;for(i=1;i<NTCB;i++){if(tcb[i].state!=finished)return 0;}return 1;
}
/****************************主函数*******************************/
void main()
{InitInDos();InitTcb();old_int8=getvect(8);			/*在使用新的时钟中断服务程序前,必须先用getvect()函数获取系统原来的INT 08H的入口地址并将它保存起来*//*创建0#线程*/strcpy(tcb[0].name,"main");tcb[0].state=running;current=0;/*创建f1,f2线程后,在CPU中,CS,IP指向current=0的函数,而ss,sp指向main函数*/create("f1",(codeptr)f1,1024);create("f2",(codeptr)f2,1024);tcb_state();/*启动多个线程的并发执行*/setvect(8,new_int8);swtch();while(!all_finished()){}tcb[0].name[0]='\0';tcb[0].state=finished;setvect(8,old_int8);			/*回复8号中断*/tcb_state();printf("\n Multi_task system terminated. \n");
}


这篇关于基于DOS的多任务系统的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118206

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo