源码获取

https://github.com/icoty/nachos-3.4-Lab

内容一:总体概述

本实习希望通过修改Nachos系统平台的底层源代码,达到“扩展调度算法”的目标。本次实验主要是要理解Timer、Scheduler和Interrupt之间的关系,从而理解线程之间是如何进行调度的。

内容二:任务完成情况

任务完成列表(Y/N)

Exercise1 Exercise2 Exercise3 Challenge1
第一部分 Y Y Y Y

具体Exercise的完成情况

Exercise1 调研

调研Linux或Windows中采用的进程/线程调度算法。具体内容见课堂要求。

  1. linux-4.19.23进程调度策略SCHED_OTHER分时调度策略,SCHED_FIFO实时调度策略(先到先服务),SCHED_RR实时调度策略(时间片轮转)。

    • RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。
    • 当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。
    • RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的位置决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。
  2. 内核代码:内核为每个cpu维护一个进程就绪队列,cpu只调度由其维护的队列上的进程:

vi linux-4.19.23/kernel/sched/core.c

……
#define CREATE_TRACE_POINTS
#include <trace/events/sched.h> DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);
……

vi linux-4.19.23/kernel/sched/sched.h

/*
* This is the main, per-CPU runqueue data structure.
*
* Locking rule: those places that want to lock multiple runqueues
* (such as the load balancing or the thread migration code), lock
* acquire operations must be ordered by ascending &runqueue.
*/
struct rq {
/* runqueue lock: */
raw_spinlock_t lock; // 锁保证互斥访问runqueue
……
struct cfs_rq cfs; // 所有普通进程的集合,采用cfs调度策略
struct rt_rq rt; // 所有实时进程的集合,采用实时调度策略
struct dl_rq dl; // struct dl_rq空闲进程集合
……
}; // cfs_rq就绪队列是一棵红黑树。
/* CFS-related fields in a runqueue */
struct cfs_rq {
……
struct rb_root_cached tasks_timeline; // 红黑树的树根 /*
* 'curr' points to currently running entity on this cfs_rq.
* It is set to NULL otherwise (i.e when none are currently running).
*/
struct sched_entity *curr; // 指向当前正运行的进程
struct sched_entity *next; // 指向将被唤醒的进程
struct sched_entity *last; // 指向唤醒next进程的进程
struct sched_entity *skip;
……
};

vi linux-4.19.23/include/linux/sched.h:实时进程调度实体struct sched_rt_entity,双向链表组织形式;空闲进程调度实体struct sched_dl_entity,红黑树组织形式;普通进程的调度实体sched_entity,每个进程描述符中均包含一个该结构体变量,该结构体有两个作用:

  1. 包含有进程调度的信息(比如进程的运行时间,睡眠时间等等,调度程序参考这些信息决定是否调度进程);
  2. 使用该结构体来组织进程,struct rb_node类型结构体变量run_node是红黑树节点,struct sched_entity调度实体将被组织成红黑树的形式,同时意味着普通进程也被组织成红黑树的形式。parent指向了当前实体的上一级实体,cfs_rq指向了该调度实体所在的就绪队列。my_q指向了本实体拥有的就绪队列(调度组),该调度组(包括组员实体)属于下一个级别,和本实体不在同一个级别,该调度组中所有成员实体的parent域指向了本实体,depth代表了此队列(调度组)的深度,每个调度组都比其parent调度组深度大1。内核依赖my_q域实现组调度。
……
// 普通进程的调度实体sched_entity,使用红黑树组织
struct sched_entity {
/* For load-balancing: */
struct load_weight load;
unsigned long runnable_weight;
struct rb_node run_node; // 红黑树节点
struct list_head group_node;
unsigned int on_rq;
……
#ifdef CONFIG_FAIR_GROUP_SCHED
int depth;
struct sched_entity *parent; // 当前节点的父节点
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq; // 当前节点所在的就绪队列
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
#endif
……
}; // 实时进程调度实体,采用双向链表组织
struct sched_rt_entity {
struct list_head run_list; // 链表组织
unsigned long timeout;
unsigned long watchdog_stamp;
unsigned int time_slice;
unsigned short on_rq;
unsigned short on_list; struct sched_rt_entity *back;
#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity *parent;
/* rq on which this entity is (to be) queued: */
struct rt_rq *rt_rq; // 当前节点所在的就绪队列
/* rq "owned" by this entity/group: */
struct rt_rq *my_q;
#endif
} __randomize_layout; // 空闲进程调度实体,采用红黑树组织
struct sched_dl_entity {
struct rb_node rb_node;
……
};
……

