课程设计总结
操作系统基本结束了,现在把做课程设计上遇到的一些设计、技术上的问题总结一下。
1. C++编程上的问题
在第一次操作系统课外实验的时候,由于是第一次尝试使用C++的多线程,所以在线程的控制上有一些不合理的地方。尤其在各个不同的类(在不同的源文件中)之间如何进行同步,如何通信。当时是通过在类中设置一个静态的成员变量,想着这样可以保证唯一性。而且在之前的线程尝试获取锁的操作,很多地方使用的是mutex.try_lock()。不过在课程设计中,我想用更有效的操作。使用extern 代替static ,查找到的资料上说可以减少编译时间,同时可以减少空间,因为使用static ,每一个编译单元都需要有一份;在多线程的控制上,主要使用unique_lock(),对于需要多个锁的情况,使用defer_lock延迟获取锁,之后使用std::lock()同时获取全部需要的锁。
2. 仿真设计的各个类如何进行通信
本来是打算先创建一个控制台工程,因为在控制台上显示调试比较方便。但是我想使用MFC的消息传递机制实现各个类之间的通信。但是模仿MFC工程下的创建消息映射并没有成功,最后改为创建MFC工程,使用VS的类向导创建自定义消息一级处理函数。至于为什么模仿MFC在控制台工程下无法实现消息映射的原因还不知道。
3. 函数指针
在实现存储部分的多种算法中,最先适应、最优适应等不过是按照不同的方式对空闲区链表进行排序,所以如果单独使用不同的排序函数感觉太冗余。所以打算模仿C++ STL sort 的实现方式,通过传入不同的排序逻辑实现不同的算法。这其中需要用到函数指针。具体实现是:
下面就是两个简单的排序逻辑
/**
* 用于通过参数的形式传入到链表排序函数中
* 该函数比较两个区的起始地址
* @return 返回值大于0 标志z1 的起始地址大于 z2的起始地址,小于0标识地址小
*/
int ChangeableZone::compareByoffset(zonesTable *z1, zonesTable *z2){
return z1->offset - z2->offset;
}
/**
* 用于通过参数的形式传入到链表排序函数中
* 该函数比较两个区的大小
* @return 返回值大于0 标识z1的地址更高, 相反小于0
*/
int ChangeableZone::compareByLength(struct zonesTable *z1, struct zonesTable *z2){
return z1->length - z2->length;
}
下面是创建一个函数指针comp 。我是在vs2017下实现的,comp = &ChangeableZone::CompareByOffset 那个取地址符号加与不加都可以运行。不过看了C语言的实现方式,那个符号是不加的。原本是参考的C语言的例子,不过C语言没有类,所以创建的函数指针是指向全局的函数的,所以本来我是创建一个指向类的函数成员变量的指针的,但是编译后报错,后来参考网上的资料,函数改为该类的static成员后,编译通过。bool ChangeableZone::solverequest(int requestSize, std::pair<int, int> &memResult, int flag){
//FIXME 首先测试最先适应分配算法
int(*comp) (struct zonesTable*, struct zonesTable*);
if (flag == 0)
comp = &ChangeableZone::compareByOffset;
else
comp = &ChangeableZone::compareByLength;
sortList(emptyTable, comp);
struct zonesTable *p = emptyTable;
4. 关于线程并发的测试
为了测试线程的并发性,我写了一个测试的程序,创建5个线程,分别执行同等的计算量比较大的任务,并在运算结束后输出当时的系统时间,精确到毫秒。输出结果发现有4个线程输出的时间相同,不过有一个线程输出的时间不同。可能是我的电脑是4核的,线程的并行也是基于物理硬件的。
5. 基于windows的消息传递
其实虽然在程序中多处使用了Sendmessage 、postmessage 。但是在它们的使用上仍有一些不明白的地方。比如PostMessage 查找到的资料是说发送消息的一方在消息发送后并不阻塞。但是在实际实现中发现,消息发送的一方需要等待接收方的消息处理函数执行完毕才会继续执行。所以本来我是打算在设计的顶层类通过消息传递将消息传递给各个下部模块后,在消息处理函数中启动该模块的主运行函数的。后来改为在消息处理函数中创建线程,并且传入该模块的主运行函数作为该线程的参数。并且不使用join()。在使用消息传递的时候,也尝试使用了其他的一些消息传递的函数,例如:SendNotifyMessage实现非阻塞消息传递。不过使用SendNotifyMessage有地方需要注意,就是sendNotifyMessage的目标窗口需要是由发送消息所在的线程创建的;SendMessageCallBack 发送消息需要使用回调函数,如果目标窗口再其他的线程,则立即返回,结果在回调函数中返回;windows与mfc的sendmessage是有不同的,windows的sendmessage可以发送给指定的窗口类,但是mfc的sendmessage在msdn上说发送this window 即发送给当前的窗口。
而且,最初设计实现的时候,看到windows的对话框是各自运行在不同的线程之上的,所以当初我认为可以实现C++ thread的效果,但是在测试之后发现并没有产生并发的效果。所以最后还是使用了C++ 创建线程。
6. ERROR c4430 缺少类型说明符
这个问题出现在两个头文件相互包含,并且其中一个中包含另一个类的对象使用。为了解决这个问题,需要删掉其中一个头文件的包含。这个问题的解决参考了这个博客:errorC4430: 缺少类型说明符 - 假定为 int....的一种情况的解决方法 - csdn博客
7. 窗口类创建中遇到的问题
由于在本次设计中,主要使用extern代替之前用于在不同文件之间共享变量。为了方便使用,对顶层类使用extern,即创建一个该类的实例,修饰为extern。从而可以保证系统中所有操作的一致性。不过在使用的时候,原本是直接使用Sys sys。但是运行时错误,调试发现,窗口的句柄为NULL。同时发现调用堆栈中提示的是当前进行的是窗口的动态创建。可能是因为在初始化全局变量的时候,主窗口还没有初始化,而sys已经调用了sys类的窗口构造函数(parent窗口是主对话框),所以出现了问题。最后通过将sys改为指针,在主窗口创建完毕后再进行初始化将问题解决。
8. 使用类的成员函数创建线程
一般创建线程需要类的静态方法,如果使用类的成员方法,需要使用threadmthread(&MClass::method,&mtd)。例如:
mycpuThread = std::thread(&Cpu::myCpuRun, this);
9. 创建各个窗口类中遇到的问题
在设计中,包含一个指向CLOCK 类的指针和一个指向SYS类的指针。在上面的问题解决后,想法是在SYS的构造函数的最后,通过消息传递发送给CLOCK 启动初始化完毕,准备启动的消息。不过在消息传递的时候,本来是使用的SendMessage 发送的消息,但是在运行后报错。VS调试调用堆栈中显示错误在SYS类的构造函数中,后来想到因为在SYS发送消息给CLOCK 类后,还需要发送消息给其他的类,如果使用SendMessage ,则SYS还没有发送完全部的消息,时钟类就已经将时钟中断的消息发送给了SYS类,导致了空指针问题。改为使用SendNotifyMessage后运行成功。
10. C++ 模板使用的小注意点
当使用等于号将一个模板对象(例如map、vector)赋予另一个对象的时候,只是拷贝了值,并不是传递引用。如果需要修改原值需要获取到对应的Iterator。
11. 使用宏定义打印日志
因为创建的是mfc工程,所以默认情况下是没有控制台窗口的。所以在网上找到了MFC工程显示控制台的方法(参考:MFc程序如何使用printf输出调试信息 - xhhjin的专栏 - CSDN博客)。但是当信息太多的时候,控制台前面的信息就看不到了,那篇教程中使用freopen重定向到文本,但是使用的时候,信息并没有输出到文件。后来想通过一个方便的方式输出日志信息,所以想到使用宏定义。首先是用宏定义创建一个FILE*,之后再使用一个宏定义定义LOG,但是组建的时候显示符号重定义,但是将定义从stdafx.h中剪切到一个cpp源文件中就没有问题。所以使用嵌套宏定义,不过定义FILE*的时候需要加上static,因为如果宏定义再stdafx.h中会报错。
在进行宏定义的时候,我定义了两个LOG和LOGD,分别用于输出带参数和不带参数的。因为输出带参数的,后边的参数的个数不确定,所以打算使用可变参数。在网上找到使用_VA_ARGS_,但是编译之后报错。时候查找它的头文件是什么,但是加上stdarg.h后仍然报错,最后发现是__VA_ARGS__,即前面后面都是两个下划线。
//用于打印运行日志的宏定义
//#ifndef MYLOGFILE
//#define MYLOGFILE
//static FILE *myfp = fopen("my_run_log/general_runlog.txt", "w");
//#ifndef LOGD
//#define LOGD(format, ...) {char logbuf[100];\
// sprintf(logbuf,format,__VA_ARGS__);\
// fputs(logbuf,myfp);}
//#endif // !_LOGD
//#ifndef LOG
//#define LOG(content) {char logbuf2[100];\
// sprintf(logbuf2,content);\
// fputs(logbuf2,myfp);}
//#endif // !LOG
//
//#endif // !_MYLOGFILE
至于为什么被我注释掉了,因为发现在输出的结果中,发现在多线程的环境下,有很多日志消息并没有打印出来。所以想到找一找,看一下有没有比较好的C++的日志打印的开源库可以使用。后来找到了几个开源的库,最终选择了glog,它可以在多线程的环境下很好的打印日志。(可以在GitHub上下载:https://github.com/Google/glog/releases)
Glog使用的是0.3.5版本的,在编译使用的时候遇到了问题,首先是win sdk 版本的问题,原工程的win8.1的。我的是VS2017,Win10。首先打开下载的glog0.3.5下的.sln,使用vs进行编译,组建。如果不是win8的先修改项目属性里的windows SDK版本。首先右击项目点击属性。
之后选择自己电脑上的windows SDK版本
将windows下的用于vs的头文件复制到工程的目录下,路径如下:
同时将debug文件夹下的dll 与lib也复制过去。在头文件中#include包含,#pragma comment(lib,”xxx/xxx.lib“)包含lib库。
同时在项目的属性的vc++目录中修改包含目录。最初的时候,运行的时候总是显示找不到glog.dll,但是将它复制到项目的直接文件夹下后可以运行(之前是和头文件、lib放在项目目录文件夹glog下面的)。可能是项目属性哪里没有配置
12. VS在同一个解决方案下创建两个工程遇到的问题
在解决方案下创建一个新的工程,但是编译一直显示_beginthreadex找不到。查找了网上的资料,但是加上process.h仍然显示找不到。右击打开文件竟然跳转到我自己写的Process.h文件。C++头文件竟然是不分大小写的。最后通过在VS项目属性中的VC++目录中将自己包含的目录加载$INCLUDE后面,这样在链接的时候会首先找到系统INCLUDE中的process.h
13. 不同工程头文件的包含
在VS中在一个解决方案下创建两个工程,但是在一个工程中使用另一个工程的类的时候发生LNK链接错误。http://blog.csdn.net/P3ray/article/details/76040740这篇文章中写道由于头文件的多重包含,导致链接的错误。
之后定义该类的变量的指针,但是仍然构建错误。
之后将存储部分的工程修改为lib静态库类型,并且在进程调度中添加对存储工程的引用。
14. 文件写入乱码的问题
由于VS2017平台使用的是unicode字符集,所以在使用CString 将数据写入到文件会产生乱码,因为文本文件中使用的是单字节的字符集。所以使用sprintf将信息转换为8字节的数据,在写入的时候使用std::string .c_str 输出单字符集数据,使用length 表示输出数据的长度。不过后来查找资料发现windows系统调用中有WideCharToMultiByte,可以用于将宽字节字符转换为多字节的字符串。例如:(数组为CFile数组)
15. MFC 界面制作中遇到的问题
风格设计永远不要忘了WS_VISIBLE。第一次使用CListCtrl 创建时候没有显示,打开VS的工具箱没发现有这个,我以为已经不支持了,没想到是在风格中忘了使用WS_VISIBLE了......。
还有获取主对话框的控件的正确的方式是:
之前是使用的AfxgetMainWnd()->getdlgitem获取的,但是运行时候显示访问错误,后来改为这个就没有再出现这个错误。但是再更新主对话框的数据的时候界面会卡住,所以打算通过发送消息的方式通知主UI线程更新数据,不过需要向主线程传递数据。
如果需要从其他地方向主对话框发送消息,并且需要包含消息。可以自定义一个结构体表示消息的格式,注意发送消息的时候需要使用postmessage ,如果使用snedmessage 的话会导致界面卡死。
消息处理函数接收自定义格式的消息
16. 传递自定义消息中遇到的问题
在更新界面数据过程中,首先使用的是postmessage,但是在接收端接收的时候,数据却不正确。原因在于消息中传送的是临时变量,postmessage不等消息处理函数处理完就结束了,所以得不到正确的数据,因为在发送方,临时变量的空间可能已经被回收了。所以使用sendmessage或者使用消息的指针,因为指针在delete前是不会被释放空间的。
17. CBrush 使用遇到的问题
在使用CBrush 时,由于需要用不同的颜色来标识当前页框是否占用。在初始化绘制页框之后,使用brush->createsolidbrush(rgb )。出现了错误。不过在调试的时候,显示循环中的变量是-882…。但是该变量只有在循环中才使用。后来将部分代码注释,排除,确定错误在cbrush的使用上。改为将原brush delete掉,并且重新初始化,问题解决。
相关阅读
前言:为了应付作业网上找的,看得懂但是不会写就很无奈orz,干脆就多加了写注解,并且修改了一下,这个是参考博客 一、设计内容 实现
一、实验要求 使用JSP技术建立一个简单的手机销售网。 采用MVC模式实现各个模块,数据库使用MySQL数据库 系统后台开发 1、 在导航
1 引言 1.1需求分析 滴滴打车系统的主要应用需求来自乘客、司机两个身份的用户需求。对于乘客,他可以登入系统,查看自己的信息,修
一.概述学生信息管理系统是学校管理的重要工具,是学校不可或缺的一部分。随着在校人数的不断增加,教务系统的数量也不断的上涨。学
概述 实验环境 GeekOS-0.3.0 Bochs和Vmware介绍 开发过程 编译运行 配置文件 前导知识 一、全局描述符表GDT(Global Descripto