文档库 最新最全的文档下载
当前位置:文档库 › Pintos-斯坦福大学操作系统Project详解-线程部分

Pintos-斯坦福大学操作系统Project详解-线程部分

Pintos-斯坦福大学操作系统Project详解-线程部分
Pintos-斯坦福大学操作系统Project详解-线程部分

前言:

本实验来自斯坦福大学cs140课程,只限于教学用途,以下是他们对于Pintos 系统的介绍:

Pintos is a simple operating system framework for the 80x86 architecture. It supports kernel threads, loading and running user programs, and a file system, but it implements all of these in a very simple way. In the Pintos projects, you and your project team will strengthen its support in all three of these areas. You will also add a virtual memory implementation.

Pintos实验主要分成四部分,如下所示:

?实验一:Thread

?实验二:User Programs

?实验三:Virtual Memory

?实验四:File System

实验原理:

通过bochs 加载pintos 操作系统,该操作系统会根据pintos 的实现打印运行结果,通过比较标准输出文档和实际输出,来判断pintos 实现是否符合要求。

环境配置:

参考:

https://www.wendangku.net/doc/9717426876.html,/class/cs140/projects/pintos/pint os_12.html#SEC166

实验实现代码地址:

https://https://www.wendangku.net/doc/9717426876.html,/laiy/Pintos/tree/master/src

实验一THREAD:

我们试验一的最终任务就是在threads/中跑make check的时候,27个test 全pass。

Mission1:

重新实现timer_sleep函数(2.2.2)

(注意,博主以下用了包括代码在内大概7000字的说明从每一个底层细节解析了这个函数的执行,虽然很长但是让我们对pintos 这个操作系统的各种机制和实现有更深刻的理解,如果嫌长请直接跳到函数重新实现)

timer_sleep函数在devices/timer.c。系统现在是使用busy wait实现的,即线程不停地循环,直到时间片耗尽。更改timer_sleep的实现方式。

我们先来看一下devices目录下timer.c中的timer_sleep实现:

1/* Sleeps for approximately TICKS timer ticks. Interrupts must 2 be turned on. */

3void

4 timer_sleep (int64_t ticks)

5 {

6 int64_t start = timer_ticks ();

7 ASSERT (intr_get_level () == INTR_ON);

8while (timer_elapsed (start) < ticks)

9 thread_yield();

10 }

让我们一行一行解析:

第6行:调用了timer_ticks函数,让我们来看看这个函数做了什么。

1/* Returns the number of timer ticks since the OS booted. */

2 int64_t

3 timer_ticks (void)

4 {

5enum intr_level old_level = intr_disable ();

6 int64_t t = ticks;

7 intr_set_level (old_level);

8return t;

9 }

然后我们注意到这里有个intr_level的东西通过intr_disable返回了一个东西,没关系,我们继续往下找。

1/* Interrupts on or off? */

2enum intr_level

3 {

4 INTR_OFF, /* Interrupts disabled. */

5 INTR_ON /* Interrupts enabled. */

6 };

1/* Disables interrupts and returns the previous interrupt status. */ 2enum intr_level

3 intr_disable (void)

4 {

5enum intr_level old_level = intr_get_level ();

6

7/* Disable interrupts by clearing the interrupt flag.

8 See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable

9 Hardware Interrupts". */

10 asm volatile ("cli" : : : "memory");

11

12return old_level;

13 }

这里很明显,intr_level代表能否被中断,而intr_disable做了两件事情:1. 调用

intr_get_level() 2. 直接执行汇编代码,调用汇编指令来保证这个线程不能被中断。

注意:这个asm volatile是在C语言中内嵌了汇编语言,调用了CLI指令,CLI指令不是command line interface, 而是clear interrupt, 作用是将标志寄存器的IF (interrupt flag)位置为0, IF=0时将不响应可屏蔽中断。

好,让我们继续来看intr_get_level又做了什么鬼。

1/* Returns the current interrupt status. */

2enum intr_level

3 intr_get_level (void)

4 {

5 uint32_t flags;

6

7/* Push the flags register on the processor stack, then pop the

8 value off the stack into `flags'. See [IA32-v2b] "PUSHF"

9 and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware

10 Interrupts". */

11 asm volatile ("pushfl; popl %0" : "=g" (flags));

12

13return flags & FLAG_IF ? INTR_ON : INTR_OFF;

14 }

这里就是intr_disable函数调用的最深的地方了!

这个函数一样是调用了汇编指令,把标志寄存器的东西放到处理器棧上,然后把值pop到flags(代表标志寄存器IF位)上,通过判断flags来返回当前终端状态(intr_level)。

好,到这里。函数嵌套了这么多层,我们整理一下逻辑:

1. intr_get_level返回了intr_level的值

2. intr_disable获取了当前的中断状态,然后将当前中断状态改为不能被中断,然后返回执行之前的中断状态。

有以上结论我们可以知道:timer_ticks第五行做了这么一件事情:禁止当前行为被中断,保存禁止被中断前的中断状态(用old_level储存)。

让我们再来看timer_ticks剩下的做了什么,剩下的就是用t获取了一个全局变量ticks, 然后返回,其中调用了set_level函数。

1/* Enables or disables interrupts as specified by LEVEL and

2 returns the previous interrupt status. */

3enum intr_level

4 intr_set_level (enum intr_level level)

5 {

6return level == INTR_ON ? intr_enable () : intr_disable ();

7 }

有了之前的基础,这个函数就很容易看了,如果之前是允许中断的(INTR_ON)则enable 否则就disable.

而intr_enable正如你们所想,实现和之前基本一致:

1/* Enables interrupts and returns the previous interrupt status. */ 2enum intr_level

3 intr_enable (void)

4 {

5enum intr_level old_level = intr_get_level ();

6 ASSERT (!intr_context ());

7

8/* Enable interrupts by setting the interrupt flag.

9

10 See [IA32-v2b] "STI" and [IA32-v3a] 5.8.1 "Masking Maskable

11 Hardware Interrupts". */

12 asm volatile ("sti");

13

14return old_level;

15 }

说明一下,sti指令就是cli指令的反面,将IF位置为1。

然后有个ASSERT断言了intr_context函数返回结果的false。

再来看intr_context

1/* Returns true during processing of an external interrupt

2 and false at all other times. */

3bool

4 intr_context (void)

5 {

6return in_external_intr;

7 }

这里直接返回了是否外中断的标志in_external_intr,就是说ASSERT断言这个中断不是外中断(IO等,也称为硬中断)而是操作系统正常线程切换流程里的内中断(也称为软中断)。

好的,至此,我们总结一下:

这么多分析其实分析出了pintos操作系统如何利用中断机制来确保一个原子性的操作的。

我们来看,我们已经分析完了timer_ticks这个函数,它其实就是获取ticks的当前值返回而已,而第5行和第7行做的其实只是确保这个过程是不能被中断的而已。

那么我们来达成一个共识,被以下两个语句包裹的内容目的是为了保证这个过程不被中断。1enum intr_level old_level = intr_disable ();

2 ...

3 intr_set_level (old_level);

好的,那么ticks又是什么?来看ticks定义。

1/* Number of timer ticks since OS booted. */

2static int64_t ticks;

从pintos被启动开始,ticks就一直在计时,代表着操作系统执行单位时间的前进计量。

好,现在回过来看timer_sleep这个函数,start获取了起始时间,然后断言必须可以被中断,不然会一直死循环下去,然后就是一个循环

1while (timer_elapsed (start) < ticks)

2 thread_yield();

注意这个ticks是函数的形参不是全局变量,然后看一下这两个函数:

1/* Returns the number of timer ticks elapsed since THEN, which

2 should be a value once returned by timer_ticks(). */

3 int64_t

4 timer_elapsed (int64_t then)

5 {

6return timer_ticks () - then;

7 }

很明显timer_elapsed返回了当前时间距离then的时间间隔,那么这个循环实质就是在ticks的时间内不断执行thread_yield。

那么我们最后来看thread_yield是什么就可以了:

1/* Yields the CPU. The current thread is not put to sleep and

2 may be scheduled again immediately at the scheduler's whim. */ 3void

4 thread_yield (void)

5 {

6struct thread *cur = thread_current ();

7enum intr_level old_level;

8

9 ASSERT (!intr_context ());

10

11 old_level = intr_disable ();

12if (cur != idle_thread)

13 list_push_back (&ready_list, &cur->elem);

14 cur->status = THREAD_READY;

15 schedule ();

16 intr_set_level (old_level);

17 }

第6行thread_current函数做的事情已经可以顾名思义了,不过具有钻研精神和强迫症的你还是要确定它的具体实现:

1/* Returns the running thread.

2 This is running_thread() plus a couple of sanity checks.

3 See the big comment at the top of thread.h for details. */

4struct thread *

5 thread_current (void)

6 {

7struct thread *t = running_thread ();

8

9/* Make sure T is really a thread.

10 If either of these assertions fire, then your thread may

11 have overflowed its stack. Each thread has less than 4 kB

12 of stack, so a few big automatic arrays or moderate

13 recursion can cause stack overflow. */

14 ASSERT (is_thread (t));

15 ASSERT (t->status == THREAD_RUNNING);

16

17return t;

18 }

1/* Returns the running thread. */

2struct thread *

3 running_thread (void)

4 {

5 uint32_t *esp;

6

7/* Copy the CPU's stack pointer into `esp', and then round that

8 down to the start of a page. Because `struct thread' is

9 always at the beginning of a page and the stack pointer is

10 somewhere in the middle, this locates the curent thread. */

11 asm ("mov %%esp, %0" : "=g" (esp));

12return pg_round_down (esp);

13 }

1/* Returns true if T appears to point to a valid thread. */

2static bool

3 is_thread (struct thread *t)

4 {

5return t != NULL && t->magic == THREAD_MAGIC;

6 }

先来看thread_current调用的running_thread, 把CPU棧的指针复制到esp中,然

后调用pg_round_down

1/* Round down to nearest page boundary. */

2static inline void *pg_round_down (const void *va) {

3return (void *) ((uintptr_t) va & ~PGMASK);

4 }

好,这里又涉及到这个操作系统是怎么设计页面的了:

1/* Page offset (bits 0:12). */

2#define PGSHIFT 0 /* Index of first offset bit. */

3#define PGBITS 12 /* Number of offset bits. */

4#define PGSIZE (1 << PGBITS) /* Bytes in a page. */

5#define PGMASK BITMASK(PGSHIFT, PGBITS) /* Page offset bits (0:12). */

1/* Functions and macros for working with virtual addresses.

2

3 See pte.h for functions and macros specifically for x86

4 hardware page tables. */

5

6#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT))

一个页面12位,PGMASK调用BITMASK其实就是一个页面全部位都是1的这么个MASK,注意1ul的意思是unsigned long的1。

然后来看pg_round_down,对PGMASK取反的结果就是一个页面大小全部为0的这么个数,然后和传过来的指针做与操作的结果就是清0指针的靠右12位。

这里有什么效果呢?我们知道一个页面12位,而struct thread是在一个页面的最开始的,所以对任何一个页面的指针做pg_round_down的结果就是返回到这个页面最开始线程结构体的位置。

好,我们现在分析出了pg_round_down其实就是返回了这个页面线程的最开始指针,那么running_thread的结果返回当前线程起始指针。

再来看thread_current里最后的两个断言,一个断言t指针是一个线程,一个断言这个线程处于THREAD_RUNNING状态。

然后is_thread用的t->magic其实是用于检测时候有栈溢出的这么个元素。

1/* Owned by thread.c. */

2 unsigned magic; /* Detects stack overflow. */

好,现在thread_current分析完了,这个就是返回当前线程起始指针位置。

我们继续看thread_yield,然后剩下的很多东西其实我们已经分析过了,在分析的过程其实是对这个操作系统工作过程的剖析,很多地方都是相通的。

第9断言这是个软中断,第11和16包裹起来的就是我们之前分析的线程机制保证的一个原子性操作。

然后我们来看12-15做了什么:

1if (cur != idle_thread)

2 list_push_back (&ready_list, &cur->elem);

3 cur->status = THREAD_READY;

4 schedule ();

如何当前线程不是空闲的线程就调用list_push_back把当前线程的元素扔到就绪队列里面,并把线程改成THREAD_READY状态。

关于队列list的相关操作mission2会涉及到,这里先不作解释,顾名思义即可。

然后再调用schedule:

1/* Schedules a new process. At entry, interrupts must be off and

2 the running process's state must have been changed from

3 running to some other state. This function finds another

4 thread to run and switches to it.

5

6 It's not safe to call printf() until thread_schedule_tail()

7 has completed. */

8static void

9 schedule (void)

10 {

11struct thread *cur = running_thread ();

12struct thread *next = next_thread_to_run ();

13struct thread *prev = NULL;

14

15 ASSERT (intr_get_level () == INTR_OFF);

16 ASSERT (cur->status != THREAD_RUNNING);

17 ASSERT (is_thread (next));

18

19if (cur != next)

20 prev = switch_threads (cur, next);

21 thread_schedule_tail (prev);

22 }

首先获取当前线程cur和调用next_thread_to_run获取下一个要run的线程:

1/* Chooses and returns the next thread to be scheduled. Should

2 return a thread from the run queue, unless the run queue is

3 empty. (If the running thread can continue running, then it

4 will be in the run queue.) If the run queue is empty, return

5 idle_thread. */

6static struct thread *

7 next_thread_to_run (void)

8 {

9if (list_empty (&ready_list))

10return idle_thread;

11else

12return list_entry (list_pop_front (&ready_list), struct thread, elem);

13 }

如果就绪队列空闲直接返回一个空闲线程指针,否则拿就绪队列第一个线程出来返回。

然后3个断言之前讲过就不多说了,确保不能被中断,当前线程是RUNNING_THREAD 等。

如果当前线程和下一个要跑的线程不是同一个的话调用switch_threads返回给prev。

1/* Switches from CUR, which must be the running thread, to NEXT,

2 which must also be running switch_threads(), returning CUR in

3 NEXT's context. */

4struct thread *switch_threads (struct thread *cur, struct thread

*next);

注意,这个函数实现是用汇编语言实现的在threads/switch.S里:

1#### struct thread *switch_threads (struct thread *cur, struct thread *next);

2 ####

3 #### Switches from CUR, which must be the running thread, to NEXT,

4 #### which must also be running switch_threads(), returning CUR in

5 #### NEXT's context.

6####

7#### This function works by assuming that the thread we're switching 8 #### into is also running switch_threads(). Thus, all it has to do is

9 #### preserve a few registers on the stack, then switch stacks and 10#### restore the registers. As part of switching stacks we record the 11 #### current stack pointer in CUR's thread structure.

12

13.globl switch_threads

14.func switch_threads

15switch_threads:

16 # Save caller's register state.

17 #

18 # Note that the SVR4 ABI allows us to destroy %eax, %ecx, %edx,

19 # but requires us to preserve %ebx, %ebp, %esi, %edi. See

20 # [SysV-ABI-386] pages 3-11and3-12 for details.

21 #

22 # This stack frame must match the one set up by thread_create()

23 # in size.

24 pushl %ebx

25 pushl %ebp

26 pushl %esi

27 pushl %edi

28

29 # Get offsetof (struct thread, stack).

30 .globl thread_stack_ofs

31mov thread_stack_ofs, %edx

32

33 # Save current stack pointer to old thread's stack, if any.

34 movl SWITCH_CUR(%esp), %eax

35 movl %esp, (%eax,%edx,1)

36

37 # Restore stack pointer from new thread's stack.

38 movl SWITCH_NEXT(%esp), %ecx

39 movl (%ecx,%edx,1), %esp

40

41 # Restore caller's register state.

42 popl %edi

43 popl %esi

44 popl %ebp

45 popl %ebx

46 ret

47.endfunc

分析一下这个汇编代码:先4个寄存器压栈保存寄存器状态(保护作用),这4个寄存器是switch_threads_frame的成员:

1/* switch_thread()'s stack frame. */

2struct switch_threads_frame

3 {

4 uint32_t edi; /* 0: Saved %edi. */

5 uint32_t esi; /* 4: Saved %esi. */

6 uint32_t ebp; /* 8: Saved %ebp. */

7 uint32_t ebx; /* 12: Saved %ebx. */

8void (*eip) (void); /* 16: Return address. */

9struct thread *cur; /* 20: switch_threads()'s CUR argument. */

10struct thread *next; /* 24: switch_threads()'s NEXT argument. */

11 };

然后全局变量thread_stack_ofs记录线程和棧之间的间隙,我们都知道线程切换有个保存现场的过程,

来看34,35行,先把当前的线程指针放到eax中,并把线程指针保存在相对基地址偏移量为edx的地址中。

38,39:切换到下一个线程的线程棧指针,保存在ecx中,再把这个线程相对基地址偏移量edx地址(上一次保存现场的时候存放的)放到esp当中继续执行。

这里ecx, eax起容器的作用,edx指向当前现场保存的地址偏移量。

简单来说就是保存当前线程状态,恢复新线程之前保存的线程状态。

然后再把4个寄存器拿出来,这个是硬件设计要求的,必须保护switch_threads_frame 里面的寄存器才可以destroy掉eax, edx, ecx。

