OS实验记录

操作系统实验报告

姓名:周洪锋 学号:20009200766 上课班级:周二、周四56节

实验一:进程的建立

实验目的

  • 创建进程及子进程
  • 在父子进程间实现通信

实验内容

  • 创建进程并显示标识等进程控制块的属性信息;
  • 显示父子进程的通信信息和相应的应答信息(进程间通信机制任选)

实验要求

  • 显示创建的进程和控制参数;
  • 显示进程间关系参数。

实验设计与实现

在Linux操作系统中,可以使用fork()函数来复制一个当前进程,并作为当前进程的子进程。fork()函数在头文件<unistd.h>中,没有参数,返回值为pid_t类型。在父进程中,该函数返回值是子进程的pid,而在子进程中,该函数返回值始终为0,可以通过返回值的特点来区别父子进程。进程创建失败时,返回值为负数。

同时需要注意的是,在父进程结束时,子进程会被迫退出,所以需要在父进程的代码中调用wait()函数(在头文件<sys/wait.h>中),其原型如下

1
extern __pid_t wait (int *__stat_loc);

该函数会等待,知道子进程运行完毕,__stat__loc参数用于接收子进程退出的信息,这里我们不关心这个值,可以直接填NULL。

fork出的子进程会复制父进程的资源(实际上用的是一种类似“懒标记”法的方法,子进程会继承父进程的资源,但是当子进程修改资源的值时,会复制一份资源,不会对父进程修改),并继续执行剩余代码。

代码中用到的剩余两个函数

1
2
pid_t getpid()
pid_t getppid()

分别用于获取自己的自己的父进程的pid。

实验结果分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <unistd.h>
#include<sys/wait.h>

int main() {
int n=100;
pid_t pid=fork();
if(pid<0) //fail
{
printf("Fail to create a new process!\n");
return 0;
}
else if(pid==0) //child process
{
n++;
printf("This is the child process, pid=%hd, father process' pid=%hd\n",getpid(),getppid());
printf("n=%d\n",n);
exit(0);
}
else //father process
{
wait(NULL);
printf("This is the father process, the pid=%hd,child's pid=%hd\n",getpid(),pid);
printf("n=%d\n",n);
}

return 0;
}

代码中,使用if语句来区别父子进程。

运行结果如下p1

可以看到,父子进程可以互相显示进程控制块中的信息,且子进程中修改值并不会影响父进程。


实验二:进程间的同步

实验目的:

  • 理解进程同步和互斥模型及其应用

实验内容

  • 利用通信API实现进程之间的同步;
  • 建立司机和售票员进程,并实现他们间的同步运行。

实验要求

显示司机和售票员进程的同步运行轨迹

实验设计与实现

创建信号量

首先使用ftok函数来创建一个关键字,该函数原型如下

1
key_t ftok( char * fname, int id );

ftok函数是根据pathname和id来创建一个关键字(类型为 key_t),此关键字在创建信号量,创建消息队列的时候都需要使用。其中pathname必须是一个存在的可访问的路径或文件,id必须不得为0。失败返回值为-1。

利用创建的关键字,就可以使用semget函数来创建信号量。

1
int semget (key_t __key, int __nsems, int __semflg);

参数中,__key是上述生成的关键字,nsems是生成的信号量数量(可以产生类似数组的效果),_semflg :信号量的创建方式或权限。有IPC_CREAT,IPC_EXCL。

  • IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取。

  • IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误。

该函数返回一个int类型的值,这个值就可以看成是信号量的标识符。

初始化信号量

信号量创建完成后,需要对其值进行初始化。

1
int semctl (int __semid, int __semnum, int __cmd, ...) ;

第一个参数传入信号量的标识符,第二个参数是这个信号量集中的编号(如果在semget时的__nsems大于1,那这个参数就相当于信号量数组的索引),第三个参数是需要做的操作,这里我们使用SETVAL,即设置信号量集中的一个单独信号量的值。

最后还有一个可变参数,在这个实验中我们只需要初始化一个值,所以只要传入一个semun类型:

1
2
3
4
5
6
union semun {
int val;
struct semid_ds *buf;
unsigned short *arrary;

};

声明一个semun,改变其中的val然后传入semctl函数即可。(详见完整代码)

定义P操作和V操作

