《操作系统原理》实验报告二

一、实验目的
(1)理解操作系统线程的概念和应用编程过程;
(2)理解线程的同步概念和编程;

二、实验内容
(1)在 Ubuntu 或 Fedora 环境使用 fork 函数创建一对父子进程,分别输出各自的进程号和提示信息串。
(2)在 Ubuntu 或 Fedora 环境使用 pthread_create 函数创建2 个线程 A 和 B。线程 A 在屏幕上用 while 循环顺序递增地输出 1-1000 的自然数;线程 B 在屏幕上用 while 循环顺序递减地输出1000-1 之间的自然数。为避免输出太快,每隔 0.5 秒输出一个数。
(3)在 windows 环境下,利用高级语言编程环境(限定为 VS 环境或 VC 环境)调用 CreateThread 函数实现(2)的功能。
(4)在 windows 环境下,利用高级语言编程环境(限定为 VS 环境或 VC 环境)调用 CreateThread 函数和相关的同步函数,模拟实现“生产者-消费者”问题。
(5)在 windows 环境下,利用高级语言编程环境(限定为 VS环境或 VC 环境)调用 CreateThread 函数实现“并发地画圆和画方”。圆的中心,半径,颜色,正方形的中心,边长,颜色等参数自己确定,合适就行。圆和正方形的边界上建议取 720 个点。为直观展示绘制的过程,每个点绘制后睡眠 0.2 秒~0.5 秒。

三、实验过程
(一)实验步骤
1)fork函数创建父子进程
1.打开ubuntu上文本编辑器写c程序(problem1.c)
2.核心代码

1.	void main()  
2.	{
    
       pid_t pid;        
3.	    fprintf(stdout,"I am the first process, my pid is %d, my parent pid is %d\n",getpid(),getppid());    
4.	    pid=fork();  
5.	    if(0==pid)  
6.	        fprintf(stdout,"I am the child process, my pid value is %d, but my real pid is %d, my parent pid is %d\n",pid,getpid(),getppid());  
7.	    else  
8.	        fprintf(stdout,"I am the parent process, my child pid value is %d, my real pid is %d, my parent pid is %d\n",pid,getpid(),getppid());   
9.	}  

3.第3行要求输出当前进程的pid和他的父进程的pid,第4行的pid是fork()函数的返回值,第6和7行分别输出不同进程运行时的信息(getpid(),getppid()分别返回进程和其父进程的pid)
4.在终端上编译该程序并输出结果
#gcc problem1.c
#./a.out
5.分析结果

2)pthread_create 函数创建2 个线程(problem2.c)
1.打开ubuntu上文本编辑器写c程序
2.核心代码

10.	int main()
11.	{
    
    
12.	    pthread_t tips1;
13.	    pthread_t tips2;
14.	    int hThread1 = pthread_create(&tips1, NULL, print1, NULL);
15.	    int hThread2 = pthread_create(&tips2, NULL, print2, NULL);
16.	    if (hThread1 != 0)
17.	    {
    
    
18.	        printf("hThread1 err");
19.	    }
20.	    else if (hThread2 != 0)
21.	    {
    
    
22.	        printf("hThread2 err!");
23.	    }
24.	    void *result1;
25.	    void *result2;
26.	    pthread_join(tips1, &result1);
27.	    pthread_join(tips2, &result2);
28.	    return 0;
29.		} 
  1. pthread_create(,,,)创建线程,第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。返回值: 若成功则返回0,否则返回出错编号
    pthread_join(,)用来等待一个线程的结束。第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。
    4.在终端上编译该程序并输出结果
    #gcc problem1.c -lpthread
    #./a.out
    5.分析结果
    3)VS 环境实现(2)的功能
    1.打开VS写c程序(problem3.cpp)
    2.核心代码