然后注意到现在eax(函数返回值是eax)就是被切换的线程棧指针。

我们由此得到一个结论,schedule先把当前线程丢到就绪队列,然后把线程切换如果下一个线程和当前线程不一样的话。

然后再看shedule最后一行的函数thread_schedule_tail做了什么鬼,这里参数prev 是NULL或者在下一个线程的上下文中的当前线程指针。

1/* Completes a thread switch by activating the new thread's page 2 tables, and, if the previous thread is dying, destroying it. 3

4 At this function's invocation, we just switched from thread

5 PREV, the new thread is already running, and interrupts are

6 still disabled. This function is normally invoked by

7 thread_schedule() as its final action before returning, but

8 the first time a thread is scheduled it is called by

9 switch_entry() (see switch.S).

10

11 It's not safe to call printf() until the thread switch is

12 complete. In practice that means that printf()s should be

13 added at the end of the function.

14

15 After this function and its caller returns, the thread switch

16 is complete. */

17void

18 thread_schedule_tail (struct thread *prev)

19 {

20struct thread *cur = running_thread ();

21

22 ASSERT (intr_get_level () == INTR_OFF);

23

24/* Mark us as running. */

25 cur->status = THREAD_RUNNING;

26

27/* Start new time slice. */

28 thread_ticks = 0;

29

30 #ifdef USERPROG

31/* Activate the new address space. */

32 process_activate ();

33#endif

34

35/* If the thread we switched from is dying, destroy its struct

36 thread. This must happen late so that thread_exit() doesn't

37 pull out the rug under itself. (We don't free

38 initial_thread because its memory was not obtained via

39 palloc().) */

40if (prev != NULL && prev->status == THREAD_DYING && prev != initial_thread)

41 {

42 ASSERT (prev != cur);

43 palloc_free_page (prev);

44 }

45 }

先是获得当前线程cur, 注意此时是已经切换过的线程了(或者还是之前run的线程,因为ready队列为空)。

然后把线程状态改成THREAD_RUNNING,然后thread_ticks清零开始新的线程切换时间片。

然后调用process_activate触发新的地址空间。

1/* Sets up the CPU for running user code in the current

2 thread.

3 This function is called on every context switch. */

4void

5 process_activate (void)

6 {

7struct thread *t = thread_current ();

8

9/* Activate thread's page tables. */

10 pagedir_activate (t->pagedir);

11

12/* Set thread's kernel stack for use in processing

13 interrupts. */

14 tss_update ();

15 }

这里先是拿到当前线程,调用pagedir_activate:

1/* Loads page directory PD into the CPU's page directory base

2 register. */

3void

4 pagedir_activate (uint32_t *pd)

5 {

6if (pd == NULL)

7 pd = init_page_dir;

8

9/* Store the physical address of the page directory into CR3

10 aka PDBR (page directory base register). This activates our

11 new page tables immediately. See [IA32-v2a] "MOV--Move

12 to/from Control Registers" and [IA32-v3a] 3.7.5 "Base

13 Address of the Page Directory". */

14 asm volatile ("movl %0, %%cr3" : : "r" (vtop (pd)) : "memory");

15 }

这个汇编指令将当前线程的页目录指针存储到CR3(页目录表物理内存基地址寄存器)中,也就是说这个函数更新了现在的页目录表。

最后来看tss_update:

1/* Sets the ring 0 stack pointer in the TSS to point to the end

2 of the thread stack. */

3void

4 tss_update (void)

5 {

6 ASSERT (tss != NULL);

7 tss->esp0 = (uint8_t *) thread_current () + PGSIZE;

8 }

首先要弄清楚tss是什么,tss是task state segment,叫任务状态段,任务(进程)切换时的任务现场信息。

这里其实是把TSS的一个棧指针指向了当前线程棧的尾部,也就是更新了任务现场的信息和状态。

好,到现在process_activate分析完了,总结一下:其实就是做了2件事情:1.更新页目录表2.更新任务现场信息(TSS)

我们现在继续来看thread_schedule_tail,最后是这4行:

1/* If the thread we switched from is dying, destroy its struct

2 thread. This must happen late so that thread_exit() doesn't

3 pull out the rug under itself. (We don't free

4 initial_thread because its memory was not obtained via

5 palloc().) */

6if (prev != NULL && prev->status == THREAD_DYING && prev != initial_thread)

7 {

8 ASSERT (prev != cur);

9 palloc_free_page (prev);

10 }

这里是如果我们切换的线程状态是THREAD_DYING(代表欲要销毁的线程)的话,调用palloc_free_page:

1/* Frees the page at PAGE. */

2void

3 palloc_free_page (void *page)

4 {

5 palloc_free_multiple (page, 1);

6 }

1/* Frees the PAGE_CNT pages starting at PAGES. */

2void

3 palloc_free_multiple (void *pages, size_t page_cnt)

4 {

5struct pool *pool;

6 size_t page_idx;

7

8 ASSERT (pg_ofs (pages) == 0);

9if (pages == NULL || page_cnt == 0)

10return;

11

12if (page_from_pool (&kernel_pool, pages))

13 pool = &kernel_pool;

14else if (page_from_pool (&user_pool, pages))

15 pool = &user_pool;

16else

17 NOT_REACHED ();

18

19 page_idx = pg_no (pages) - pg_no (pool->base);

20

21 #ifndef NDEBUG

22 memset (pages, 0xcc, PGSIZE * page_cnt);

23#endif

24

25 ASSERT (bitmap_all (pool->used_map, page_idx, page_cnt));

26 bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false);

27 }

这里创建了一个pool的结构体:

1/* A memory pool. */

2struct pool

3 {

4struct lock lock; /* Mutual exclusion. */

5struct bitmap *used_map; /* Bitmap of free pages. */

6 uint8_t *base; /* Base of pool. */

7 };

首先palloc实现的是一个页分配器,这里pool的角色就是记忆分配的内容。这里结构体用位图记录空的页,关键是这里又有一个操作系统很重要的知识概念出现了,就是lock:

1/* Lock. */

2struct lock

3 {

4struct thread *holder; /* Thread holding lock (for debugging). */

5struct semaphore semaphore; /* Binary semaphore controlling access. */

6 };

然后锁其实是由二值信号量实现的:

1/* A counting semaphore. */

2struct semaphore

3 {

4 unsigned value; /* Current value. */

5struct list waiters; /* List of waiting threads. */

6 };

具体信号量方法实现在threads/synch.c中,这里不作更多讲解了,毕竟函数分析还没

涉及到这里。

继续看palloc_free_multiple,第8行其实就是截取后12位,即获得当前页偏差量,断言为0就是说页指针应该指向线程结构体

1/* Offset within a page. */

2static inline unsigned pg_ofs (const void *va) {

3return (uintptr_t) va & PGMASK;

4 }

然后分析12-17行,这里要弄清楚一点是系统memory分成2个池,一个是kernel pool, 一个是user pool, user pool是提供给用户页的,别的都是kernel pool。

然后看下这里调用的page_from_pool函数:

1/* Returns true if PAGE was allocated from POOL,

2 false otherwise. */

3static bool

4 page_from_pool (const struct pool *pool, void *page)

5 {

6 size_t page_no = pg_no (page);

7 size_t start_page = pg_no (pool->base);

8 size_t end_page = start_page + bitmap_size (pool->used_map);

9

10return page_no >= start_page && page_no < end_page;

11 }

pg_no是获取虚拟页数的,方法其实就是直接指针右移12位就行了:

1/* Virtual page number. */

2static inline uintptr_t pg_no (const void *va) {

3return (uintptr_t) va >> PGBITS;

4 }

然后这里获取当前池中的的起始页和结束页位置,然后判断页面时候在这个池的Number 范围之类来判断时候属于某个池。

再看NOT_REACHED函数,这个函数博主找了半天,最后用全文件搜索才找着在哪,在lib/debug.h中:

1/* This is outside the header guard so that debug.h may be

2 included multiple times with different settings of NDEBUG. */

3#undef ASSERT

4#undef NOT_REACHED

5

6 #ifndef NDEBUG

7#define ASSERT(CONDITION) \ 8if (CONDITION) { } else { \

9 PANIC ("assertion `%s' failed.", #CONDITION); \

10 }

11#define NOT_REACHED() PANIC ("executed an unreachable statement"); 12#else

13#define ASSERT(CONDITION) ((void) 0)

14#define NOT_REACHED() for (;;)

15#endif /* lib/debug.h */

1/* GCC lets us add "attributes" to functions, function

2 parameters, etc. to indicate their properties.

3 See the GCC manual for details. */

4#define UNUSED __attribute__ ((unused))