vi linux-4.19.23/kernel/sched/sched.h:内核声明了一个调度类sched_class的结构体类型,用来实现不同的调度策略,可以看到该结构体成员都是函数指针,这些指针指向的函数就是调度策略的具体实现,所有和进程调度有关的函数都直接或者间接调用了这些成员函数,来实现进程调度。此外,每个进程描述符中都包含一个指向该结构体类型的指针sched_class,指向了所采用的调度类。

……
struct sched_class {
const struct sched_class *next; void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
void (*yield_task) (struct rq *rq);
bool (*yield_to_task)(struct rq *rq, struct task_struct *p, bool preempt); void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);
……
};
……

Exercise2 源代码阅读

code/threads/scheduler.h和code/threads/scheduler.cc:scheduler类是nachos中的进程调度器,维护了一个挂起的中断队列,通过FIFO进行调度。

  • void ReadyToRun(Thread* thread);设置线程状态为READY,并放入就绪队列readyList。
  • Thread* FindNextToRun(int source); 从就绪队列中取出下一个上CPU的线程,实现基于优先级的抢占式调度和FIFO调度。
  • void Run(Thread* nextThread); 把下CPU的线程的寄存器和堆栈信息从CPU保存到线程本身的寄存器数据结构中, 执行线程切换,把上CPU的线程的寄存器和堆栈信息从线程本身的寄存器中拷贝到CPU的寄存器中,运行新线程。

code/threads/switch.s:switch.s模拟内容是汇编代码,负责CPU上进程的切换。切换过程中,首先保存当前进程的状态,然后恢复新运行进程的状态,之后切换到新进程的栈空间,开始运行新进程。

code/machine/timer.h和code/machine/timer.cc:Timer类用以模拟硬件的时间中断。在TimerExired中,会调用TimeOfNextInterrupt,计算出下次时间中断的时间,并将中断插入中断队列中。初始化时会调用TimerExired,然后每次中断处理函数中都会调用一次TimerExired,从而时间系统时间一步步向前走。需要说明的是,在运行nachos时加入-rs选项,会初始化一个随机中断的Timer。当然你也可以自己声明一个非随机的Timer,每隔固定的时间片执行中断。时间片大小的定义位于ststs.h中,每次开关中断会调用OneTick(),当Ticks数目达到时间片大小时,会出发一次时钟中断。

Exercise3 线程调度算法扩展

扩展线程调度算法,实现基于优先级的抢占式调度算法。

思路:更改Thread类,加入priority成员变量,同时更改初始化函数对其初始化,并完成对应的set和get函数。scheduler中的FindNextToRun负责找到下一个运行的进程,默认是FIFO,找到队列最开始时的线程返回。我们现在要实现的是根据优先级来返回,仅需将插入readyList队列的方法按照优先级从高到低顺序插入SortedInsert,那么插入时会维护队列中的Thread按照优先级排序,每次依旧从头取出第一个,即为优先级最高的队列。抢占式调度则需要在每次中断发生时尝试进行进程切换,如果有优先级更高的进程,则运行高优先级进程。