30.	int main()
31.	{
    
    
32.	    HANDLE hThread[2];
33.	    //创建线程,并调用函数打印输出
34.	    hThread[0] = CreateThread(NULL, 0, print1, (LPVOID)1000, 0, NULL);
35.	    hThread[1] = CreateThread(NULL, 0, print2, (LPVOID)1, 0, NULL);
36.	    //等待所有线程结束
37.	    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
38.	
39.	    CloseHandle(hThread[0]);
40.	    CloseHandle(hThread[1]);
41.	
42.	    return 0;
43.	}
  1. 在C/C++中可以通过CreateThread函数在进程中创建线程,函数的具体格式如下:
HANDLE CreateThread(
                    LPSECURITY_ATTRIBUTES lpThreadAttributes,
                    DWORD dwStackSize,
                    LPTHREAD_START_ROUTINE lpStartAddress,
                    LPVOID lpParameter,
                    DWORD dwCreationFlags,
                    LPDWORD lpThreadID
                   );

参数的含义如下:
lpThreadAttrivutes:指向SECURITY_ATTRIBUTES的指针,用于定义新线程的安全属性,一般设置成NULL;
dwStackSize:分配以字节数表示的线程堆栈的大小,默认值是0;
lpStartAddress:指向一个线程函数地址。每个线程都有自己的线程函数,线程函数是线程具体的执行代码;
lpParameter:传递给线程函数的参数;
dwCreationFlags:表示创建线程的运行状态,其中CREATE_SUSPEND表示挂起当前创建的线程,而0表示立即执行当前创建的进程;
lpThreadID:返回新创建的线程的ID编号;
如果函数调用成功,则返回新线程的句柄,调用WaitForSingleObject函数等待所创建线程的运行结束。函数的格式如下:
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
参数的含义如下:
hHandle:指定对象或时间的句柄;
dwMilliseconds:等待时间,以毫秒为单位,当超过等待时间时,此函数返回。如果参数设置为0,则该函数立即返回;如果设置成INFINITE,则该函数直到有信号才返回。 4.调试并运行程序
5.分析结果

4)VS 环境模拟实现“生产者-消费者”问题
1.打开VS写c程序(problem4.cpp)
2. 线程并发的生产者-消费者模型:1.两个进程对同一个内存资源进行操作,一个是生产者,一个是消费者。2.生产者往共享内存资源填充数据,如果区域满,则等待消费者消费数据。3.消费者从共享内存资源取数据,如果区域空,则等待生产者填充数据。4.生产者的填充数据行为和消费者的消费数据行为不可在同一时间发生。、
3.核心代码

44.	DWORD WINAPI producer(LPVOID v)
45.	{
    
    
46.		int item;
47.		while (true)
48.		{
    
    
49.			item = produce_item();
50.			empty--;
51.			if (empty < 0)
52.				empty++;
53.			else if (mutex > 0) 
54.			{
    
    
55.				mutex--;
56.				insert_item(item); 
57.				full++;
58.				mutex++;
59.			}
60.			Sleep(2000);
61.		}
62.		return 1;
63.	}
64.	
65.	DWORD WINAPI consumer(LPVOID v)
66.	{
    
    
67.		int item;
68.		while (true)
69.		{
    
    
70.			full--;
71.			if (full < 0)
72.				full++;
73.			else if (mutex > 0)
74.			{
    
    
75.				mutex--;
76.				item = remove_item();
77.				empty++;
78.				mutex++;
79.			}
80.			Sleep(2000);
81.		}
82.		return 1;
83.	}
  1. DWORD WINAPI producer(LPVOID v), DWORD WINAPI consumer(LPVOID v)表示生产者和消费者两个线程,通过PV操作使二者有效并发运行;两线程在缓冲区互斥,定义信号量mutex=1(初值);用full和empty表示缓冲区存储情况;insert_item(item),remove_item()表示向缓冲区中存储和移除数据。
    4.调试并运行程序
    5.分析结果
    5)VS环境并发地画圆和画方
    1.打开VS写c程序(problem5.cpp),先用EasyX创建画布
    2.核心代码
	DWORD WINAPI circle(LPVOID n)