5#define NO_RETURN __attribute__ ((noreturn))

6#define NO_INLINE __attribute__ ((noinline))

7#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST)))

8

9/* Halts the OS, printing the source file name, line number, and

10 function name, plus a user-specific message. */

11#define PANIC(...) debug_panic (__FILE__, __LINE__, __func__,

__VA_ARGS__)

12

13void debug_panic (const char *file, int line, const char *function, 14const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN;

这里根据NDEBUG状态分两种define,一个是ASSERT空函数,NOT_REACHED执行死循环,一个是如果ASSERT参数CONDITION为false的话就调用PANIC输出文件,行数,函数名和用户信息,NOT_REACHED也会输出信息。

有些童鞋在跑测试的时候会出现卡在一个地方不动的状态,其实不是因为你电脑的问题,

而是当一些错误触发NOT_REACHED之类的问题的时候,因为非debug环境就一直执

行死循环了,反映出来的行为就是命令行卡住不动没有输出。

注意这里的语法类似__attribute__和((format(printf, m , n)))是面向gcc编译器处理

的写法,这里做的事情其实是参数声明和调用匹配性检查。

好,继续来看palloc_free_multiple,用page_idx保存了计算出来了页id,清空了页指针,然后还剩下最后两行:

1 ASSERT (bitmap_all (pool->used_map, page_idx, page_cnt));

2 bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false);

第一个断言:

1/* Returns true if every bit in B between START and START + CNT,

2 exclusive, is set to true, and false otherwise. */

3bool

4 bitmap_all (const struct bitmap *b, size_t start, size_t cnt)

5 {

6return !bitmap_contains (b, start, cnt, false);

7 }

1/* Returns true if any bits in B between START and START + CNT,

2 exclusive, are set to VALUE, and false otherwise. */

3bool

4bitmap_contains (const struct bitmap *b, size_t start, size_t cnt, bool value)

5 {

6 size_t i;

7

8 ASSERT (b != NULL);

9 ASSERT (start <= b->bit_cnt);

10 ASSERT (start + cnt <= b->bit_cnt);

11

12for (i = 0; i < cnt; i++)

13if (bitmap_test (b, start + i) == value)

14return true;

15return false;

16 }

bitmap_contains首先做断言对参数正确性确认,然后如果所有位处于start到

start+cnt都是value的话,别的都是~value的话,返回true,从我们的函数调用来看就是断言位图全是0。

1/* Returns the value of the bit numbered IDX in B. */

2bool

3 bitmap_test (const struct bitmap *b, size_t idx)

4 {

5 ASSERT (b != NULL);

6 ASSERT (idx < b->bit_cnt);

7return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0;

8 }

9

1/* Returns the index of the element that contains the bit

2 numbered BIT_IDX. */

3static inline size_t

4 elem_idx (size_t bit_idx)

5 {

6return bit_idx / ELEM_BITS;

7 }

8

9/* Returns an elem_type where only the bit corresponding to

10 BIT_IDX is turned on. */

11static inline elem_type

12 bit_mask (size_t bit_idx)

13 {

14return (elem_type) 1 << (bit_idx % ELEM_BITS);

15 }

来看bit_test的实现,这里直接返回某一位的具体值。

这里直接用elem_idx获取idx对应的index取出位,然后和bit_mask做与操作,bit_mask就是返回了一个只有idx位是1其他都是0的一个数,也就是说idx必须为1才返回true对bit_test来说,否则false。

好,至此,对palloc_free_multiple只剩一行了:

1 bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false);

/* Sets the CNT bits starting at START in B to VALUE. */

void

bitmap_set_multiple (struct bitmap *b, size_t start, size_t cnt, bool value)

{

size_t i;

ASSERT (b != NULL);

ASSERT (start <= b->bit_cnt);

ASSERT (start + cnt <= b->bit_cnt);

for (i = 0; i < cnt; i++)

bitmap_set (b, start + i, value);

}

这里对位图所有位都做了bitmap_set设置:

1/* Atomically sets the bit numbered IDX in B to VALUE. */

2void

3 bitmap_set (struct bitmap *b, size_t idx, bool value)

4 {

5 ASSERT (b != NULL);

6 ASSERT (idx < b->bit_cnt);

7if (value)

8 bitmap_mark (b, idx);

9else

10 bitmap_reset (b, idx);

11 }

操作系统_第四版_答案_孙钟秀主编 - 第四章

L : semaphore : = m ; / *控制读进程数信号量,最多m W : semaphore : = 1 ; begin cobegin process reader begin repeat SP ( L , 1 , 1 ; W , 1 , 0 ) ; Read the file ; SV ( L , 1 ) ; until false ; end process writer begin Repeat SP ( W , 1 , 1 ; L , rn , 0 ) ; Write the file ; SV ( W , 1 ) ; until false ; end coend end . 上述算法中,SP ( w , 1 , 0 )语句起开关作用,只要没有写者进程进入写,由于这时w = 1 , 读者进程就都可以进入读文件。但一旦有写者进程进入写时,其W = 0 ,则任何读者进程及其他写者进程就无法进入读写。sP ( w , 1 , 1 ; L , rn , 0 )语句表示仅当既无写者进程在写(这时w = 1)、又无读者进程在读(这时L = rn )时,写者进程才能进行临界区写文件。 第四章 作者:佚名来源:网络 1 在一个请求分页虚拟存储管理系统中,一个程序运行的页面走向是: 1 、 2 、 3 、 4 、2 、1 、 5 、 6 、2 、1 、2 、3 、 7 、6 、3 、2 、1 、2 、3 、6 。 分别用FIFO 、OPT 和LRU 算法,对分配给程序3 个页框、4 个页框、5 个页框和6 个页框的情况下,分别求出缺页中断次数和缺页中断率。 答: 只要把表中缺页中断次数除以20,便得到缺页中断率。

操作系统实验报告一

重庆大学 学生实验报告 实验课程名称操作系统原理 开课实验室DS1501 学院软件学院年级2013专业班软件工程2 班学生姓名胡其友学号20131802 开课时间2015至2016学年第一学期 总成绩 教师签名洪明坚 软件学院制

《操作系统原理》实验报告 开课实验室:年月日学院软件学院年级、专业、班2013级软件工 程2班 姓名胡其友成绩 课程名称操作系统原理 实验项目 名称 指导教师洪明坚 教师 评语教师签名:洪明坚年月日 1.实验目的: ?进入实验环境 –双击expenv/setvars.bat ?检出(checkout)EPOS的源代码 –svn checkout https://www.wendangku.net/doc/9717426876.html,/svn/epos ?编译及运行 –cd epos/app –make run ?清除所有的临时文件 –make clean ?调试 –make debug ?在“Bochs Enhanced Debugger”中,输入“quit”退出调试 –调试指令,请看附录A 2.实验内容: ?编写系统调用“time_t time(time_t *loc)” –功能描述 ?返回从格林尼治时间1970年1月1日午夜起所经过的秒数。如果指针loc 非NULL,则返回值也被填到loc所指向的内存位置 –数据类型time_t其实就是long ?typedef long time_t; 3.实验步骤: ?Kernel space –K1、在machdep.c中,编写系统调用的实现函数“time_t sys_time()”,计算用户秒数。需要用到 ?变量g_startup_time,它记录了EPOS启动时,距离格林尼治时间1970年1午夜的秒数 ?变量g_timer_ticks

操作系统OS报告读者与写者问题(进程同步问题)

目录 一、课程设计目的及要求 (1) 二、相关知识 (1) 三、题目分析 (2) 四、概要设计 (4) 五、代码及流程 (5) 六、运行结果 (11) 七、设计心得 (12) 八、参考文献 (12)

一、课程设计目的及要求 读者与写者问题(进程同步问题) 用n 个线程来表示n个读者或写者。每个线程按相应测试数据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者优先的读者-写者问题。 读者-写者问题的读写操作限制: 1)写-写互斥; 2)读-写互斥; 3)读-读允许; 写者优先的附加限制:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态后才能开始读操作。 二、相关知识 Windows API: 在本实验中涉及的API 有: 1线程控制: CreateThread 完成线程创建,在调用进程的地址空间上创建一个线程,以执行指定的函数;它的返回值为所创建线程的句柄。 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD DWORD dwStackSize, // initial stack size LPTHREAD_START_ROUTINE lpStartAddress, // thread function LPVOID lpParameter, // thread argument DWORD dwCreationFlags, // creation option LPDWORD lpThreadId // thread identifier ); 2 ExitThread 用于结束当前线程。 VOID ExitThread( DWORD dwExitCode // exit code for this thread ); 3Sleep 可在指定的时间内挂起当前线程。 VOID Sleep( DWORD dwMilliseconds // sleep time ); 4信号量控制: WaitForSingleObject可在指定的时间内等待指定对象为可用状态; DWORD WaitForSingleObject( HANDLE hHandle, // handle to object DWORD dwMilliseconds // time-out interval );

