精华内容
下载资源
问答
  • 南京大学操作系统实验lab1
  • 南京大学操作系统课程实验/ OperatingSystemLabs_NJU 实验网站网址: : 实验内容 Lab1实验要求 1.1。在实模式下实现一个Hello World程序 1.2。在保护模式下实现一个Hello World程序 1.3。在保护模式下加载磁盘中的...
  • 1 MINIX操作系统简介11 1.1 MINIX与UNIX . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.2 探索MINIX . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.3 编辑器:vi . . . . . . . . . ....
  • OS:南京大学软件学院操作系统实验
  • 南京邮电大学操作系统——实验二:线程的互斥与同步前言编程题题目1 售票同步问题题目2 司机和售票员的同步过程关于吃橘子问题的异常输出及排查问题对应的错误输出 前言 由于本实验的大部分实验较为简单,所以这里只...

    前言

    由于本实验的大部分实验较为简单,所以这里只贴出两个需要注意的点:

    1. 最后的编程实现部分;
    2. 关于其中一个实验的debug过程,这个过程相当痛苦,值得记录一下;

    编程题

    题目1 售票同步问题

    源码为:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include<semaphore.h>
    
    int ticketAmount = 2; //Global Variable 
    sem_t lock; //Global lock
    
    void* ticketAgent(void* arg){
        sem_wait(&lock);
        int t = ticketAmount;
        if (t > 0)
        {
            printf("One ticket sold!\n");
            t--;
        }else{
            printf("Ticket sold out!!\n");
        }
        ticketAmount = t;
        sem_post(&lock);
        pthread_exit(0);
    }
    
    int main(int argc, char const *argv[])
    {
        sem_init(&lock,0,1);
        pthread_t ticketAgent_tid[2];
        for (int i = 0; i < 2; ++i)
        {
            pthread_create(ticketAgent_tid+i, NULL, ticketAgent,NULL);
        }
        for (int i = 0; i < 2; ++i)
        {
            pthread_join(ticketAgent_tid[i],NULL);
        }
    
        printf("The left ticket is %d\n", ticketAmount);
        return 0;
    }
    
    

    题目2 司机和售票员的同步过程

    源码为:

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <semaphore.h>
    
    sem_t door;
    sem_t bus;
    void Proc_Seller()
    {
        int i = 0;
        while (i++ < 10)
        {
            sem_wait(&bus);
            printf("Seller:I'm selling ticket,wait me!\n");
            printf("Seller:The door is closed,please start the bus!\n");
            sem_post(&door);
        }
    }
    void Proc_Driver()
    {
        int i = 0;
        while (i++ < 10)
        {
            sem_wait(&door);
            printf("Driver:I'm driving!\n");
            printf("Driver:Bus arrived!\n");
            sem_post(&bus);
        }
    }
    int main(int argc, char const *argv[])
    {
        sem_init(&door, 0, 0);
        sem_init(&bus, 0, 1);
        pthread_t ticketAgent_tid[2];
        pthread_create(ticketAgent_tid, NULL, Proc_Driver, NULL);
        pthread_create(ticketAgent_tid, NULL, Proc_Seller, NULL);
    
        for (int i = 0; i < 2; ++i)
        {
            pthread_join(ticketAgent_tid[i], NULL);
        }
    
        return 0;
    }
    
    

    关于吃橘子问题的异常输出及排查问题

    题目为:一个能放N(这里N设为3)个水果的盘子,爸爸只往盘子里放苹果,妈妈只放橙子,女儿只吃盘子里的苹果,儿子只吃橙子。
    对应的源码为:

    #include <stdio.h> 
    #include <pthread.h> 
    #include <semaphore.h> 
    #include <unistd.h> 
    sem_t empty; //控制盘子里可放的水果数 
    sem_t apple; //控制苹果数 
    sem_t orange; //控制橙子数 
    pthread_mutex_t work_mutex=PTHREAD_MUTEX_INITIALIZER; //声明互斥量work_mutex 
    int fruitCount = 0;//盘子中水果的数量 
    
    void *thread_f(void *arg) //father线程 
    { 
    while(1)
    { 
    sem_wait(&empty); //占用一个盘子空间,可放水果数减1 
    pthread_mutex_lock(&work_mutex); //加锁 
    printf("爸爸放入一个苹果!(盘子当前水果总数:%d)\n", ++fruitCount); 
    sem_post(&apple); //释放一个apple信号了,可吃苹果数加1 
    pthread_mutex_unlock(&work_mutex); //解锁 
    sleep(0.1); 
    } 
    } 
    
    void * thread_m(void *arg) //mother线程 
    { 
    while(1){ 
    sem_wait(&empty); 
    pthread_mutex_lock(&work_mutex); //加锁 
    printf("妈妈放入一个橙子!(盘子当前水果总数:%d)\n", ++fruitCount); 
    sem_post(&orange); 
    pthread_mutex_unlock(&work_mutex); //解锁 
    sleep(0.2); 
    } 
    } 
    void * thread_s(void *arg) //son线程 
    { 
    while(1){ 
    sem_wait(&orange); //占用一个橙子信号量,可吃橙子数减1 
    pthread_mutex_lock(&work_mutex); //加锁 
    printf("儿子吃了一个橙子!(盘子当前水果总数:%d)\n", --fruitCount); 
    sem_post(&empty); //吃了一个橙子,释放一个盘子空间,可放水果数加1 
    pthread_mutex_unlock(&work_mutex); //解锁 
    sleep(0.2); 
    } 
    } 
    void * thread_d(void *arg) //daughter线程 
    { 
    while(1){ 
    sem_wait(&apple); 
    pthread_mutex_lock(&work_mutex); //加锁 
    printf("女儿吃了一个苹果!(盘子当前水果总数:%d)\n", --fruitCount); 
    sem_post(&empty); 
    pthread_mutex_unlock(&work_mutex); //解锁 
    sleep(0.1); 
    } 
    } 
    int main() 
    { 
    pthread_t father; //定义线程 
    pthread_t mother; 
    pthread_t son; 
    pthread_t daughter; 
    sem_init(&empty, 0, 3); //信号量初始化 
    sem_init(&apple, 0, 0); 
    sem_init(&orange, 0, 0); 
    pthread_create(&father,NULL, thread_f,NULL); //创建线程 
    pthread_create(&mother,NULL, thread_m,NULL); 
    pthread_create(&daughter,NULL, thread_d,NULL); 
    pthread_create(&son,NULL, thread_s,NULL); 
    sleep(1); 
    sem_destroy(&empty); 
    sem_destroy(&apple); 
    sem_destroy(&orange); 
    return 0; 
    }
    
    

    按道理来说,这个题目是很简单的,但是

    对应的错误输出

    经过多次调试,在某些情况下会出现以下输出情况
    在这里插入图片描述经过多次测试,发现该bug的发生时刻虽然很不稳定,但是有以下特点:

    1. 是发生在最后两个进程结束的时候。
    2. 所有进程都有可能发生这个问题。

    接下来就记录一下整个过程的分析记录。

    分析记录

    第一阶段:怀疑是编译器问题

    在最初看到这个的时候,第一反应是在优化printf("妈妈放入一个橙子!(盘子当前水果总数:%d)\n", ++fruitCount);时编译器出现了一些bug,所以我将这段代码修改为

    fruitCount=fruitCount+2;
    fruitCount=fruitCount-1;
    `printf("妈妈放入一个橙子!(盘子当前水果总数:%d)\n", ++fruitCount); `
    

    这样就杜绝了编译器生成的对应的代码。
    但是这再次进行尝试,发现还是有这个问题

    第二阶段:怀疑实验代码有误

    排除了编译器问题后,我开始怀疑是实验代码中的信号量有问题,并且整个过程出错的并不只是这两行,还有许多行,只不过是因为打印行数过多才没有显示出来。
    这时这个错误有两个产生的充分条件:

    1. fruitCount在第二次循环中增加。
    2. 两次循环中该线程没有被中断。

    这两个调试,在调试器中不太好进行设置,为了达到实验效果,我对代码增加了一部分的调试设施。其代码如下:

    int PerVarNum[4];
    int PerThreadId;
    

    首先需要维护两个变量,一个用于保存上一趟循环中的变量值,另外一个用于保存上一个运行的线程ID。

    int fruitCount = 0;                                     //盘子中水果的数量
    
    
    void *thread_f(void *arg) //father线程
    {
        pthread_cleanup_push(cleanup,NULL);
        pthread_cleanup_pop(0);
        int test = 0;
        while (1)
        {
    
            sem_wait(&empty); //占用一个盘子空间,可放水果数减1
    
            pthread_mutex_lock(&work_mutex); //加锁
    
            fruitCount = fruitCount + 1;
            if (PerVarNum[0] == fruitCount && PerThreadId == 0)
            {
                asm("int $0x3");
            }
            PerVarNum[0] = fruitCount;
            PerThreadId = 0;
            printf("爸爸放入一个苹果!(盘子当前水果总数:%d) fruitCount);
            sem_post(&apple); //释放一个apple信号了,可吃苹果数加1
    
            pthread_mutex_unlock(&work_mutex); //解锁
            sleep(0.1);
        }
    }
    
    

    其中解释一下asm("int $0x3");这个代码,这个代码用于引起INT 3 断点异常,在引发异常之后经过系统分发,最后由调试器处理异常,所以异常会在这里停下来。
    实际上来说,这端代码是不严谨的,原因是多个线程并发对PerThreadId进行了写操作,之所以这样写的原因是在于:

    1. 个人觉得影响并不大,原因是对一个全局变量进行赋值,对应的汇编代码只会有1到2行(汇编代码是原子操作),即使在多处理器环境下,出现冲突的可能性也很小。
    2. 出现这个BUG的原因不知道是不是和线程切换相关的,如果贸然使用信号量进行控制,可能由于陷入了系统调用的原因,会影响BUG的复现。

    总而言之,在不知道BUG原因的情况下,添加调试代码的时候要尽可能小的影响到代码本身,在考虑线程的情况下,使用系统调用对于线程推进时序会有较大的影响。
    当添加完了之后,我们手工定义的断点并没有被触发,这说明算法的定义是正确的…这就比较奇怪了。

    第三阶段:排除共享变量及发现问题所在

    当确认算法无误后,还剩下一个共享变量fruitCount,这时候我在每个线程里维护了一个本地变量,用于记录调用printf的次数,并排除共享变量的干扰。

    void *thread_f(void *arg) //father线程
    {
        int test = 0;
        while (1)
        {
            int Printf_num = 0;
    
            sem_wait(&empty); //占用一个盘子空间,可放水果数减1
    
            pthread_mutex_lock(&work_mutex); //加锁
    
            fruitCount = fruitCount + 1;
            if (PerVarNum[0] == fruitCount && PerThreadId == 0)
            {
                asm("int $0x3");
            }
            PerVarNum[0] = fruitCount;
            PerThreadId = 0;
            printf("爸爸放入一个苹果!(盘子当前水果总数:%d),the Printf_num is %d\n", fruitCount, ++Printf_num);
            fflush(stdout);
            sem_post(&apple); //释放一个apple信号了,可吃苹果数加1
    
            pthread_mutex_unlock(&work_mutex); //解锁
            sleep(0.1);
        }
    }
    

    多次现场复现后出现这个现场:
    在这里插入图片描述这说明实际上prinf实际上只调用了一次,这让我想起了printf的实现细节。
    printf的实现大致流程为:向stdout文件写入字符串->调用fflush(stdout)刷新缓存
    涉及到文件的操作,就涉及到文件资源的管理,如果在进程结束时资源回收机制回收资源时会将残余在stdout中的数据流输出,就会出现两个一模一样的输出,而且这两个输出一定位于输出的尾部,这和bug中的情况很像。

    bug的确认及尝试修复

    为了确认这个BUG的原因,可以利用atexit这个函数在进程结束时打印一个字符串,用于观察。经过多次尝试,终于发现了如下情况。
    在这里插入图片描述除了上述情况外,还有下面这个情况也是意外的发现,由于在finish之后进程已经结束了,所以尽管看着这个线程并没有出现bug,但是实际上下图中的最后一行和第一行是同一个循环中打印出来的。
    在这里插入图片描述总体来说,这个bug的原因已经搞清楚了,就是由于进程结束时的资源回收导致的将残留在stdout文件中的字符流输出。
    当搞清楚了这个bug后,我尝试进行修复,尝试利用pthread_cleanup_push(cleanup,NULL)pthread_cleanup_pop(0);这两个函数设置线程退出回调函数对缓冲区进行清空,但是发现没有效果。利用atexit设置进程退出回调函数也没有效果。如果真的要修复这个函数的话,可能需要对Ubuntu的printf这个函数源码阅读,这个时间成本很高,所以就不进行了。

    总结

    总体来说,这个BUG的产生有两个原因:

    1. printf的实现有一定BUG;
    2. 由于线程的被迫退出,造成了一些不可预期的问题;

    另外这个BUG的原因找了这么长时间,主要是因为:

    1. 通过表现形式很难猜到BUG的本质
    2. 这个BUG涉及到了操作系统的资源回收和printf这个函数的实现流程,这些冰山下的东西也是很难找到的。
    展开全文
  • 文章目录实验内容实验源码实验结果结果分析 实验内容 理解请求分页式虚拟存储管理方案中的页面置换算法,理解缺页中断率的概念并可以进行正确地统计。具体要求如下: 使用数组存储一组页面请求,页面请求的数量要50...

    实验内容

    理解请求分页式虚拟存储管理方案中的页面置换算法,理解缺页中断率的概念并可以进行正确地统计。具体要求如下:

    1. 使用数组存储一组页面请求,页面请求的数量要50个以上,访问的页面号可以用随机数生成(0~20):
    2. 设置为分配给进程的页框数(假定是5),使用LRU算法,模拟完成全部的页面请求,最后输出总共发生了多少次缺页;重新设置页框为10,模拟过程,完成输出,观察页框数量对缺页中断率的影响;
    3. 在相同页框的情况下,使用FIFO算法模拟全部的页面请求,以此来比对FIFO和LRU之间的差别。

    实验源码

    #include<iostream>
    constexpr int PageTableLength = 20;//页表长度
    constexpr int TotalFream = 5;//页框长度
    
    //关于随机数生成频率的设置
    constexpr int Frame_Locality_Frequence = 90;
    constexpr int Frame_Locality_Begin = 5;
    constexpr int Frame_Locality_End = 8;
    //关于随机数生成频率的设置
    
    
    struct PageItem
    {
    	int PageFrame;
    	bool IsValid;
    	bool IsWritten;
    };
    
    //使用Rdrand指令提供的真随机数,该指令是Inter的IA32和AMD64架构特有的,并且需要在Ivy Bridge微架构之后该条指令才加入Inter标准,
    //标准的使用用法应当判断架构类型和微架构类型,此处省略
    unsigned int GetRand() {
    	__asm {
    		RdRand eax
    	}
    }
    
    //提供具有局部性的随机数,特点是返回的随机数中以MostFrequence的频率分布在BeginMostFrame至EndMostFrame中间
    //参数需要满足:BeginFrame<BeginMostFrame<EndMostFrame<EndFrame
    int Get_RequestFrame(int MostFrequence, int BeginMostFrame, int EndMostFrame, int BeginFrame, int EndFrame) {
    	if (GetRand() % 100 < MostFrequence) {
    		return GetRand() % (EndMostFrame - BeginMostFrame) + BeginMostFrame;
    	}
    	else {
    		if (GetRand() % 100 < 50) {
    			return GetRand() % (BeginMostFrame - BeginFrame) + BeginFrame;
    		}
    		else {
    			return GetRand() % (EndFrame - EndMostFrame) + EndMostFrame;
    		}
    	}
    }
    
    
    //本次实验采用了空间换时间策略,使用了反向页表
    
    //MMU类模拟地址转换的一部分内容,提供基础的数据结构
    class MMU
    {
    public:
    	void Run(int LoopNum,int FrameLocality=0) {
    		for (int loop_1 = 0; loop_1 < LoopNum; loop_1++)
    		{
    			unsigned int ThisLoop_RequestFrame;
    			if (FrameLocality) {
    				ThisLoop_RequestFrame = Get_RequestFrame(Frame_Locality_Frequence, Frame_Locality_Begin, Frame_Locality_End, 0, PageTableLength);
    			}
    			else {
    				ThisLoop_RequestFrame = GetRand() % PageTableLength;
    			}
    			if (PageTable[ThisLoop_RequestFrame].IsValid == 0) {
    				if (Used_Frame_Num < TotalFream) {
    					CallIn_Algorithm(ThisLoop_RequestFrame);
    					Used_Frame_Num++;
    				}
    				else {
    					Swap_Algorithm(ThisLoop_RequestFrame);
    				}
    				Page_Fault++;
    			}
    			Statistics_Algorithm(ThisLoop_RequestFrame);
    		}
    	}
    	void virtual Swap_Algorithm(int RequestFrame) { ; }
    	void virtual Statistics_Algorithm(int RequestFrame) { ; }
    	void virtual CallIn_Algorithm(int RequestFrame) {
    		Reversed_PageTable[Used_Frame_Num] = RequestFrame;
    		PageTable[RequestFrame].IsValid = 1;
    		PageTable[RequestFrame].PageFrame = Used_Frame_Num;
    	}
    	int Page_Fault=0;
    	int  Used_Frame_Num=0;
    	int Reversed_PageTable[TotalFream];
    
    
    	PageItem PageTable[PageTableLength];
    
    	MMU() {
    		for (int i = 0; i < PageTableLength; i++)
    		{
    			PageTable[i].IsValid = 0;
    			PageTable[i].IsWritten = 0;
    			PageTable[i].PageFrame = 0;
    		}
    	}
    };
    
    //MMU类模拟地址转换的一部分内容,提供基础的数据结构
    
    class FIFO:public MMU
    {
    public:
    	int Longest_PageFrame = 0;
    	void Swap_Algorithm(int RequestFrame) {
    		PageTable[Reversed_PageTable[Longest_PageFrame]].IsValid = 0;
    		PageTable[RequestFrame].IsValid = 1;
    		PageTable[RequestFrame].PageFrame = Longest_PageFrame;
    		Reversed_PageTable[Longest_PageFrame] = RequestFrame;
    		Longest_PageFrame = (Longest_PageFrame + 1) % TotalFream;
    	}
    	
    };
    
    class LRU:public MMU
    {
    public:
    	LRU() {
    		for (int i = 0; i < PageTableLength; i++)
    		{
    			Counter_PageNum[i] = 0;
    		}
    	}
    	int Counter_PageNum[PageTableLength];
    	void Swap_Algorithm(int RequestFrame) {
    		int Min = -1;
    		int Min_Index = 0;
    
    		for (int loop_1 = 0; loop_1 < TotalFream; loop_1++)
    		{
    			if (Counter_PageNum[Reversed_PageTable[loop_1]] > Min)
    				Min_Index = loop_1;
    		}
    
    		PageTable[RequestFrame].IsValid = 1;
    		PageTable[RequestFrame].PageFrame = Min_Index;
    		PageTable[Reversed_PageTable[Min_Index]].IsValid = 0;
    		Counter_PageNum[RequestFrame] = 0;
    		Reversed_PageTable[Min_Index] = RequestFrame;
    
    	}
    	void Statistics_Algorithm(int RequestFrame) {
    		Counter_PageNum[RequestFrame]++;
    	}
    };
    
    
    
    int main() {
    	FIFO _FIFO;
    	LRU  _LRU;
    	int RequestNum = 10000;
    	_FIFO.Run(RequestNum,1);
    	printf("FIFO算法运行%d次发生缺页的次数为%d,缺页发生率为:%.2f\n", RequestNum, _FIFO.Page_Fault,_FIFO.Page_Fault/(RequestNum*1.0));
    	_LRU.Run(RequestNum,1);
    	printf("LRU算法运行%d次发生缺页的次数为%d,缺页发生率为:%.2f\n", RequestNum, _LRU.Page_Fault, _LRU.Page_Fault / (RequestNum*1.0));
    }
    
    

    需要注意的是在LRU算法中并没有使用效率最高的老化算法,原因是在模拟中并不需要考虑LRU算法的实现效率问题。
    另外还需要解释一下Get_RequestFrame这个函数,该函数能够生成符合程序局部性原理的随机数集合,实现思路也并不复杂:生成一个(0,100)范围内的随机数来决定当前生成数的范围。其生成的频率分布图大致如下所示(画工太渣,不要介意):
    在这里插入图片描述

    实验结果

    在10000次请求中,实验结果如下所示:

    页框数 缺页次数 缺页率 请求次数
    3 2745 0.27 10000
    3 1769 0.18 10000
    5 1640 0.16 10000
    5 851 0.09 10000
    10 725 0.07 10000
    10 458 0.05 10000

    对应的结果图片如下所示:

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    结果分析

    从表格可以看到,在程序集中访问率为90%的情况下,能够较好的还原出课堂上的统计结果:

    • 页框数越少,缺页率越严重,两种算法的差距越明显;
    • 当页框数增加到一定程度后,两种算法的差距将可以忽略;

    在这里插入图片描述

    展开全文
  • 南京大学操作系统学习笔记 第三课 多处理器编程 1.Non-deterministic Polynomial Complete NP难的问题 2.线程共享那些部分 a)全局变量 b)Foo函数的代码共享 3.线程独享的部分 a)堆栈、寄存器独享 4.写了malloc以后...

    南京大学操作系统学习笔记
    第三课 多处理器编程
    1.Non-deterministic Polynomial Complete NP难的问题
    2.线程共享那些部分
    a)全局变量
    b)Foo函数的代码共享
    3.线程独享的部分
    a)堆栈、寄存器独享
    4.写了malloc以后需要判断是否分配成功
    a)Assert()是偷懒的方法
    b)判断是否为NULL
    5.实验得到pthread库给每个线程分配8M内存
    6.C语言问题 (之前一直有,但是一直没深究)
    a)_tmain( int argc ,_TCHAR *ARGV[])
    argc: 表示程序运行时命令行的个数
    *argv: 指令字符串数组的指针,每个字符串对应一个参数
    Argc[0]: 表示程序的路径及名称
    Argv[1],argv[2]: 表示自己输入的参数
    7.__attribute__介绍
    attribute是GNU C特色之一,在iOS用的比较广泛.系统中有许多地方使用到. attribute可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)等.
    ·函数属性
    ·noreturn
    · noinline
    ·always_inline
    ·pure
    ·const
    ·nothrow
    ·sentinel
    ·format
    · format_arg
    ·no_instrument_function
    ·section
    ·constructor
    ·destructor
    · unused
    ·deprecated
    ·weak
    ·malloc
    ·alias
    ·warn_unused_result
    ·nonnull
    详细见https://www.jianshu.com/p/29eb7b5c8b2d
    在这里用到的是__attribute((constructor)) 表示在main之前执行,attribute((destructor)) 表示在main()执行结束后执行,也可以用attexit()
    8.原子性
    Dekker’s algorithm——一个实现锁的算法
    是实现锁的一种算法。这种算法只解决两个线程竞争资源的情况。为了不让两个线程同时执行一段代码,比较直观的方案是设一个共享bool变量,线程进入前先检测它的值,有线程进入后改变它的值。但是在并发的情况下,可能在A检查完认为B没进入,准备改备值前,B也检测完了,这时候就两个线程都进入了。为了解决这个问题AB应该能够知道对方的情况,所以把一个BOOL变量变成两个BOOL变量,分别表示AB是否已经到这个区域。这时候怎么决定谁先继续走下去呢?要增加一个BIT,等于0表示A,等于1表示B,A,B进入后都把这个值改成它们对应的值,在决定往下走前,再回头检查一下这个值,看有没有被别人改了,如果被对方改了,则让对方先走。如果没被对方改,则认为自己仍然应该走,但是在对方放弃之前,不继续往下走。这时候会不会两个人都不往下走呢?不会的,因为那个BIT不是0就是1,所以总有一个人放弃的。也有一种可能是对方已经进入临界区,这时候对方就会一直执行到结束才修改自身状态,另一个线程才能往下走。
    伪代码
    flag[0] = 0
    flag[1] = 0

    P0: flag[0] = 1 P1: flag[1] = 1
    turn = 1 turn = 0
    while (flag[1] && turn==1); while (flag[0] && turn == 0); //Busy test loop
    // critical section // critical section
    … …
    // end of critical section // end of critical section
    flag[0] = 0 flag[1] = 0
    这个算法的好处是,不用硬件配合。当然局限也是很明显的,只支持两个线程。另外这是一个很古老的算法,在现在某些CPU架构下是不能工作的。

    展开全文
  • 这次的实验代码都给了,需要做的事情是实现简单的生产者-消费者模型,这部分的原理也很简单,所以也就不重新造轮子了。本来这次实验的目的也是想让我们了解一下Windows内核对象的一些背景知识、WINAPI的相关调用过程...

    前言

    这次的实验代码都给了,需要做的事情是实现简单的生产者-消费者模型,这部分的原理也很简单,所以也就不重新造轮子了。本来这次实验的目的也是想让我们了解一下Windows内核对象的一些背景知识、WINAPI的相关调用过程、DLL的创建过程及如何在主函数中调用DLL。

    项目拓扑

    这里说一句题外话,原来Visual Studio是可以直接打开VC的项目的…之前还傻傻的复制粘贴。接下来就是项目的拓扑。
    在这里插入图片描述
    可以看到,整个项目分为三个模块,分别对应着Producer、Customer、SharedDll这三个文件。

    Producer和Customer模块

    这两个模块相似度非常高,主函数的代码:

    int _tmain(int argc, _TCHAR* argv[])
    {
    
    	
    	int aData = 0;
    
    	while( 1 )
    	{
    		++aData;
    		
    		WriteBuffer(aData);
    
    		printf("write %d  to the buffer.....\n", aData);
    
    		
    	}
    
    
    	return 0;
    }
    
    

    两个模块的不同在于WriteBuffer函数和ReadBuffer函数,其他的细节完全相同。另外这两个模块的dll库的导入采用导入表方式导入,而不是采用LoadLibrary动态加载,对应的IDE的设置应该是项目下的引用。
    在这里插入图片描述

    ShareDll模块

    这个模块才是重点,我们首先看一下由三个模块共同引用的ShallDllLib.h这个头文件

    ShareDllLib.h

    该头文件的主要作用是定义一个用于导入、导出的符号__SHAREDLLLIB,具体的实现代码为这一小段

    
    #ifdef  __SHAREDLLLIB
    
    #else
    #define __SHAREDLLLIB   extern "C" __declspec(dllimport)
    
    
    #endif
    

    这段通过判断是否define了__SHAREDLLLIB来定义不同的__SHAREDLLLIB符号,具体来说,当Customer模块或者Producer模块调用该头文件时,__SHAREDLLLIB被定义为extern "C" __declspec(dllimport)。而当ShareDll模块调用该头文件时,__SHAREDLLLIB被定义为extern "C" __declspec(dllexport)。两者的主要区别在于一个是告诉编译器这个函数(变量)是导入的,另一个告诉编译器是导出的。
    使用extern "C"的原因是在于C++会允许源码中的函数重载,但是实际上函数重载的实现机理是编译器对函数的重命名,如将int a(int,int)这样的函数声明修改为int_a_int_int,不同的编译器实现细节不同,这会导致按名索引时出现麻烦,而使用了extern "C"实际上强制编译器使用统一的C规范,同时这也会导致函数无法重载。
    接下来头文件中通过调用这个符号来定义了三个需要导入、导出的符号。

    __SHAREDLLLIB LONG glInstanceCounter;
    
    __SHAREDLLLIB int WriteBuffer( int & aData);
    __SHAREDLLLIB int ReadBuffer( int  & aData);
    

    ShareDll.cpp

    这个cpp文件中,主要是做了建立一个共享段,在共享段里面分配了一些数据结构,实现了ReadBufferWriteBuffer的接口、实现了DllMain函数来告诉使用者Dll库已经加载。
    建立共享段的例程为:

    #pragma data_seg("DvShareData")//定义一个自定义的内存段
    
    //for count the instance
    LONG glInstanceCounter    = 0;
    
    #pragma data_seg()
    
    //to allocate the glob buffer in the DvShareData section
    __declspec(allocate("DvShareData")) class CRbuffer<int, 7, 1> gBuffer;
    
    
    //to share the section DvShareData
    #pragma comment(linker, "/SECTION:DvShareData,RWS")
    

    其中的#pragma的作用分别为:

    1. #pragma data_seg("xxx")表示建立一个自定义的数据段名称(一般情况下数据段名称为DATA),这个编译选项需要和#pragma data_seg()成对使用,夹在中间的是这个内存段中的数据结构。
    2. __declspec(allocate("xxx")) class CRbuffer<int, 7, 1> gBuffer;则表示在xxx内存段中分配一个数据结构,这里是CRbuffer的一个模板类的对象。
    3. #pragma comment(linker, "/SECTION:DvShareData,RWS")则是添加了一个链接器参数/SECTION:DvShareData,RWS,表示将该内存段设置为RWS,即可读写、共享;

    而在ShareDll这个动态库中的DllMain函数主要作用是显示有几个进程加载了ShareDll。
    DllMain函数是动态库中在动态库初始化、写在动态库时由Windows调用的回调函数,一般用于初始化、回收一些资源。该函数有四个调用原因,分别是进程初始化(DLL_PROCESS_ATTACH)、线程初始化(DLL_THREAD_ATTACH)、线程卸载(DLL_THREAD_DETACH)、进程卸载(DLL_PROCESS_DETACH)1。另外需要注意的是,在DllMain中有一些函数是不可以被调用的,否则会造成死锁。

    Rbuff.h

    最重点的是这个头文件,该头文件定义了一个CBuffer类模板,该类模板实现了生产者-消费者模型,在这个类的构造函数中,通过调用CreateMutexCreateSemaphore函数来定义了两个互斥体对象和两个信号量对象,另外一提点,在VC中CreateMutexCreateMutexA的别名。这两个函数的API接口分别为:

    HANDLE CreateMutexA(
      LPSECURITY_ATTRIBUTES lpMutexAttributes,
      BOOL                  bInitialOwner,
      LPCSTR                lpName
    );
    
    HANDLE CreateSemaphoreA(
      LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
      LONG                  lInitialCount,
      LONG                  lMaximumCount,
      LPCSTR                lpName
    );
    

    其中的参数意义是:

    • 两者共有的第一个参数是一个安全令牌的指针,当为NULL时,该内核对象使用创建者的令牌或者是模拟令牌,并且不可以被子进程继承。
    • Mutex的第二个参数为ture时,创建者线程获取刚刚创建好的内核对象的所有权。2
    • CreateSemaphoreA的第二和第三参数分别表示信号量模型的初始化数量和最大数量。
    • 而最后一个参数则是表示内核对象的命名。如果在内核的名字空间中已经存在的话,创建函数失败,可以调用GetLastError函数获取错误的原因,另外,该函数可以为空。2
      另外在Windows中信号的信号发送机制是当资源拥有量大于0时,即向队列的第一个进程发送信号。3

    ReadBuffer函数和WriteBuffer函数的结构基本上是一致的,他们的大致逻辑为:

    1. 调用WaitForSingleObject函数等待进入临界区;
    2. 在临界区中向缓冲区写入/取出一个Buffer;
    3. 调用ReleaseMutexReleaseSemaphore来释放自己的互斥体/信号量;

    WaitForSingleObject的函数声明为:

    DWORD WaitForSingleObject(
      HANDLE hHandle,
      DWORD  dwMilliseconds
    );
    

    第一个为互斥体的句柄,第二个参数为超时时间,如果为0的话,该函数总是不会阻塞,如果是INFINITE的话(INFINITE的值为0xffffffff),该函数总是等待直到句柄的对象被信号通知。4
    ReleaseMutex的函数接口为:

    BOOL ReleaseMutex(
      HANDLE hMutex
    );
    

    该函数有以下几点注意事项5

    1. 该函数必须被拥有互斥体的线程所使用,如果该线程没有互斥体,则返回错误;
    2. 一个线程可以通过Wait functions来判断自己是否拥有互斥体;
    3. 一旦一个线程从Wait functions的调用中返回时,该线程已经默认获取了对应互斥体的所有权限;

    ReleaseSemaphore函数和上述大致一致。


    1. MSDN:https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain ↩︎

    2. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexa ↩︎ ↩︎

    3. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createsemaphorew ↩︎

    4. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject ↩︎

    5. MSDN:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-releasemutex ↩︎

    展开全文
  • 这次的实验要求写一个文件管理系统,使用FAT格式存储文件,但是我想既让要写,那就写好一点吧,所以这次实验,我的最终目的是创建一个虚拟硬盘,并编写一个符合UEFI规范的“驱动程序”来格式化这个硬盘。UEFI规范...
  • 实验环境 Ubuntu 18.04 Visual Code 实验记录 实验一:观察进程标识符 所用代码如下: #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { //pid_t是数据类型,...
  • 文章目录实验环境实验记录第一步:单次计算π\piπ值第二步:多次计算π\piπ值数据处理及分析关于rand和rand_s函数的两个小实验 实验环境 Windows10 Ubuntu 18.04 VisualCode 实验记录 第一步:单次计算π\piπ值...

空空如也

空空如也

1 2 3 4
收藏数 68
精华内容 27
关键字:

南京大学操作系统实验