Framist's Little House

◇ 自顶而下 - 面向未来 ◇

0%

【操作系统概念 - 作业 3】Operating-System Structures

Operating System Concepts Exercises 3

Operating-System Structures

操作系统作业 3

  • “uptime” command
  • Code Examples of Message queue (POSIX, System V)
  • 3.1, 3.5
  • 3.8, 3.9, 3.12, 3.13, 3.14, 3.17

每题最后一个引用块是老师提供的参考答案

uptime command

参考:uptime 命令,Linux uptime 命令详解:查看 Linux 系统负载信息 - Linux 命令搜索引擎 (wangchujiang.com)

查看 Linux 系统负载信息

uptime 命令 能够打印系统总共运行了多长时间和系统的平均负载。uptime 命令可以显示的信息显示依次为:现在时间、系统已经运行了多长时间、目前有多少登陆用户、系统在过去的 1 分钟、5 分钟和 15 分钟内的平均负载。

1
uptime(选项)

使用 uptime 命令查看系统负载:

1
2
3
4
5
[root@LinServ-1 ~]# uptime -V    #显示 uptime 命令版本信息
procps version 3.2.7

[root@LinServ-1 ~]# uptime
15:31:30 up 127 days, 3:00, 1 user, load average: 0.00, 0.00, 0.00

显示内容说明:

1
2
3
4
15:31:30             # 系统当前时间
up 127 days, 3:00 # 主机已运行时间,时间越大,说明你的机器越稳定。
1 user # 用户连接数,是总连接数而不是用户数
load average: 0.00, 0.00, 0.00 # 系统平均负载,统计最近 1,5,15 分钟的系统平均负载

那么什么是系统平均负载呢?系统平均负载是指在特定时间间隔内运行队列中的平均进程数。

如果每个 CPU 内核的当前活动进程数不大于 3 的话,那么系统的性能是良好的。如果每个 CPU 内核的任务数大于 5,那么这台机器的性能有严重问题。

如果你的 linux 主机是 1 个双核 CPU 的话,当 Load Average 为 6 的时候说明机器已经被充分使用了。

Code Examples of Message queue (POSIX, System V)

POSIX

引用课本上的例子:

Producer process illustrating POSIX shared-memory API.

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
#include <stdio.h>
#include <stlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main()
{
/* the size (in bytes) of shared memory object */
const int SIZE = 4096;
/* name of the shared memory object */
const char *name = "OS";
/* strings written to shared memory */
const char *message_0 = "Hello";
const char *message_1 = "World!";
/* shared memory file descriptor */
int shm_fd;
/* pointer to shared memory obect */
void *ptr;
/* create the shared memory object */
shm_fd = shm_open(name, O_CREAT | O_RDRW, 0666);
/* configure the size of the shared memory object */
ftruncate(shm_fd, SIZE);
/* memory map the shared memory object */
ptr = mmap(0, SIZE, PROT_WRITE, MAP_SHARED, shm_fd, 0);
/* write to the shared memory object */
sprintf(ptr, "%s", message_0);
ptr += strlen(message_0);
sprintf(ptr, "%s", message_1);
ptr += strlen(message_1);
return 0;
}