操作系统实验实验1

广州大学学生实验报告 1、实验目的 1.1、掌握进程的概念,明确进程的含义 1.2、认识并了解并发执行的实质 2.1、掌握进程另外的创建方法 2.2、熟悉进程的睡眠、同步、撤消等进程控制方法 3.1、进一步认识并发执行的实质 3.2、分析进程竞争资源的现象,学习解决进程互斥的方法 4.1、了解守护进程 5.1、了解什么是信号 5.2、INUX系统中进程之间软中断通信的基本原理 6.1、了解什么是管道 6.2、熟悉UNIX/LINUX支持的管道通信方式 7.1、了解什么是消息 7.2、熟悉消息传送的机理 8.1、了解和熟悉共享存储机制 二、实验内容 1.1、编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统 中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示'a',子进程分别显示字符'b'和字符'c'。试观察记录屏幕上的显示结果,并分析原因。 1.2、修改上述程序,每一个进程循环显示一句话。子进程显示'daughter …'及 'son ……',父进程显示'parent ……',观察结果,分析原因。 2.1、用fork( )创建一个进程,再调用exec( )用新的程序替换该子进程的内容 2.2、利用wait( )来控制进程执行顺序 3.1、修改实验(一)中的程序2,用lockf( )来给每一个进程加锁,以实现进程之间的互斥 3.2、观察并分析出现的现象 4.1、写一个使用守护进程(daemon)的程序,来实现: 创建一个日志文件/var/log/Mydaemon.log ; 每分钟都向其中写入一个时间戳(使用time_t的格式) ; 5.1、用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止: Child process1 is killed by parent! Child process2 is killed by parent! 父进程等待两个子进程终止后,输出如下的信息后终止: Parent process is killed! 5.2、用软中断通信实现进程同步的机理

pintos-pro4-filesystem

