注!!!本文大量抄袭这个帖子:深大操作系统实验二:处理机调度_处理机调度实验_AkagiSenpai的博客-CSDN博客
连名字都是一样的。
没错,这就是深大人的态度,懒惰而无知。感谢AkagiSenpai大神。
试验目的
加深对进程调度的直观认识;
掌握Linux操作系统中调度信息的查看方法;
掌握Linux中CFS和RT调度的API;
实验内容
可以使用Linux或其它Unix类操作系统;
学习该操作系统提供的进程、线程创建的函数使用方法;
利用该操作系统提供的进程间同步的信号量,线程间同步的互斥量使用方法。
实验环境
硬件:桌面PC
软件:Linux 或其他操作系统
实验步骤及说明
阅读实验辅助材料,学会解读以下调度相关的信息:top命令输出的多核及调度相关信息、ps查看到的调度相关信息、/proc/cpuinfo、/proc/PID/sched、/proc/PID/status、/proc/PID/stat、/proc/sched_debug、/proc/schedstat。
借助google工具查找资料(深大爷送个梯子),学习使用Linux进程间调度API,学习使用通信:管道、消息队列、共享内存的API,完成以下实验操作
操作部分:
- 在一个空闲的单核Linux系统上用nice命令调整两个进程的优先级(20%)
要求:使得它们各自使用约1/5和4/5的CPU资源。用top和/proc/PID/sched展示各进程使用的调度策略以及调整前后的优先级变化,用top命令展示CPU资源分配的效果。
将cpu数量限制在1核(不然两个进程无法各自使用1/5和4/5):
写死循环代码:
将其编译为P1 和P2可执行文件,然后加上&号在后台运行。
使用Top命令,发现P1和P2占用同样的cpu资源。
使用cat /proc/<PID>/sched发现两个进程的prio都是120.这是因为两个进程的NI都是0,prio = NI + 120, 所以两者的prio都是120.
使用renice命令将两个进程的优先级设置为-17和-11, 酌情上下调整优先级,得到差不多4/5和1/5的cpu占用。
打印两者的sched, 发现prio改变为103和109.
- 在一个空闲的双核Linux系统上启动P1/P2/P3/P4四个一直就绪不阻塞进程。(20%)
要求:使得P1/P3在第一个处理器上运行各占50%的CPU资源,P2/P4在另一个处理器上运行,各自30%和70%的CPU资源。展示并记录/proc/cpuinfo给出的系统核数,展示并记录进程在各处理器上的绑定情况,展示并记录top命令给出的CPU资源分配情况。运行第5个不阻塞进程,用top查看并记录负载均衡现象,用/proc/PID/sched展示并记录各进程在处理器核间的迁移次数。
将cpu设置为双核
生成4个可执行文件P1, P2, P3, P4
后台运行P1, P2, P3, P4.
使用top命令打印cpu占用情况,四个进程的cpu占用率相等。
打印affinity mask(亲和力掩码),3即为11,即两个cpu都可以使用。
使用-pc参数绑定进程至cpu。P1, P2 绑定cpu0, P2, P3绑定cpu1.
使用renice命令将P3的优先级设置为5.
top中查看cpu优先级,P4 和 P3各占cpu1的70%和30%左右,P2和P1各占cpu0的50%左右。
使用head -n 6 /proc/<PID>/sched打印迁移次数,此迁移只发生在绑定cpu核前。
新建进程5,后台运行。
top中发现P5抢占了cpu资源。P4和P3的cpu占用都有所下降,成为了50%和16%左右,而P1和P2也有所下降(44%)。P5的cpu占用率次于P2和P1, 优于P3.
同时,下两图发现,为绑定cpu核的P5的迁移数在不断上升。
而已绑定核心的P2迁移数不再上升了。
- 在一个空闲的单核Linux系统运行两个进程,以相同优先级的RR实时调度的进程,在上述两个进程结束前运行另一个优先级更高的FIFO进程。(20%)
要求:用top和/proc/PID/sched展示并记录各进程的调度策略和优先级,展示并记录FIFO进程抢占CPU的现象,展示并记录两个RR进程轮流执行的过程。
本部分使用单核。
RR进程代码如下(使用fork()创建两个进程):
#include <sched.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
int main() {
// create child process
fork();
// create sched_param struct, set sched_priority to 1
struct sched_param param;
param.sched_priority = 1;
int r = sched_setscheduler(getpid(), SCHED_RR, ¶m);
int max_num = (unsigned int)-1 >> 2;
int cnt = max_num;
while(1) {
if (cnt-- <= 0) {
cnt = max_num;
time_t timep;
time(&timep);
printf("PROCESS %d TIME %s",getpid(), ctime(&timep));
}
}
return 0;
}
如上文代码,新建一个sched_param调度参数,并将这个调度参数传给scheduler, 其中,将调度参数的优先级设置为1,使用SCHED_RR表示使用时间片轮转策略。
使用time.h库打印当前时间,每隔3秒左右打印一次。
由于和bash抢占进程,所以此时的终端会变得非常卡。
使用top指令查看RR进程,可以看到PR变为-2:
cat /proc/<PID>/sched, 实时调度的优先级变为了98.
FIFO进程代码:
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
int main() {
struct sched_param param;
param.sched_priority = 2;
int r = sched_setscheduler(getpid(), SCHED_FIFO, ¶m);
unsigned long long cnt = (unsigned long long)-1 >> 5;
while (cnt--){}
return 0;
}
运行FIFO(RR和FIFO都要使用超级用户来运行 使用su命令):
FIFO的/proc/<PID>/sched中的内容:
FIFO抢占了几乎所有CPU资源
RR的运行完全停止。
对FIFO进程Ctrl+C中止运行,RR才能开始打印:
可以看到,7点55到8点,5分钟内RR都没能运行。
- 设计编写以下程序,着重考虑其同步问题(40%):
- 一个程序(进程)从客户端读入按键信息,一次将“一整行”按键信息保存到一个共享存储的缓冲区内并等待读取进程将数据读走,不断重复上面的操作;
- 另一个程序(进程)生成两个进程/线程,用于显示缓冲区内的信息,这两个线程并发读取缓冲区信息后将缓冲区清空(一个线程的两次显示操作之间可以加入适当的时延以便于观察)。
- 在两个独立的终端窗口上分别运行上述两个程序,展示其同步与通信功能,要求一次只有一个任务在操作缓冲区。
要求:
使用posix信号量来完成这里的生产者和消费者的同步关系,其中消费者两个线程的互斥使用posix互斥量来实现。
运行程序,记录上述进程/线程完成通信的操作过程的截屏并给出文字说明。
通过/proc文件系统和ipcs命令查看和记录共享内存创建撤销等操作、信号量数值变化、进程阻塞的过程。
本部分使用双核。
生产者源代码:
#include <unistd.h>
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#define PRO_SEM "/producer"
#define CON_SEM "/consumer"
#define SHARED_MEM_NAME "/shared_memory"
#define BUF_SIZE 1024
int main() {
char *buffer;
buffer = malloc(BUF_SIZE * sizeof(char));
// Close the previous semaphore
sem_t* producer_semaphore = sem_open(PRO_SEM, O_RDWR, 0644, 0);
sem_unlink(PRO_SEM);
sem_close(producer_semaphore);
// open producer semaphore
producer_semaphore = sem_open(PRO_SEM, O_CREAT | O_EXCL, 0644, 0);
if (producer_semaphore == SEM_FAILED) {
printf("Failed to open producer semaphore.\n");
exit(1);
}
// clear the previous shared memory
shm_unlink(SHARED_MEM_NAME);
// open shared memory
int fd = shm_open(SHARED_MEM_NAME, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) {
printf("shmget error\n");
exit(1);
}
// ftruncate before mmap
ftruncate(fd, BUF_SIZE * sizeof(char));
// Attach the shared memory segment to the process's address space
void* shmaddr = mmap(NULL, BUF_SIZE * sizeof(char),
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (shmaddr == MAP_FAILED) {
printf("mmap error\n");
exit(1);
}
while (1) {
// read input from terminal
memset(buffer, 0, sizeof(char) * BUF_SIZE);
printf("Please enter an input: ");
scanf("%s", buffer);
// open consumer semaphore
sem_t* consumer_semaphore = sem_open(CON_SEM, O_CREAT);
if (consumer_semaphore == SEM_FAILED) {
printf("Failed to open consumer semaphore.\n");
exit(1);
}
// wait for consumer's semaphore
sem_wait(consumer_semaphore);
// put buffer into shared memory
memcpy((char *)shmaddr, buffer, BUF_SIZE * sizeof(char));
// unlock the semaphore two times
sem_post(producer_semaphore);
sem_post(producer_semaphore);
}
// unlink the semaphore
sem_unlink(PRO_SEM);
free(buffer);
return 0;
}
代码讲解:
使用sem_open()来新建一个名为”/producer”的信号量。
其中O_CREAT | O_EXCL一起使用时,sem_open有可能打开的是已经创建的信号量,这两个位可以强制创建新的信号量,若信号量已经存在,会返回SEM_FAILED创建失败。
下图创建共享内存。使用的是shm_open()函数,而不是shmget()函数. 使用这个命令时,传入shared memory 的名字(定义在SHARED_MEM_NAME中,上图即有),传入O_CREAT | O_EXCL | O_RDWR代表创建和允许读写,同时传入S_IRUSR | S_IWUSR允许用户读写。
接下来,由于没有在shm_open中声明共享内存的大小,使用ftruncate改变shm_open()得到的fd的文件大小,改变大小为buffer的大小。
随后使用mmap()函数,这是一个系统调用,将文件或设备映射到内存中。使用mmap()函数,可以得到直接可以写入的内存空间。
然后,使用循环,首先清空输入buffer, 让用户输入数据。接下来打开消费者的信号量,等待消费者的信号量释放。释放后,使用memcpy()函数,将用户输入的buffer拷贝到shmaddr也就是共享内存的地址,供消费者读取。消费者读取完后,生产者信号量释放两次(供两个线程读取)。
消费者源代码:
#include <unistd.h>
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#define PRO_SEM "/producer"
#define CON_SEM "/consumer"
#define SHARED_MEM_NAME "/shared_memory"
#define BUF_SIZE 1024
#ifndef NUM_THREADS
#define NUM_THREADS 2
#endif
// init or open a semaphore (lock)
sem_t* consumer_semaphore;
// Mutex declaration
pthread_mutex_t mutex;
void* thread_func(void* param) {
int* thread_num_ = (int *)param;
int thread_num = *thread_num_;
// open shared memory
int fd = shm_open(SHARED_MEM_NAME, O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0) {
printf("shmget error\n");
exit(1);
}
char *buffer;
buffer = malloc(BUF_SIZE * sizeof(char));
// Attach the shared memory segment to the process's address space
void* shmaddr = mmap(NULL, BUF_SIZE * sizeof(char),
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
while (1) {
// get new producer_semaphore
sem_t* producer_semaphore = sem_open(PRO_SEM, O_RDONLY, 0644);
if (producer_semaphore == NULL) {
printf("Didn't get producer semaphore\n");
exit(1);
}
sem_wait(producer_semaphore);
// mutex lock
pthread_mutex_lock(&mutex);
// read data from shared memory
memcpy(buffer, shmaddr, BUF_SIZE * sizeof(char));
// clear the data in shared memory
memset(shmaddr, 0, BUF_SIZE * sizeof(char));
// unlock semaphore and mutex
pthread_mutex_unlock(&mutex);
sem_post(consumer_semaphore);
// print the data
printf("Thread%d output:%s\n", thread_num, buffer);
}
}
int main() {
// Close the previous semaphore
consumer_semaphore = sem_open(CON_SEM, O_CREAT, 0644, 0);
sem_unlink(CON_SEM);
sem_close(consumer_semaphore);
// Create the consumer semaphore
consumer_semaphore = sem_open(CON_SEM, O_CREAT | O_EXCL, 0644, 1);
if (consumer_semaphore == SEM_FAILED) {
printf("Failed to open consumer semaphore.\n");
exit(1);
}
// create 2 threads
int thread_num[] = {1, 2};
pthread_t threads[NUM_THREADS];
for (int i = 0;i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, thread_func, &thread_num[i]);
}
// wait for threads to join
for (int i = 0;i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("Threads joined\n");
sem_unlink(CON_SEM);
return 0;
}
代码讲解:
首先,声明一个线程的互斥锁。用于两个读取线程的控制。
在主函数中,创建消费者信号量。
创建两个线程,运行两个线程, 运行void* thread_func(void* param)函数。
打开共享内存 O_RDWR代表允许读写:
接下来mmap()映射到内存空间。
使用sem_open获得producer信号量,等待producer信号量释放,互斥锁上锁,从共享内存地址读取内容,清空内容,互斥锁释放,消费者信号量释放。最后打印读取到的内容。
运行时需要link realtime库和pthread库,-lrt(shm_open) 和 -lpthread.
producer运行结果:
consumer运行结果:
使用命令观察信号量和共享内存:
使用lsof命令查看1024大小的共享内存。
需要注意的是,由于shm_open()来自mman.h库,且其行为更像是映射内存,并不是SystemV的IPC共享内存,所以并不能用ipcs命令来查看共享内存。
实验心得
1.很多POSIX的API都deprecated了,比如sem_init()和shm_get()函数,所以一定要持续学习持续迭代。
2.使用信号量来进行进程间通信。
3.使用共享内存在进程间共享数据。
4.使用sem_open(), sem_unlink(), sem_wait()等函数进行信号量的控制。
5.使用shm_open()搭配ftruncate()和mmap()函数进行共享内存的创建和使用。
6.使用renice调整命令的优先级。
7.观察/proc/<PID>/sched获得进程信息。
8.Linux和Unix是神,windows是给文科专业的同学用的。