// 基于优先级的可抢占式调度策略和FIFO调度策略
Thread * Scheduler::FindNextToRun (bool bySleep)
{
// called by threadsleep,直接调度,不用判断时间片
if(bySleep){
lastSwitchTick = stats->systemTicks;
return (Thread *)readyList->SortedRemove(NULL); // 与Remove()等价,都是从队头取
}else{
int ticks = stats->systemTicks - lastSwitchTick; // 这里设置了运行的最短时间TimerSlice,防止频繁切换消耗CPU资源
// 测试优先级抢占调度时需要屏蔽这句,因为调用Yield()的线程运行时间很短
// 会直接返回NULL
/*if(ticks < TimerSlice){
// 不用切换
return NULL;
}else*/{
if(readyList->IsEmpty()){
return NULL;
}
Thread * next = (Thread *)readyList->SortedRemove(NULL);
// 基于优先级可抢占调度策略,自己添加的宏,Makefile编译添加: -DSCHED_PRIORITY
#ifdef SCHED_PRIORITY
// nextThread优先级高于当前线程则切换,否则不切换
if(next->getPriority() < currentThread->getPriority()){
lastSwitchTick = stats->systemTicks;
return next;
}else{
readyList->SortedInsert(next, next->getPriority());
return NULL;
}
#else // FIFO策略需要取消Makefile编译选项:-DSCHED_PRIORITY
lastSwitchTick = stats->systemTicks;
return next;
#endif
}
}
} // 线程主动让出cpu,在FIFO调度策略下能够看到多个线程按顺序运行
void SimpleThread(int which)
{
for (int num = 0; num < 5; num++) {
int ticks = stats->systemTicks - scheduler->getLastSwitchTick();
printf("userId=%d,threadId=%d,prio=%d,loop:%d,lastSwitchTick=%d,systemTicks=%d,usedTicks=%d,TimerSlice=%d\n",currentThread->getUserId(),currentThread->getThreadId(),currentThread->getPriority(),num,scheduler->getLastSwitchTick(),stats->systemTicks,ticks,TimerSlice);
// 时间片轮转算法,判断时间片是否用完,
// 如果用完主动让出cpu,针对nachos内核线程算法
/*if(ticks >= TimerSlice){
//printf("threadId=%d Yield\n",currentThread->getThreadId());
currentThread->Yield();
}*/ // 非抢占模式下,多个线程同时执行该接口的话,会交替执行,交替让出cpu
// 基于优先级抢占模式下,优先级高的线程运行结束后才调度低优先级线程
currentThread->Yield();
}
} threadtest.cc:
// 创建四个线程,加上主线程共五个,优先值越小优先级越高
void ThreadPriorityTest()
{
Thread* t1 = new Thread("forkThread1", 1);
printf("-->name=%s,threadId=%d\n",t1->getName(),t1->getThreadId());
t1->Fork(SimpleThread, (void*)1); Thread* t2 = new Thread("forkThread2", 2);
printf("-->name=%s,threadId=%d\n",t2->getName(),t2->getThreadId());
t2->Fork(SimpleThread, (void*)2); Thread* t3 = new Thread("forkThread3", 3);
printf("-->name=%s,threadId=%d\n",t3->getName(),t3->getThreadId());
t3->Fork(SimpleThread, (void*)3); Thread* t4 = new Thread("forkThread4", 4);
printf("-->name=%s,threadId=%d\n",t4->getName(),t4->getThreadId());
t4->Fork(SimpleThread, (void*)4); currentThread->Yield();
SimpleThread(0);
} // 运行结果,优先级1最高,最先执行完,其次是优先为2的线程,直到所有线程结束
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads# ./nachos -q 3
-->name=forkThread1,threadId=1
-->name=forkThread2,threadId=2
-->name=forkThread3,threadId=3
-->name=forkThread4,threadId=4
userId=0,threadId=1,prio=1,loop:0,lastSwitchTick=50,systemTicks=60,usedTicks=10,TimerSlice=30
userId=0,threadId=1,prio=1,loop:1,lastSwitchTick=50,systemTicks=70,usedTicks=20,TimerSlice=30
userId=0,threadId=1,prio=1,loop:2,lastSwitchTick=50,systemTicks=80,usedTicks=30,TimerSlice=30
userId=0,threadId=1,prio=1,loop:3,lastSwitchTick=50,systemTicks=90,usedTicks=40,TimerSlice=30
userId=0,threadId=1,prio=1,loop:4,lastSwitchTick=50,systemTicks=100,usedTicks=50,TimerSlice=30
userId=0,threadId=2,prio=2,loop:0,lastSwitchTick=110,systemTicks=120,usedTicks=10,TimerSlice=30
userId=0,threadId=2,prio=2,loop:1,lastSwitchTick=110,systemTicks=130,usedTicks=20,TimerSlice=30
userId=0,threadId=2,prio=2,loop:2,lastSwitchTick=110,systemTicks=140,usedTicks=30,TimerSlice=30
userId=0,threadId=2,prio=2,loop:3,lastSwitchTick=110,systemTicks=150,usedTicks=40,TimerSlice=30
userId=0,threadId=2,prio=2,loop:4,lastSwitchTick=110,systemTicks=160,usedTicks=50,TimerSlice=30
userId=0,threadId=3,prio=3,loop:0,lastSwitchTick=170,systemTicks=180,usedTicks=10,TimerSlice=30
userId=0,threadId=3,prio=3,loop:1,lastSwitchTick=170,systemTicks=190,usedTicks=20,TimerSlice=30
userId=0,threadId=3,prio=3,loop:2,lastSwitchTick=170,systemTicks=200,usedTicks=30,TimerSlice=30
userId=0,threadId=3,prio=3,loop:3,lastSwitchTick=170,systemTicks=210,usedTicks=40,TimerSlice=30
userId=0,threadId=3,prio=3,loop:4,lastSwitchTick=170,systemTicks=220,usedTicks=50,TimerSlice=30
userId=0,threadId=4,prio=4,loop:0,lastSwitchTick=230,systemTicks=240,usedTicks=10,TimerSlice=30
userId=0,threadId=4,prio=4,loop:1,lastSwitchTick=230,systemTicks=250,usedTicks=20,TimerSlice=30
userId=0,threadId=4,prio=4,loop:2,lastSwitchTick=230,systemTicks=260,usedTicks=30,TimerSlice=30
userId=0,threadId=4,prio=4,loop:3,lastSwitchTick=230,systemTicks=270,usedTicks=40,TimerSlice=30
userId=0,threadId=4,prio=4,loop:4,lastSwitchTick=230,systemTicks=280,usedTicks=50,TimerSlice=30
userId=0,threadId=0,prio=6,loop:0,lastSwitchTick=290,systemTicks=300,usedTicks=10,TimerSlice=30
userId=0,threadId=0,prio=6,loop:1,lastSwitchTick=290,systemTicks=310,usedTicks=20,TimerSlice=30
userId=0,threadId=0,prio=6,loop:2,lastSwitchTick=290,systemTicks=320,usedTicks=30,TimerSlice=30
userId=0,threadId=0,prio=6,loop:3,lastSwitchTick=290,systemTicks=330,usedTicks=40,TimerSlice=30
userId=0,threadId=0,prio=6,loop:4,lastSwitchTick=290,systemTicks=340,usedTicks=50,TimerSlice=30
No threads ready or runnable, and no pending interrupts.
Assuming the program completed.
Machine halting! Ticks: total 350, idle 0, system 350, user 0
Disk I/O: reads 0, writes 0
Console I/O: reads 0, writes 0
Paging: faults 0
Network I/O: packets received 0, sent 0 Cleaning up...
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#