1
2
3
4
5
void P(int sem_id)
{
sembuf op = {0,-1,0};
semop(sem_id,&op,1);
}

semop用于操作信号量,可以使用资源也可以释放资源,第一个参数为信号量标识符,第二参数是一个sembuf结构体的指针,第三个参数用于指定操作的结构数量。

其中sembuf结构体定义如下

1
2
3
4
5
6
 struct   sembuf{

unsigned short sem_num;//第几个信号量,第一个信号量为0;
short sem_op;//对该信号量的操作。
short semflg;//
};

在P操作中,需要占用资源,所以sem_op设置为-1,sem_num为0,表示我们希望修改这个信号集中的第一个信号量。

1
2
3
4
5
void V(int sem_id)
{
sembuf op = {0,1,0};
semop(sem_id,&op,1);
}

V操作同理,将sem_op设置为1即可。

实验结果分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <iostream>
#include <sys/sem.h>
#include <unistd.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int sem_id1=0,sem_id2=0;

void P(int sem_id);
void V(int sem_id);

int main() {
int key1= ftok(".",36),key2=ftok(".",98);
if(key1==-1||key2==-1)
{
printf("keyFail!\n");
exit(0);
}
sem_id1=semget(key1,1,IPC_CREAT|0666);
sem_id2=semget(key2,1,IPC_CREAT|0666);
semun u1,u2;
u1.val=0;u2.val=0;
if(sem_id1==-1||sem_id2==-1)
{
printf("semFail!\n");
exit(0);
}

int flag1=semctl(sem_id1,0,SETVAL,u1);
int flag2=semctl(sem_id2,0,SETVAL,u2);
if(flag1==-1||flag2==-1)
{
printf("fail to set!\n");
exit(0);
}

pid_t pid=fork();
if(pid<0)
{
printf("Fail to create a new process!\n");

exit(0);
}
else if(pid==0) //child process
{
while(true)
{

P(sem_id2);
printf("The seller start to sell to ticker~\n");
printf("press '#stop' to stop selling:");
// getchar();
std::string s;
std::cin>>s;
printf("The seller stops selling!\n");
V(sem_id1);

}

}
else //father process
{
while(true)
{
printf("press '#stop' to stop the car:");
std::string s;
std::cin>>s;
printf("The driver stops the car\n");
V(sem_id2);
P(sem_id1);
printf("The driver ignited the car~\n");
}
}
return 0;
}

void P(int sem_id)
{
sembuf op = {0,-1,0};
semop(sem_id,&op,1);
}

void V(int sem_id)
{
sembuf op = {0,1,0};
semop(sem_id,&op,1);
}

开始时,默认汽车在行驶,当控制台输入字符串时,汽车停止,售票员开始售票;再次输入字符串,售票员停止售票,汽车启动。

在这里插入图片描述

运行结果

p2


实验三:线程进程共享数据

实验目的

了解线程与进程之间的数据共享关系。创建一个线程,在线程中更改进程中的数据。

实验内容

在进程中定义全局共享数据,在线程中直接引用该数据进行更改并输出该数据。

实验要求

显示和输出共享数据。

实验设计与实现

创建进程

1
2
3
4
int pthread_create (pthread_t *__restrict __newthread,
const pthread_attr_t *__restrict __attr,
void *(*__start_routine) (void *),
void *__restrict __arg)

这个函数用于创建一个线程,需要指定调用的函数和函数的参数。

参数:

  • 第一个参数:新建的线程ID,也就是指向的内存单元(传入的是指针),
  • 第二个参数:线程属性,默认为NULL
  • 第三个参数:可以理解为希望被执行的函数指针
  • 第四个参数:函数的参数
1
int pthread_join (pthread_t __th, void **__thread_return);

当进程结束时,子进程也会被迫结束,所以需要调用这个函数,阻塞进程,直到指定的进程结束。

参数:

  • 第一个参数:线程的ID
  • 第二个参数:指向字符串的指针,用于接受子进程的运行结果

实验结果分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <pthread.h>
#include <errno.h>

void *func(void* x)
{
int *p=(int*)x;
*p=100;
printf("in thread,n=%d\n",*p);
}