Project 4 FileSystem 作者:电子科技大学 2014.2.7 文件系统是操作系统的五大功能模块之一,主要实现操作系统对程序、数据、设备等的管理。 一、当前pintos文件系统的功能 当前pintos文件系统已经实现了基本的文件创建删除功能。文件是固定大小,连续存放的。 (1)文件在磁盘的存储方式: 每个文件都有一个disk_inode存放在磁盘的一个扇区上,其结构如下:struct inode_disk { block_sector_t start; //文件数据每一个起始块 off_t length; //文件长度。 unsigned magic; // uint32_t unused[125]; }; 现在pintos文件是连续的,而且在创建时指定其大小后,再不能改变大小。由起始扇区和文件大小就能找到所有文件数据。 目录也是一个文件,只是其容中都存放如下结构: struct dir_entry { Block_sector_t inode_sector; //文件inode_disk 所在扇区。 Char name[NAME_MAX+1]; Bool in_use; }; (2)磁盘空闲空间管理方式。 空闲空间是用位图来表示的。其中位图也是一个文件,其disk inode存放在Sector 0. 位图文件大小显然与磁盘大小有关,用一个位表示一个扇区是否被分配,一个扇区512字节,一个扇区作为一个物理块,创建一个2M的磁盘,有1024*1024*2/512bit=4096bit= 4096/8=512Byte。 (3)文件系统初始化过程: 在init.c:main()函数中调用了filesys_init(); 1. 在filesys_init()中,初始化了bitmap,而且对磁盘进行了格式化。其中格式化就是创建 了两个文件,一个用来管理空闲块的位置图文件,一个是根目录文件。 2.Filesys_init()中调用了free_map_init(),在free_map_init()中调用bitmap_create()创建了位 图,大小依据磁盘大小。而且标记了0 1两个扇区为已经分配,作为free_map_file的disk_inode空间和根目录文件的disk_inode空间。此时free_map只是在存中。 3.Filesys_init()中又调用了do_format()

操作系统第4章答案(上)

赵盈盈 93 第四章作业上 1. 解释名词:程序的顺序执行;程序的并发执行。 答:程序的顺序执行:一个具有独立功能的程序独占cpu直到得到最终结果的进程。 程序的并发执行:两个或两个以上程序在计算机系统中同时处于一开始执行且尚未结束的状态。 2. 什么是进程进程与程序的主要区别是什么 答:进程:进程是具有独立功能的程序关于某个数据集合的一次运行活动,进程是系统进行资源分配和调度的独立单元。 进程和程序的区别: ●程序是静态的,进程是动态的 ●进程有程序和数据两部分组成 ●进程具有生命周期,有诞生和消亡,是短暂的;而程序是相对长久的 ●进程能更真实的描述并发,而程序不行。 ●一个进程可以对应多个程序。一个程序可以对应多个进程 ●进程可以创建其他进程,程序不能 3. 图1所示,设一誊抄程序,将f中记录序列正确誊抄到g中,这一程序由get、copy、put三个程序段组成,它们分别负责获得记录、复制记录、输出记录。请指出这三个程序段对f中的m个记录进行处理时各种操作的先后次序,并画出誊抄此记录序列的先后次序图(假设f中有1,2,…,m个记录,s,t为设置在主存中的软件缓冲区,每次只能装一个记录)。 图1 改进后的誊抄过程 答:

4. 进程有哪几种基本状态试画出进程状态变迁图,并标明发生变迁的可能原因。 答:进程基本状态:运行、就绪、等待 就绪到运行:调度程序选择一个新的进程运行 运行到就绪:运行进程用完了时间片 或运行进程被中断,因为一个高优先级的进程处于就绪状态 运行到等待:OS 尚未完成服务 或对一资源的访问尚不能进行 或初始化I/O 且必须等待结果 或等待某一进程提供输入(IPC ) 等待到就绪:当所有的事件发生时 5. 什么是进程控制块它有什么作用 答:PCB :为了便于系统控制和描述进程的活动过程,在操作系统核心中为进程定义的一个专门的数据结构。 作用:系统用PCB 来控制和管理进程的调用,PCB 也是系统感知进程存在的唯一标志 G C G P C P G …C P

操作系统实验报告4

《操作系统》实验报告 实验序号: 4 实验项目名称:进程控制

Printf(“child Complete”); CloseHandle(pi.hProcess); CloseHandle(pi hThread); ﹜ 修改后: #include #include int main(VOID) { STARTUPINFO si; PROCESS_INFORMA TION pi; ZeroMemory(&si,sizeof(si)); si.cb=sizeof(si); ZeroMemory(&pi,sizeof(pi)); if(!CreateProcess(NULL, "c:\\WINDOWS\\system32\\mspaint.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si,&pi)) { fprintf(stderr,"Creat Process Failed"); return -1; } WaitForSingleObject(pi.hProcess,INFINITE); printf("child Complete"); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } 在“命令提示符”窗口运行CL命令产生可执行程序4-1.exe:C:\ >CL 4-1.cpp

实验任务:写出程序的运行结果。 4.正在运行的进程 (2)、编程二下面给出了一个使用进程和操作系统版本信息应用程序(文件名为4-5.cpp)。它利用进程信息查询的API函数GetProcessVersion()与GetVersionEx()的共同作用。确定运行进程的操作系统版本号。阅读该程序并完成实验任务。 #include #include

操作系统 实验 五 线程间的互斥与同步

实验五线程间的互斥与同步 实验学时:2学时 实验类型:验证、设计型 一、实验目的 理解POSIX线程(Pthread)互斥锁和POSIX信号量机制,学习它们的使用方法;编写程序,实现多个POSIX线程的同步控制。 二,实验内容 创建4个POSIX线程。其中2个线程(A和B)分别从2个数据文件(data1.txt和data2.txt)读取10个整数. 线程A和B把从文件中读取的逐一整数放入一个缓冲池. 缓冲池由n个缓冲区构成(n=5,并可以方便地调整为其他值),每个缓冲区可以存放一个整数。另外2个线程,C和D,各从缓冲池读取10数据。线程C、D每读出2个数据,分别求出它们的和或乘积,并打印输出。 提示:在创建4个线程当中,A和B是生产者,负责从文件读取数据到公共的缓冲区,C和D是消费者,从缓冲区读取数据然后作不同的计算(加和乘运算)。使用互斥锁和信号量控制这些线程的同步。不限制线程C和D从缓冲区得到的数据来自哪个文件。 在开始设计和实现之前,务必认真阅读课本6.8.4节和第6章后面的编程项目——生产者-消费者问题。

三,实验要求 按照要求编写程序,放在相应的目录中,编译成功后执行,并按照要求分析执行结果,并写出实验报告。 四,实验设计 1,功能设计 根据实验要求,主程序需要创建四个线程,两个线程负责从文件读取数据到缓冲区,两个线程负责将缓冲区的数据做数学运算。由于同一个进程中的各个线程共享资源,可以用一个二维数组的全局变量作为公共缓冲区,同时还需要一个整形全局变量size用来做数组的索引。读线程的运行函数打开不同的文件并从中读取数据到二维数组中,每次写入数组后size加一。运算线程从二维数组中读数并做运算,每次读数之前size减一。本题的关键在于如何使用信号量保证进程的同步与互斥。在运算线程从缓冲区读取之前缓冲区里必须有数,即任意时刻运算操作的执行次数必须小于等于读取操作的执行次数。同时应该保证两个读线程和两个运算线程两两互斥。由于以上分析,使用了四个信号量sem1,sem2,sem3和sem4。sem1保证线程1和线程2互斥,sem2保证线程3和线程4互斥,sem3保证线程3和线程4互斥,sem4保证线程4和线程1互斥。即这四个信号量使四个线程循环进行,从而保证了运行结果的正确性。 源代码及注释: #include #include #include #define NUM 200

操作系统实验_实验1

广州大学学生实验报告 开课学院及实验室:计算机科学与工程实验室 2015年11月11日 实验课 操作系统成绩 程名称 实验项 进程管理与进程通信指导老师陈康民目名称 (***报告只能为文字和图片,老师评语将添加到此处,学生请勿作答***) 进程管理 (一)进程的创建实验 一、实验目的 1、掌握进程的概念,明确进程的含义 2、认识并了解并发执行的实质 二、实验内容 1、编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一 个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符:父进程显示'a',子进程分别显示字符'b'和字符'c'。试观察记录屏幕上的显示结果,并分析原因。 2、修改上述程序,每一个进程循环显示一句话。子进程显示'daughter …'及'son ……', 父进程显示'parent ……',观察结果,分析原因。 三、实验步骤 1、编写一段程序,使用系统调用fork( )创建两个子进程。 代码: #include main( ) { int p1,p2; while((p1=fork( ))= = -1); /*创建子进程p1*/ if (p1= =0) putchar('b'); else { while((p2=fork( ))= = -1); /*创建子进程p2*/ if(p2= =0) putchar('c'); else putchar('a'); } } 运行结果:

bca,bac, abc ,……都有可能。 2、修改上述程序,每一个进程循环显示一句话。子进程显示'daughter …'及'son ……',父进程显示'parent ……',观察结果,分析原因。 代码:#include main( ) { int p1,p2,i; while((p1=fork( ))= = -1); /*创建子进程p1*/ if (p1= =0) for(i=0;i<10;i++) printf("daughter %d\n",i); else { while((p2=fork( ))= = -1); /*创建子进程p2*/ if(p2= =0) for(i=0;i<10;i++) printf("son %d\n",i); else for(i=0;i<10;i++) printf("parent %d\n",i); } } 结果:

操作系统实验报告

操作系统教程 实 验 指 导 书 姓名: 学号: 班级:软124班 指导老师:郭玉华 2014年12月10日

实验一WINDOWS进程初识 1、实验目的 (1)学会使用VC编写基本的Win32 Consol Application(控制台应用程序)。 (2)掌握WINDOWS API的使用方法。 (3)编写测试程序,理解用户态运行和核心态运行。 2、实验内容和步骤 (1)编写基本的Win32 Consol Application 步骤1:登录进入Windows,启动VC++ 6.0。 步骤2:在“FILE”菜单中单击“NEW”子菜单,在“projects”选项卡中选择“Win32 Consol Application”,然后在“Project name”处输入工程名,在“Location”处输入工程目录。创建一个新的控制台应用程序工程。 步骤3:在“FILE”菜单中单击“NEW”子菜单,在“Files”选项卡中选择“C++ Source File”, 然后在“File”处输入C/C++源程序的文件名。 步骤4:将清单1-1所示的程序清单复制到新创建的C/C++源程序中。编译成可执行文件。 步骤5:在“开始”菜单中单击“程序”-“附件”-“命令提示符”命令,进入Windows“命令提示符”窗口,然后进入工程目录中的debug子目录,执行编译好的可执行程序: E:\课程\os课\os实验\程序\os11\debug>hello.exe 运行结果 (如果运行不成功,则可能的原因是什么?) : 有可能是因为DOS下路径的问题 (2)计算进程在核心态运行和用户态运行的时间 步骤1:按照(1)中的步骤创建一个新的“Win32 Consol Application”工程,然后将清单1-2中的程序拷贝过来,编译成可执行文件。 步骤2:在创建一个新的“Win32 Consol Application”工程,程序的参考程序如清单1-3所示,编译成可执行文件并执行。 步骤3:在“命令提示符”窗口中运行步骤1中生成的可执行文件,测试步骤2中可执行文件在核心态运行和用户态运行的时间。 E:\课程\os课\os实验\程序\os12\debug>time TEST.exe 步骤4:运行结果 (如果运行不成功,则可能的原因是什么?) : 因为程序是个死循环程序 步骤5:分别屏蔽While循环中的两个for循环,或调整两个for循环的次数,写出运行结果。 屏蔽i循环: 屏蔽j循环: _______________________________________________________________________________调整循环变量i的循环次数:

操作系统课程设计用多进程同步方法解决生产者-消费者问题

操作系统课程设计 用多进程同步方法解决生产者-消费者问题 系别:计科系 专业: 计算机科学与技术 班级:04 级 4 班 学号:0410******* 姓名:苏德洪 时间:2006-7-7—2006-7-14

目录 一、题目: (3) 二、设计目的: (3) 三、总体设计思想概述: (3) 四、说明: (3) 五、设计要求: (3) 六、设计方案: (3) 七、流程图: (5) 八、运行结果 (7) 九、源程序 (11) 十、总结 (18) 十一、参考文献 (20)

一、题目: 用多进程同步方法解决生产者-消费者问题。 二、设计目的: 通过研究Linux 的进程机制和信号量实现生产者消费者问题的并发控制。 三、总体设计思想概述: 1、生产者—消费者问题是一种同步问题的抽象描述。 2、计算机系统中的每个进程都可以消费或生产某类资源。当系统中某一进程使用某一 资源时,可以看作是消耗,且该进程称为消费者。 3、而当某个进程释放资源时,则它就相当一个生产者。 四、说明: 有界缓冲区内设有20个存储单元,放入/取出的数据项设定为1-20这20个整型数。 五、设计要求: 1、每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的全部内容,当前 指针位置和生产者/消费者线程的标识符。 2、生产者和消费者各有两个以上。 3、多个生产者或多个消费者之间须有共享对缓冲区进行操作的函数代码。 六、设计方案: 通过一个有界缓冲区(用数组来实现,类似循环队列)把生产者和消费者联系起来。假定生产者和消费者的优先级是相同的,只要缓冲区未满,生产者就可以生产产品并将产品送入缓冲区。类似地,只要缓冲区未空,消费者就可以从缓冲区中去走产品并消费它。 应该禁止生产者向满的缓冲区送入产品,同时也应该禁止消费者从空的缓冲区中取出产品,这一机制有生产者线程和消费者线程之间的互斥关系来实现。 为解决生产者/消费者问题,应该设置两个资源信号量,其中一个表示空缓冲区的数目,用g_hFullSemaphore表示,其初始值为有界缓冲区的大小SIZE_OF_BUFFER;另一个表示缓冲区中产品的数目,用g_hEmptySemaphore表示,其初始值为0。另外,由于有界缓冲区是一个临界资源,必须互斥使用,所以还需要再设置一个互斥信号量g_hMutex,起初值为1。

操作系统实验一

本科实验报告 课程名称:操作系统 学号: 姓名: 专业: 班级: 指导教师: 课内实验目录及成绩 信息技术学院

实验(实验一) 1 实验名称:基本shell命令及用户管理 2 实验目的 2.1 掌握安装Linux操作系统的方法。 2.2 掌握Linux操作系统的基本配置。 2.3 了解GNOME桌面环境。 2.4 掌握基本shell命令的使用。 3 实验准备 3.1 下载VMware Workstation虚拟机软件(版本不限)。 3.2 准备Linux操作系统的安装源(内核版本和发行版本均不限)。 注:实验准备、实验内容4.1和4.2作为回家作业布置,同学们利用课余时间可在私人计算机上完成。 4 实验要求、步骤及结果 4.1 安装虚拟机软件。 【操作要求】安装VMware Workstation虚拟机软件,并填写以下4.1.1和4.1.2的内容。 4.1.1【VMware Workstation虚拟机版本号】 4.1.2【主要配置参数】 4.2 安装Linux操作系统。 【操作要求】安装Linux操作系统,版本不限。 Linux发行版本: Linux内核版本:

【主要操作步骤:包括分区情况】 1、创建一台虚拟机安装操作系统时客户机操作系统选择Linux 2、修改虚拟机的安装路径。 3、建一个新的虚拟磁盘,磁盘的空间20GB,并且将单个文件存储虚拟磁盘。 4、设置分区完毕,安装虚拟机 4.3 了解Linux操作系统的桌面环境之一GNOME。 【操作要求】查看桌面图标,查看主菜单,查看个人用户主目录等个人使用环境。【操作步骤1】桌面图标

【操作步骤2】主菜单 【操作步骤3】个人用户主目录 【操作步骤4】启动字符终端

操作系统课程设计报告Alarm-Clock

西安电子科技大学 操作系统课程设计 (2016年度) 实 验 报 告 实验名称:Alarm-Clock 班级:1403018 姓名:张可心 学号:14030188030

一实验内容 源代码devices/timer.c中有一个timer_sleep()函数。定义如图1所示 图1 timer_sleep()函数的定义 该函数的功能是让调用它的线程睡眠一段时间(ticks),然后唤醒。事实上,品同时已经实现该函数,只是使用的是“忙等待”的方法。 任务要求:重新实现timer_sleep()函数,避免“忙等待”的发生,设计一种策略并实现。 二分析及设计 1. 阅读相关的源代码文件,并了解其中关键的数据结构和函数的含义:在xd/os/pintos/src/threads目录下的thread.h,thread.c文件,它们是有关线程初始化、阻塞、解除阻塞,线程调度等内容。xd/os/pintos/src/devices/目录下的timer.h,timer.c文件,本实验要修改的timer_sleep()函数就在其中。同时还要注意定时器中断的处理过程。 2. Thread.h中定义了一个结构体struct thread,这个结构体中用于存放线程的基本信息,如图2所示

图2线程的基本信息 3. Pintos中线程的状态有四种,在thread.h函数中的定义如图3 图 3 线程的状态定义 4.系统的驱动:驱动力为定时器中断函数,定时器中断频率在timer.h中定义如

图4所示 图4 定时器中断频率 由此可知一个定时器中断的时长大约为10ms,这里称为一个ticks。 5.中断处理过程 中断处理函数的调用过程如图5所示 图5中断处理函数的调用过程 原线程中这个timer_sleep函数执行过程是不断地循环检测这个函数执行以及执行过后等待时长是否小于cpu的时钟周期,如果是,则重复循环等待,直至等待时间大于等于ticks,则执行线程后续代码。此方法的缺点是,函数不断循环试探,占用cpu。 设计方案从去掉循环测试时间开始,将在thread结构体中添加一个变量block_ticks(线程阻塞时间),来标记时间的变化。当线程度过了ticks,就唤醒它,进入ready状态。 三详细实现 1 改造timer_sleep,如图6所示

操作系统 第四章 课后题答案

第四章 1.为什么说多级反馈队列调度算法能较好地满足各类用户的需要(来自百度): 答案一: 多级反馈队列调度算法能较好地满足各种类型用户的需要。对终端型作业用户而言,由于他们所提交的大多属于交互型作业,作业通常比较短小,系统只要能使这些作业在第1级队列所规定的时间片内完成,便可使终端型作业用户感到满意;对于短批处理作业用户而言,他们的作业开始时像终端型作业一样,如果仅在第1级队列中执行一个时间片即可完成,便可以获得与终端型作业一样的响应时间,对于稍长的作业,通常也只需要在第2级队列和第3级队列中各执行一个时间片即可完成,其周转时间仍然较短;对于长批处理作业用户而言,它们的长作业将依次在第1,2,…,直到第n级队列中运行,然后再按时间片轮转方式运行,用户不必担心其作业长期得不到处理。 答案二:(惠州学院操作系统课后题)与答案一基本相似,可看做精简版。 答:(1)终端型作业用户提交的作业大多属于较小的交互型作业,系统只要使这些作业在第一队列规定的时间片内完成,终端作业用户就会感到满足。 (2)短批处理作业用户,开始时像终端型作业一样,如果在第一队列中执行一个时间片段即可完成,便可获得与终端作业一样的响应时间。对于稍长作业,通常只需在第二和第三队列各执行一时间片即可完成,其周转时间仍然较短。 (3)长批处理作业,它将依次在第1 ,2 ,…,n个队列中运行,然后再按轮转方式运行,用户不必担心其作业长期得不到处理。所以,多级反馈队列调度算法能满足多用户需求。 2.

分别对以上两个进程集合,计算使用先来先服务(FCFS)、时间片轮转法(时间片q=1)、短进程优先(SPN)、最短剩余时间优先(SRT,时间片q=1)、响应比高者优先(HRRN)及多级反馈队列(MFQ,第1个队列的时间片为1,第i(i<1)个队列的时间片q=2(i-1))算法进行CPU调度,请给出各进程的完成时间、周转时间、带权周转时间,及所有进程的平均周转时间和平均带权周转时间。

南昌大学操作系统线程进程同步实验报告

南昌大学实验报告 ---(1)进程/线程同步 学生姓名:学号:专业班级:网络工程131班 实验类型:■验证□综合□设计□创新实验日期:实验成绩: 一、实验目的 本实验讨论临界区问题及其解决方案。首先创建两个共享数据资源的并发线程。在没有同步控制机制的情况下,我们将看到某些异常现象。针对观察到的现象,本实验采用Windows 的信号量机制解决临界区互斥访问。 二、实验内容 2.1 进程/线程并发执行 Windows操作系统支持抢先式调度,这意味着一线程运行一段时间后,操作系统会暂停其运行并启动另一线程。也就是说,进程内的所有线程会以不可预知的步调并发执行。为了制造混乱,我们首先创建两个线程t1和t2。父线程(主线程)定义两个全局变量,比如accnt1和accnt2。每个变量表示一个银行账户,其值表示该账户的存款余额,初始值为0。线程模拟在两个账户之间进行转账的交易。也即,每个线程首先读取两个账户的余额,然后产生一个随机数r,在其中一个账户上减去该数,在另一个账户上加上该数。线程操作的代码框架如下: counter=0; do { tmp1 = accnt1 ; tmp2 = accnt2 ; r = rand ( ) ; accnt1 = tmp1 + r ; accnt2 = tmp2 ? r ; counter++; } while ( accnt1 + accnt2 == 0 ) ; print ( counter ) ; 两个线程执行相同的代码。只要它们的执行过程不相互交叉,那么两个账户的余额之和将永远是0。但如果发生了交叉,那么某线程就有可能读到新的accnt1值和老的accnt2值,从而导致账户余额数据发生混乱。线程一旦检测到混乱的发生,便终止循环并打印交易的次数(counter)。 请编写出完整的程序代码并运行,然后观察产生混乱需要的时间长短。因为这是我们编写的第一个程序,因此这里我给出了完整的代码,请参考。有能力的同学在参考下面的代码之前,请先自己尝试一下。 #include "stdafx.h" #include

Pintos pro 1 实验报告

Pintos pro 1 实验报告 第丁组 王小花 201201102 李三强 201201103 1.Alarm Clock timer_sleep的作用是让此线程等待ticks单位时长,然后再执行。函数原型: void timer_sleep (int64_t ticks) //参数的意思是你想要等待的时间长度 { int64_t start = timer_ticks (); //记录开始时的系统时间 ASSERT (intr_get_level () == INTR_ON); while (timer_elapsed (start) < ticks) //如果elapse(流逝)的时间>=ticks时就返回。否则将持续占用cpu。 thread_yield (); } 在timer_sleep()函数中让该进程暂时阻塞(调用thread_block()),然后过了ticks个时间段后再把它加回到ready queue中。 至于因为每一次时间中断的时候恰好是ticks加一的时候,因此我们可以改进timer_interrup()函数,使得系统每次调用他的时候都检查一下我的这个进程是否已经等待了足够长得时间了。如果还没有够,则不管它,如果已经足够长了,则调用thread_unblock()函数将它召唤回ready_queue中。 加入一个整形变量int block_ticks。当这个线程被block的时候,将block_ticks记录为需要等待的时间长度。之后每次中断的时候检查它一次,并且顺便使其自减。当它小到等于0的时候,把线程调到ready queue中。 Threads.h tread.c interrup.h timer.c

操作系统第四章

第四章 一、问答题 1、什么叫临界资源?什么叫临界区?对临界区的使用应符合哪些规则?(同步机制应遵循的准则是什么?) 2、死锁产生的4个必要条件是什么?它们是彼此独立的吗? 3、何谓死锁?为什么将所有资源按类型赋予不同的序号,并规定所有进程按资源序号递增的顺序申请资源后,系统便不会产生死锁? 4、什么是安全状态?怎么判断系统是否处于安全状态? 5、简述死锁定理和解除死锁的方法。 二、计算和证明 1、当前系统中出现下述资源分配情况: 利用银行家算法,试问如果进程P2提出资源请求Request(1,2,2,2)后,系统能否将资源分配给它? 2、若系统有某类资源m×n+1个,允许进程执行过程中动态申请该类资源,但在该系统上运行的每一个进程对该资源的占有量任何时刻都不会超过m+1个。当进程申请资源时只要有资源尚未分配完则满足它的申请,但用限制系统中可同时执行的进程数来防止发生死锁,你认为进程调度允许同时执行的最大进程数应该是多少?并说明原因。 3、n个进程共享某种资源R,该资源共有m个,每个进程一次一个地申请或释放资源。假设每个进程对该资源的最大需求量均小于m,且各进程最大需求量之和小于m+n,试证明在这个系统中不可能发生死锁。

4、当前某系统有同类资源7个,进程P,Q所需资源总数分别为5,4。它们向系统申请资源的次序和数量如表所示。回答: 问:采用死锁避免的方法进行资源分配,请你写出系统完成第3次分配后各进程占有资源量,在以后各次的申请中,哪次的申请要求可先得到满足? 5、一个计算机系统有6个磁带驱动器4个进程。每个进程最多需要n个磁带驱动器。问当n为什么值时,系统不会发生死锁?并说明理由 6、n个进程共享某种资源R,该资源共有m个可分配单位,每个进程一次一个地申请或释放资源单位。假设每个进程对该资源的最大需求量均小于m,问各进程最大需求量之和至少小于多少,系统不会发生死锁,并证明。 7. 考虑某一系统,它有4类资源R1,R2,R3,R4,有5个并发进程P0,P1,P2,P3,P4。请按照银行家算法回答下列问题; ⑴各进程的最大资源请求,已分配的资源矩阵和当前资源剩余向量如下图所示,计算各进程的需求向量组成的矩阵。 ⑵系统当前是处于安全状态吗? ⑶当进程P2申请的资源分别为(0,3,2,0)时,系统能立即满足吗?

操作系统实验报告

操作系统教程实验报告 专业班级 学号 姓名 指导教师

实验一WINDOWS进程初识 1、实验目的 (1)学会使用VC编写基本的Win32 Consol Application(控制台应用程序)。 (2)掌握WINDOWS API的使用方法。 (3)编写测试程序,理解用户态运行和核心态运行。 2、实验内容和步骤 (1)编写基本的Win32 Consol Application 步骤1:登录进入Windows,启动VC++ 6.0。 步骤2:在“FILE”菜单中单击“NEW”子菜单,在“projects”选项卡中选择“Win32 Consol Application”,然后在“Project name”处输入工程名,在“Location”处输入工程目录。创建一个新的控制台应用程序工程。 步骤3:在“FILE”菜单中单击“NEW”子菜单,在“Files”选项卡中选择“C++ Source File”, 然后在“File”处输入C/C++源程序的文件名。 步骤4:将清单1-1所示的程序清单复制到新创建的C/C++源程序中。编译成可执行文件。 步骤5:在“开始”菜单中单击“程序”-“附件”-“命令提示符”命令,进入Windows “命令提示符”窗口,然后进入工程目录中的debug子目录,执行编译好的可执行程序:E:\课程\os课\os实验\程序\os11\debug>hello.exe 运行结果 (如果运行不成功,则可能的原因是什么?) : (2)计算进程在核心态运行和用户态运行的时间 步骤1:按照(1)中的步骤创建一个新的“Win32 Consol Application”工程,然后将清单1-2中的程序拷贝过来,编译成可执行文件。 步骤2:在创建一个新的“Win32 Consol Application”工程,程序的参考程序如清单1-3所示,编译成可执行文件并执行。 步骤3:在“命令提示符”窗口中运行步骤1中生成的可执行文件,测试步骤2中可执行文件在核心态运行和用户态运行的时间。 E:\课程\os课\os实验\程序\os12\debug>time TEST.exe 步骤4:运行结果 (如果运行不成功,则可能的原因是什么?) : 步骤5:分别屏蔽While循环中的两个for循环,或调整两个for循环的次数,写出运行结果。 屏蔽i循环:

用多线程同步方法解决生产者-消费者问题(操作系统课设)

. 题目用多线程同步方法解决生产者-消费 者问题(Producer-Consumer Problem) 学院计算机科学与技术学院 专业软件工程 班级 姓名 指导教师 年月日

目录 目录 (1) 课程设计任务书 (2) 正文 (2) 1.设计目的与要求 (2) 1.1设计目的 (2) 1.2设计要求 (2) 2.设计思想及系统平台 (2) 2.1设计思想 (2) 2.2系统平台及使用语言 (2) 3.详细算法描述 (3) 4.源程序清单 (5) 5.运行结果与运行情况 (10) 6.调试过程 (15) 7.总结 (15) 本科生课程设计成绩评定表 (16)

课程设计任务书 学生姓名:专业班级: 指导教师:工作单位:计算机科学与技术学院 题目: 用多线程同步方法解决生产者-消费者问题 (Producer-Consumer Problem) 初始条件: 1.操作系统:Linux 2.程序设计语言:C语言 3.有界缓冲区内设有20个存储单元,其初值为0。放入/取出的数据项按增序设定为1-20这20个整型数。 要求完成的主要任务:(包括课程设计工作量及其技术要求,以及说明书撰写等具体要 求) 1.技术要求: 1)为每个生产者/消费者产生一个线程,设计正确的同步算法 2)每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的当前全部内容、当前指针位置和生产者/消费者线程的自定义标识符。 3)生产者和消费者各有两个以上。 4)多个生产者或多个消费者之间须共享对缓冲区进行操作的函数代码。 2.设计说明书内容要求: 1)设计题目与要求 2)总的设计思想及系统平台、语言、工具等。 3)数据结构与模块说明(功能与流程图) 4)给出用户名、源程序名、目标程序名和源程序及其运行结果。(要注明存储各个程序及其运行结果的主机IP地址和目录。) 5)运行结果与运行情况 (提示: (1)有界缓冲区可用数组实现。 (2)编译命令可用:cc -lpthread -o 目标文件名源文件名 (3)多线程编程方法参见附件。) 3. 调试报告: 1)调试记录 2)自我评析和总结 上机时间安排: 18周一~ 五 08:0 - 12:00 指导教师签名:年月日