Challenge 线程调度算法扩展(至少实现一种算法)

可实现“时间片轮转算法”、“多级队列反馈调度算法”,或将Linux或Windows采用的调度算法应用到Nachos上。

思路:nachos启动时在system.cc中会new一个timer类,每隔一个TimerTicks大小触发时钟中断,从而让时钟向前走,时间片的大下定义在stats.h中。同时在stats.h中定义一个时间片大小变量TimerSlice,每个线程运行时间只要大于等于TimerSlice,立即放弃CPU。

stats.h:
……
// nachos执行每条用户指令的时间为1Tick
#define UserTick 1
// 系统态无法进行指令计算,
// 所以nachos系统态的一次中断调用或其他需要进行时间计算的单位设置为10Tick
#define SystemTick 10 // 磁头寻找超过一个扇区的时间
#define RotationTime 500 // 磁头寻找超过一个磁道的时间
#define SeekTime 500
#define ConsoleTime 100 // time to read or write one character
#define NetworkTime 100 // time to send or receive one packet // 时钟中断间隔
#define TimerTicks 5 // (average) time between timer interrupts // 时间片轮转算法一个时间片大小
#define TimerSlice 10
…… threadtest.cc:
void SimpleThread(int which)
{
for (int num = 0; num < 5; num++) {
int ticks = stats->systemTicks - scheduler->getLastSwitchTick();
printf("userId=%d,threadId=%d,prio=%d,loop:%d,lastSwitchTick=%d,systemTicks=%d,usedTicks=%d,TimerSlice=%d\n",currentThread->getUserId(),currentThread->getThreadId(),currentThread->getPriority(),num,scheduler->getLastSwitchTick(),stats->systemTicks,ticks,TimerSlice);
// 时间片轮转算法,判断时间片是否用完
// 如果用完主动让出cpu,针对nachos内核线程算法
if(ticks >= TimerSlice){
printf("threadId=%d Yield\n",currentThread->getThreadId());
currentThread->Yield();
} // 非抢占模式下,多个线程同时执行该接口的话,会交替执行,交替让出cpu
// currentThread->Yield();
}
} threadtest.cc:
// 创建四个线程,加上主线程共五个,时间片轮转调度策略,不可抢占
void ThreadPriorityTest()
{
Thread* t1 = new Thread("forkThread1", 1);
printf("-->name=%s,threadId=%d\n",t1->getName(),t1->getThreadId());
t1->Fork(SimpleThread, (void*)1); Thread* t2 = new Thread("forkThread2", 2);
printf("-->name=%s,threadId=%d\n",t2->getName(),t2->getThreadId());
t2->Fork(SimpleThread, (void*)2); Thread* t3 = new Thread("forkThread3", 3);
printf("-->name=%s,threadId=%d\n",t3->getName(),t3->getThreadId());
t3->Fork(SimpleThread, (void*)3); Thread* t4 = new Thread("forkThread4", 4);
printf("-->name=%s,threadId=%d\n",t4->getName(),t4->getThreadId());
t4->Fork(SimpleThread, (void*)4); currentThread->Yield();
SimpleThread(0);
} // 运行结果,可看到usedTicks >= TimserSlice时都让出cpu
// 并且线程执行顺序为1 2 3 4 0,直到结束
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads# ./nachos -q 3
-->name=forkThread1,threadId=1
-->name=forkThread2,threadId=2
-->name=forkThread3,threadId=3
-->name=forkThread4,threadId=4
userId=0,threadId=1,prio=1,loop:0,lastSwitchTick=50,systemTicks=60,usedTicks=10,TimerSlice=10
threadId=1 Yield
userId=0,threadId=2,prio=2,loop:0,lastSwitchTick=60,systemTicks=70,usedTicks=10,TimerSlice=10
threadId=2 Yield
userId=0,threadId=3,prio=3,loop:0,lastSwitchTick=70,systemTicks=80,usedTicks=10,TimerSlice=10
threadId=3 Yield
userId=0,threadId=4,prio=4,loop:0,lastSwitchTick=80,systemTicks=90,usedTicks=10,TimerSlice=10
threadId=4 Yield
userId=0,threadId=0,prio=6,loop:0,lastSwitchTick=90,systemTicks=100,usedTicks=10,TimerSlice=10
threadId=0 Yield
userId=0,threadId=1,prio=1,loop:1,lastSwitchTick=100,systemTicks=110,usedTicks=10,TimerSlice=10
threadId=1 Yield
userId=0,threadId=2,prio=2,loop:1,lastSwitchTick=110,systemTicks=120,usedTicks=10,TimerSlice=10
threadId=2 Yield
userId=0,threadId=3,prio=3,loop:1,lastSwitchTick=120,systemTicks=130,usedTicks=10,TimerSlice=10
threadId=3 Yield
userId=0,threadId=4,prio=4,loop:1,lastSwitchTick=130,systemTicks=140,usedTicks=10,TimerSlice=10
threadId=4 Yield
userId=0,threadId=0,prio=6,loop:1,lastSwitchTick=140,systemTicks=150,usedTicks=10,TimerSlice=10
threadId=0 Yield
userId=0,threadId=1,prio=1,loop:2,lastSwitchTick=150,systemTicks=160,usedTicks=10,TimerSlice=10
threadId=1 Yield
userId=0,threadId=2,prio=2,loop:2,lastSwitchTick=160,systemTicks=170,usedTicks=10,TimerSlice=10
threadId=2 Yield
userId=0,threadId=3,prio=3,loop:2,lastSwitchTick=170,systemTicks=180,usedTicks=10,TimerSlice=10
threadId=3 Yield
userId=0,threadId=4,prio=4,loop:2,lastSwitchTick=180,systemTicks=190,usedTicks=10,TimerSlice=10
threadId=4 Yield
userId=0,threadId=0,prio=6,loop:2,lastSwitchTick=190,systemTicks=200,usedTicks=10,TimerSlice=10
threadId=0 Yield
userId=0,threadId=1,prio=1,loop:3,lastSwitchTick=200,systemTicks=210,usedTicks=10,TimerSlice=10
threadId=1 Yield
userId=0,threadId=2,prio=2,loop:3,lastSwitchTick=210,systemTicks=220,usedTicks=10,TimerSlice=10
threadId=2 Yield
userId=0,threadId=3,prio=3,loop:3,lastSwitchTick=220,systemTicks=230,usedTicks=10,TimerSlice=10
threadId=3 Yield
userId=0,threadId=4,prio=4,loop:3,lastSwitchTick=230,systemTicks=240,usedTicks=10,TimerSlice=10
threadId=4 Yield
userId=0,threadId=0,prio=6,loop:3,lastSwitchTick=240,systemTicks=250,usedTicks=10,TimerSlice=10
threadId=0 Yield
userId=0,threadId=1,prio=1,loop:4,lastSwitchTick=250,systemTicks=260,usedTicks=10,TimerSlice=10
threadId=1 Yield
userId=0,threadId=2,prio=2,loop:4,lastSwitchTick=260,systemTicks=270,usedTicks=10,TimerSlice=10
threadId=2 Yield
userId=0,threadId=3,prio=3,loop:4,lastSwitchTick=270,systemTicks=280,usedTicks=10,TimerSlice=10
threadId=3 Yield
userId=0,threadId=4,prio=4,loop:4,lastSwitchTick=280,systemTicks=290,usedTicks=10,TimerSlice=10
threadId=4 Yield
userId=0,threadId=0,prio=6,loop:4,lastSwitchTick=290,systemTicks=300,usedTicks=10,TimerSlice=10
threadId=0 Yield
No threads ready or runnable, and no pending interrupts.
Assuming the program completed.
Machine halting! Ticks: total 350, idle 0, system 350, user 0
Disk I/O: reads 0, writes 0
Console I/O: reads 0, writes 0
Paging: faults 0
Network I/O: packets received 0, sent 0 Cleaning up...
root@yangyu-ubuntu-32:/mnt/nachos-3.4-Lab/nachos-3.4/threads#