Producer process illustrating POSIX shared-memory API.

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
#include <stdio.h>
#include <stlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main()
{
/* the size (in bytes) of shared memory object */
const int SIZE 4096;
/* name of the shared memory object */
const char *name = "OS";
/* shared memory file descriptor */
int shm_fd;
/* pointer to shared memory obect */
void *ptr;
/* open the shared memory object */
shm fd = shm open(name, O_RDONLY, 0666);
/* memory map the shared memory object */
ptr = mmap(0, SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
/* read from the shared memory object */
printf("%s", (char *)ptr);
/* remove the shared memory object */
shm unlink(name);
return 0;
}

但不能成功运行

System V

参考:使用 System V message queue 实现进程间通信 – 肥叉烧 feichashao.com

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
// C Program for Message Queue (Writer Process)
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

// structure for message queue
struct mesg_buffer {
long mesg_type;
char mesg_text[100];
} message;

int main()
{
key_t key;
int msgid;

// ftok to generate unique key
// 每个程序都可以用相同的 pathname 和 proj_id 来获取相同的 key.
key = ftok("progfile", 65);

// msgget creates a message queue
// and returns identifier
msgid = msgget(key, 0666 | IPC_CREAT);
message.mesg_type = 1;

// 获取用户输入
printf("Write Data : ");
gets(message.mesg_text);

// msgsnd to send message
msgsnd(msgid, &message, sizeof(message), 0);

// display the message
printf("Data send is : %s \n", message.mesg_text);

return 0;
}
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
// C Program for Message Queue (Reader Process)
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>

// structure for message queue
struct mesg_buffer {
long mesg_type;
char mesg_text[100];
} message;

int main()
{
key_t key;
int msgid;

// ftok to generate unique key
// 使用跟 writer 相同的 pathname 和 proj_id 来获取 key.
key = ftok("progfile", 65);

// msgget creates a message queue
// and returns identifier
msgid = msgget(key, 0666 | IPC_CREAT);

// msgrcv to receive message
msgrcv(msgid, &message, sizeof(message), 1, 0);

// display the message
printf("Data Received is : %s \n",
message.mesg_text);

// to destroy the message queue
msgctl(msgid, IPC_RMID, NULL);

return 0;
}

也不能成功运行……

==TODO==马上就要交作业了,就先放在这里吧

Practice Exercises

3.1, 3.5

3.1 Using the program shown in Figure 3.30, explain what the output will be at LINE A.

Figure 3.30 What output will be at Line A?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int value = 5;
int main() {
pid_t pid;
pid = fork();
if (pid == 0) { /* child process */
value += 15;
return 0;
}
else if (pid > 0) { /* parent process */
wait(NULL);
printf("PARENT: value = %d", value); /* LINE A */
return 0;
}
}

答:

output:

1
PARENT: value = 5%             

子进程改变的是子进程的 value 值,当控制权给回父进程之后,他的值保持为 5。

Answer:

The result is still 5 as the child updates its copy of value. When control returns to the parent, its value remains at 5.

3.5 When a process creates a new process using the fork() operation, which of the following states is shared between the parent process and the child process?

  • a. Stack
  • b. Heap
  • c. Shared memory segments

a 和 b 由子进程新建

答:

Answer:

Only the shared memory segments are shared between the parent process and the newly forked child process. Copies of the stack and the heap are made for the newly created process.

Exercises

3.8, 3.9, 3.12, 3.13, 3.14, 3.17

3.8 Describe the differences among short-term, medium-term, and long-term scheduling.

答:

长期调度决定哪些进程进入到系统中。

中期调度决定进入到系统中的进程哪些可以竞争处理器,即哪些进程可以进入到就绪队列。

短期调度决定将处理器分配给就绪队列中的哪些进程。

上面其实原指高级调度、中级调度、低级调度

Answer:

Short-term (CPU scheduler): selects a process from those that are in memory and ready to execute, and allocates the CPU to it.

Medium-term (memory manager): selects a process from the ready or blocked queue and swaps them from memory to disk, then swaps them in later to continue running.

Long-term (job scheduler): determines which jobs are brought into memory from back store on disk for processing.

A primary difference is in the frequency of their execution.

短期(CPU 调度器):从内存中准备执行的进程中选择一个进程,并将 CPU 分配给它。

中期(内存管理器):从准备好的或阻塞的队列中选择一个进程,把它们从内存中换到磁盘上,然后再把它们换进去继续运行。

长期(作业调度器):决定哪些作业从磁盘上的回存带入内存进行处理。

一个主要的区别在于其执行的频率。

3.9 Describe the actions taken by a kernel to context-switch between processes.

答:

内核在进行进程上下文切换时,首先将当前进程的上下文保存在内存中的 PCB 中,然后再将下一个进程在内存中的 PCB 里的上下文读取出来。上下文包含 CPU 寄存器里的内容,堆,用户栈,内存管理信息,数据,文本。

[3]

Answer:

  1. In response to a clock interrupt, the OS saves the PC and user stack pointer of the currently executing process, and transfers control to the kernel clock interrupt handler,

  2. The clock interrupt handler saves the rest of the registers, as well as other machine state, such as the state of the floating point registers, in the process PCB.

  3. The OS invokes the scheduler to determine the next process to execute,

  4. The OS then retrieves the state of the next process from its PCB, and restores the registers. This restore operation takes the processor back to the state in which this process was previously interrupted, executing in user code with user mode privileges.

  5. 作为对时钟中断的响应,操作系统保存当前执行进程的 PC 和用户堆栈指针,并将控制权转移到内核时钟中断处理程序。

  6. 时钟中断处理程序将其余的寄存器以及其他机器状态,如浮点寄存器的状态,保存在进程 PCB 中。

  7. 操作系统调用调度器来决定下一个要执行的进程。

  8. 然后操作系统从其 PCB 中检索下一个进程的状态,并恢复寄存器。这个恢复操作使处理器回到了这个进程之前被中断的状态,以用户模式权限执行用户代码。

3.12 Including the initial parent process, how many processes are created by the program shown in Figure 3.32?

Figure 3.32 How many processes are created?

1
#include <stdio.h>#include <unistd.h>int main(){    int i;    for (i = 0; i < 4; i++)    	fork();	    return 0;}

答:

共 16 个

因为 fork 是把进程当前的情况拷贝一份

Answer:

16

3.13 Explain the circumstances under which which the line of code marked printf(“LINE J”) in Figure 3.33 will be reached.

Figure 3.33 When will LINE J be reached?

1
#include <sys/types.h>#include <stdio.h>#include <unistd.h>int main(){	pid_t pid;	/* fork a child process */	pid = fork();	if (pid < 0) { /* error occurred */		fprintf(stderr, "Fork Failed");		return 1;	} else if (pid == 0) { /* child process */		execlp("/bin/ls", "ls", NULL);		printf("LINE J");	} else { /* parent process */		/* parent will wait for the child to complete */		wait(NULL);		printf("Child Complete");	}	return 0;}

答:

execlp()函数属于exec()函数族

如果execlp调用成功,进程自己的执行代码就会变成加载程序的代码,execlp()后边的代码也就不会执行了

所以无法到达printf(“LINE J”)代码

Answer:

The call to exec() replaces the address space of the process with the program specified as the parameter to exec(). If the call to exec() succeeds, the new program is now running and control from the call to exec() never returns. In this scenario, the line printf(“Line J”); would never be performed.

However, if an error occurs in the call to exec(), the function returns control and therefore the line printf(“Line J”); would be performed.

3.14 Using the program in Figure 3.34, identify the values of pid at lines A, B, C, and D. (Assume that the actual pids of the parent and child are 2600 and 2603, respectively.)

Figure 3.34 What are the pid values?

1
#include <sys/types.h>#include <stdio.h>#include <unistd.h>int main(){	pid—_t pid, pid1;	/* fork a child process */	pid = fork();	if (pid < 0) { /* error occurred */		fprintf(stderr, "Fork Failed");		return 1;	}	else if (pid == 0) { /* child process */		pid1 = getpid();		printf("child: pid = %d", pid);	  /* A */		printf("child: pid1 = %d", pid1); /* B */	}	else { /* parent process */		pid1 = getpid();		printf("parent: pid = %d", pid);   /* C */		printf("parent: pid1 = %d", pid1); /* D */		wait(NULL);	}	return 0;}

答:

A:0

B:2603

C:2603

D:2600

Answer:

A=0

B=2603

C=2603

D=2600

3.17 Using the program shown in Figure 3.35, explain what the output will be at lines X and Y.

Figure 3.35 What output will be at Line X and Line Y?

1
#include <sys/types.h>#include <stdio.h>#include <unistd.h>#define SIZE 5int nums[SIZE] = {0, 1, 2, 3, 4};int main(){	int i;	pid_t pid;	pid = fork();	if (pid == 0) {		for (i = 0; i < SIZE; i++) {			nums[i] *= -i;			printf("CHILD: %d ", nums[i]); /* LINE X */		}	}	else if (pid > 0) {		wait(NULL);		for (i = 0; i < SIZE; i++)			printf("PARENT: %d ", nums[i]); /* LINE Y */	}	return 0;}

答:

output:

1
CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16 PARENT: 0 PARENT: 1 PARENT: 2 PARENT: 3 PARENT: 4

父子进程变量不共享

Answer:

Because the child is a copy of the parent, any changes the child makes will occur in its copy of the data and won’t be reflected in the parent. As a result, the values output by the child at line X are 0, -1, -4, -9, -16. The values output by the parent at line Y are 0, 1, 2, 3, 4
因为子程序是父程序的副本,子程序的任何改变都会发生在它的数据副本中,不会反映在父程序中。因此,子代在 X 行输出的值是 0, -1, -4, -9, -16。父代在 Y 行输出的值是 0, 1, 2, 3, 4


注:

翻译:deepl

参考资料:

[1] zhousiyu369 的博客-CSDN 博客

[2] Operating System Concepts – 9th Edition 及其答案