操作系统实验报告.

学生学号0121210680225 实验课成绩 武汉理工大学 学生实验报告书 实验课程名称操作系统 开课学院计算机科学与技术学院 指导老师姓名刘军 学生姓名李安福 学生专业班级软件sy1201 2014 — 2015 学年第一学期

《操作系统》实验教学大纲 课程编号: 课程名称:操作系统/Operating System 实验总学时数:12学时 适应专业:计算机科学与技术、软件工程 承担实验室:计算机科学与技术学院实验中心 一、实验教学的目的和任务 通过实验掌握Linux系统下常用键盘命令、系统调用、SHELL编程、后台批处理和C程序开发调试手段等基本用法。 二、实验项目及学时分配 序号实验项目名称实验学时实验类型开出要求 01 Linux键盘命令和vi 2 设计必开 02 Linux下C编程 2 设计必开 03 SHELL编程和后台批处理 2 设计必开 04 Linux系统调用(time) 2 设计必开 05 Linux进程控制(fork) 4 设计必开 三、每项实验的内容和要求: 1、Linux键盘命令和vi 要求:掌握Linux系统键盘命令的使用方法。 内容:见教材p4, p9, p40, p49-53, p89, p100 2、Linux下的C编程 要求:掌握vi编辑器的使用方法;掌握Linux下C程序的源程序编辑方法;编译、连接和运行方法。 内容:设计、编辑、编译、连接以及运行一个C程序,其中包含键盘输入和屏幕输出语句。 3、SHELL编程和后台批处理 要求:掌握Linux系统的SHELL编程方法和后台批处理方法。 内容:(1) 将编译、连接以及运行上述C程序各步骤用SHELL程序批处理完成,前台运行。 (2) 将上面SHELLL程序后台运行。观察原C程序运行时输入输出情况。 (3) 修改调试上面SHELL程序和C程序,使得在后台批处理方式下,原键 盘输入内容可以键盘命令行位置参数方式交互式输入替代原键盘输入内容, 然后输出到屏幕。 4、Linux系统调用使用方法。

相关文档