内容三:遇到的困难以及解决方法

困难1

切换线程过程中,产生段错误,通过定位,误把销毁的线程挂入就绪对了所致。

内容四:收获及感想

自己动手实现后,发现时间片轮转算法,线程调度,FIFO,时钟中断等其实并不陌生。一切只要你不懒和肯付出实际行动的难题都是纸老虎。

内容五:对课程的意见和建议

暂无。

内容六:参考文献

暂无。

Nachos-Lab2-线程调度模块实现的更多相关文章

  1. Nachos-Lab1-完善线程机制

    Nachos是什么 Nachos (Not Another Completely Heuristic Operating System),是一个教学用操作系统,提供了操作系统框架: 线程 中断 虚拟内 ...

  2. [转]设计一款Android App总结

    开发工具的选择 开发工具我将选用Android Studio,它是Google官方指定的Android开发工具,目前是1.2.2稳定版,1.3的预览版也已经发布了.Android Studio的优点就 ...

  3. 【Android开发】如何设计开发一款Android App

    本文从开发工具选择,UI界面.图片模块.网络模块.数据库产品选择.性能.安全性等几个方面讲述了如果开发一个Android应用.现在整理出来分享给广大的Android程序员. 开发工具的选择 开发工具我 ...

  4. IOCP编程原理(转)

    在我的博客之前写了很多关于IOCP的“行云流水”似的看了让人发狂的文章,尤其是几篇关于 IOCP加线程池文章,更是让一些功力不够深厚的初学IOCP者,有种吐血的感觉.为了让大家能够立刻提升内力修为,并 ...

  5. linux 时间模块 一

    作者:云天明链接:https://www.zhihu.com/question/50764115/answer/122613742来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  6. IOCP编程之基本原理

    在我的博客之前写了很多关于IOCP的“行云流水”似的看了让人发狂的文章,尤其是几篇关于IOCP加线程池文章,更是让一些功力不够深厚的初学IOCP者,有种吐血的感觉.为了让大家能够立刻提升内力修为,并且 ...

  7. 假设让我又一次设计一款Android App

    转载请注明出处: 本文来自aspook的博客:http://blog.csdn.net/ahence/article/details/47154419 开发工具的选择 开发工具我将选用Android  ...

  8. 100行代码实现一个RISC-V架构下的多线程管理框架

    1. 摘要 本文将基于RISC-V架构和qemu仿真器实现一个简单的多线程调度和管理框架, 旨在通过简单的代码阐明如何实现线程的上下文保存和切换, 线程的调度并非本文的重点, 故线程调度模块只是简单地 ...

  9. CMU-15445 LAB2:实现一个支持并发操作的B+树

    概述 经过几天鏖战终于完成了lab2,本lab实现一个支持并发操作的B+树.简直B格满满. B+树 为什么需要B+树 B+树本质上是一个索引数据结构.比如我们要用某个给定的ID去检索某个student ...