int main()
{
pthread_t tid;
int n=50;
printf("in main process, n=%d\n",n);
int res= pthread_create(&tid,NULL,func,(void*)&n);
if(res!=0)
{
printf("Fail to create thread! \n");
return 0;
}
pthread_join(tid,NULL);
printf("after the thread, n=%d\n",n);

return 0;
}

运行结果

p3

可以看到,子线程修改了主进程中的数据,并将结果输出。

实验四:创建进程

实验目的

学会通过基本的windows或者Linux进程控制函数,由父进程创建子进程,并实现父子进程协同工作。

实验内容

创建两个进程,让子进程读取一个文件,父进程等待子进程读取完文件后继续执行,实现进程协同工作。

进程协同工作就是协调好两个进程,使之安排好先后次序,并以此执行,可以用等待函数来实现这一点。当需要等待子进程运行结束时,可在父进程中调用等待函数。

实验要求

让父子进程按要求的顺序读取文件。

实验设计与实现

在父进程中调用fork()函数产生一个子进程,在子进程中读取文件,再在父进程中读取文件。为了保证父进程在子进程之后读取文件,所以使用wait()函数在父进程中等待子进程运行完毕后再运行接下来的代码。

在两个进程中,使用fopen等文件指针相关操作来读取文件。

实验结果分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/wait.h>


int main() {
pid_t pid;
pid=fork();
FILE *fp=fopen("/home/zhouhf/CLionProjects/os_exp4/data.txt","r");
if(fp==NULL)
{
printf("Fail to open file!\n");
exit(0);
}
if(pid==-1)
{
printf("Fail to create process!\n");
exit(0);
}
else if(pid==0)
{
//child process
char str[101];
fgets(str,100,fp);
printf("This is from child process:\n");
puts(str);
}
else // father process
{
int status;
wait(&status); //wait until child process ends
char str[101];
fgets(str,100,fp);
printf("This is from father process:\n");
puts(str);
}

return 0;
}

其中,文件data.txt的内容为如下

Hello, have a nice day!

运行结果:

p4

实验五:匿名管道通信

实验目的

学习使用匿名管道在两个进程间建立通信。

实验内容

分别建立名为Parent的单文档应用程序和Child的单文档应用程序作为父子进程,由父进程创建一个匿名管道,实现父子进程向匿名管道写入和读取数据。

实验要求

父进程要能够从子进程创建的匿名管道中读取数据,实现进程间通信。

实验设计与实现

创建管道

函数:

1
int pipe (int __pipedes[2]);

pipe函数需要一个长度为2的数组指针作为参数,创建管道完成后,会修改数组指针的值作为标识符(_pipedes[0]是读取的管道,_pipedes[1]写入的管道),当创建失败时,该函数会返回-1值。

子进程

子进程只需要写入数据,所以可以在子进程中使用close(int)函数将读取的管道关闭。

接下来,使用write函数

1
ssize_t write (int __fd, const void *__buf, size_t __n)

三个参数分别是:

  • 管道标识符
  • 缓冲区的头指针
  • 缓冲区的大小

该函数会返回成功写入的字节数(由于各种可能导致写入失败的因素,该值可能会比预期的小)

父进程

同理,在父进程中,只需要读取数据,所以使用close函数关闭写入管道,然后使用read函数从管道读取内容。

1
ssize_t read (int __fd, void *__buf, size_t __nbytes)

该函数与write函数类似,但需要注意的是第一个参数要传递读取管道的标识符,buf指针是期望读入的缓冲区。

实验结果分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstring>
#include "cstdlib"
#include "time.h"

int main() {
int _pipe[2];
char msg[]="Have a nice day!\n";
char buf[101];
int res=pipe(_pipe);
if(res==-1)
{
printf("Fail to build pipe!\n");
return 0;
}
pid_t pid=fork();

if(pid==-1)
{
printf("Fail to fork process!\n");
return 0;
}
else if(pid==0) //child process
{
close(_pipe[0]);
write(_pipe[1],msg,sizeof(msg));
exit(0);
}
else //father process
{
int status;
wait(&status);
close(_pipe[1]);
read(_pipe[0],buf,sizeof(buf));
printf("msg from child process :%s",buf);
}


return 0;
}

运行结果:

p5


OS实验记录
http://zhouhf.top/2022/05/23/OS实验记录/
作者
周洪锋
发布于
2022年5月23日
许可协议