85.	{
    
    
86.	    double r = 200.0,x0=250.0,y0=250.0;//半径与圆心
87.	    int x1,x2,y;
88.	    for (y=50;y<=450;y+=6)
89.	    {
    
    
90.	        double yy = sqrt(r*r-(y-y0)*(y-y0));
91.	        x1 = x0 - yy;
92.	        x2 = x0 + yy;
93.	        outtextxy(x1,y,c);
94.	        Sleep(100);
95.	        outtextxy(x2,y,c);
96.	        Sleep(100);
97.	    }
98.	    return 0;
99.	}
100.	
101.	//画方
102.	DWORD WINAPI tangle(LPVOID n)
103.	{
    
    
104.	    int mx = 540, my = 50;//左上角
105.	    for (; my < 300; my+=10)
106.	    {
    
    
107.	        outtextxy(mx,my,c);
108.	        Sleep(100);
109.	    }   
110.	    for (; mx < 940; mx += 10)
111.	    {
    
    
112.	        outtextxy(mx, my, c);
113.	        Sleep(100);
114.	    }
115.	    for (; my > 50; my -= 10)
116.	    {
    
    
117.	        outtextxy(mx, my, c);
118.	        Sleep(100);
119.	    }
120.	    for (; mx > 540; mx -= 10)
121.	    {
    
    
122.	        outtextxy(mx, my, c);
123.	        Sleep(100);
124.	    }
125.	    return 0;}

3.在画布上将圆和矩形的边以点的形式一个个输出,通过使用EasyX上的outtestxy()函数,利用圆和矩形上的点的坐标更加方便。
4.调试并运行程序
5.分析结果

(二)解决错误和优化
1. 操作过程错误,在终端编译c语言程序时找不到相应文件。因为我把c程序保存在了桌面上,终端默认位置为主目录,所以找不到,把文件移动到主目录即可。
2. 特殊语法错误,输出程序运行结果时出错。把命令输错了,应该为#./a.out,不要忽略“.”。
3.编译错误,problem2.c无法编译。在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
在这里插入图片描述
4.特殊的语法错误,
在这里插入图片描述

当调用CloseHandle成功后,相关的内核对象的引用计数被减1。这个函数做的工作就这么多。它并没有真正的关闭内核对象,只是将计数减1,CreateThread()返回值正确即可。

5.运行错误,生产者和消费者存取数不对。两个PV操作没有合理安排顺序,导致出错。画图分析PV操作先后执行顺序。
在这里插入图片描述

6.运行错误,圆和矩形的形状发生了错误。刚开始没使用EasyX画图,使用空格来确定坐标,导致圆和矩形的空格发生错乱,导致形状出错。
在这里插入图片描述

使用EasyX创建画布后,可以直接利用画布上的坐标,从而避免错误。
7.运行错误,矩形的边长输出为空。原因是坐标之间的间隔太小,字符相互重叠,结果全部都无法显示,把字符间的距离调大即可。

四、实验结果
1)fork函数创建父子进程
在这里插入图片描述
fork()函数在不同进程上下文返回不同的值,在父进程中返回子进程的pid,在子进程中返回0,若出错则返回一个负数;
输出的第一句为当前进程和其父进程的pid,当前进程pid为18239,然后创建子进程,pid在父进程中为18240,在子进程中为0;输出的第二句为父进程输出,第三句为子进程输出。

2)pthread_create 函数创建2 个线程
在这里插入图片描述
在这里插入图片描述
由图可知,两个线程并发运行成功。

3)VS 环境实现(2)的功能
在这里插入图片描述

由图可知,两个线程并发运行成功。

4)VS 环境模拟实现“生产者-消费者”问题
在这里插入图片描述
有图像可知,生产者和消费者两者生产和消费同时执行且互不耽误,缓冲区中存取的数和被取出的数正确。

5)VS环境并发地画圆和画方
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由图可知,双线程并发地画圆和画方成功实现。

五、体会
通过本次实验,我对父子进程和多线程有了初步的了解,可以理解PV操作的意义并掌握其用法,以后的编程中可以更加巧妙地处理一些问题;但目前的熟练程度还不够,仍需多加练习。

猜你喜欢

转载自blog.csdn.net/weixin_45341339/article/details/112412788
今日推荐