随机推荐

  1. express框架安装及中间件原理

    本文主要介绍express中间件的原理,来应对面试. 1.安装express及初始化: npm install express-generator -g   =>   express expre ...

  2. 本周总结(19年暑假)—— Part3

    日期:2019.7.28 博客期:109 星期日 这几天要练车,嗯呢,但是对于分布式数据库的研究并没有停止!

  3. 导航栏协议方法UINavigationControllerDelegate

    关于UINavigationControllerDelegate: Delegate中一共有6个方法.其中两个跟控制器ViewController的跳转有关.有两个跟屏幕的旋转有关.有两个跟导航栏动画 ...

  4. python读取文件用b模式读取

    f = open('aaa','rb')    返回的是字节 字符串编码 python中所有的字符串编码为Unicode,如果从一个文件读取字符串,那么该字符串的编码就是该文件的编码. f.tell( ...

  5. Abstract Data Type

  6. 使用Thymeleaf时,ajax的url如何设置?

    使用Thymeleaf时,ajax的url如何设置? 最近在做一个论坛项目使用到了Thymeleaf,在使用ajax请求的时候发现无法获取BasePath.在经过一番查阅资料后终于得知如下俩种方法,在 ...

  7. 吴裕雄--天生自然HADOOP学习笔记:hadoop集群实现PageRank算法实验报告

    实验课程名称:大数据处理技术 实验项目名称:hadoop集群实现PageRank算法 实验类型:综合性 实验日期:2018年 6 月4日-6月14日 学生姓名 吴裕雄 学号 15210120331 班 ...

  8. mybatis动态参数查询

    参考:https://blog.csdn.net/zbw18297786698/article/details/53727594

  9. MAC 安装 pygraphviz 找不到头文件

    networkx的有向图只能通过箭头来区别两点之间的两条边,但是我在复现snake论文的时候,需要绘制两个交叉口之间的两条不同方向的路段,最后选择了pygraphviz 直接通过anaconda打开对 ...

  10. JetBrains IntelliJ IDEA(IJ)v2019.3.3/3.1/3.2/3.4/3.5 for mac/windows/linux 详细安装破解教程

    手欠升级了IntelliJ IDEA到2019.3.3,原来的破解不可用,IntelliJ IDEA 2019.3.3破解办法如下,为方便自己使用记录下.======================= ...