《计算机操作系统》第三次实验

计算机操作系统-实验三

一、实验目的

  1. 系统调用的进一步理解。
  2. 进程上下文切换。
  3. 同步与通信方法。

二、实验题目

  1. 通过fork的方式,产生4个进程P1,P2,P3,P4,每个进程打印输出自己的名字,例如P1输出“I am the process P1”。要求P1最先执行,P2、P3互斥执行,P4最后执行。通过多次测试验证实现是否正确。
    顺序图如下:
    在这里插入图片描述
    代码如下:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include<semaphore.h>
#include<sys/types.h>
#include <sys/wait.h>


sem_t *P1_sig = NULL;
sem_t *P2_sig = NULL;
sem_t *P3_sig = NULL;

int main(int argc, char *argv[])
{
    pid_t pid;
    P1_sig = sem_open("P1_sig",O_CREAT,0666,0);
    P2_sig = sem_open("P2_sig",O_CREAT,0666,0);
    P3_sig = sem_open("P3_sig",O_CREAT,0666,0);

    pid = fork();

    if(pid < 0)
    {
        printf("进程为创建失败!");

    }


    else if(pid == 0)
    {
        //sleep(1);
        sem_wait(P1_sig);
        printf("I am the process P2\n");
        sem_post(P1_sig);
        sem_post(P2_sig);

         pid = fork();

            if(pid < 0)
            {
                printf("进程为创建失败!");
             }

            else if(pid == 0)
            {
                sem_wait(P2_sig);
                sem_wait(P3_sig);
                printf("I am the process P4\n");

            }
    }

   else
    {
        printf("I am the process P1\n");
        sem_post(P1_sig);

        pid = fork();

        if(pid < 0)
        {
        printf("进程为创建失败!");

        }

        else if(pid == 0)
        {
            sem_wait(P1_sig);
            printf("I am the process P3\n");
            sem_post(P1_sig);
            sem_post(P3_sig);
            return 0;

        }
    }

    sem_close(P1_sig);
    sem_unlink("P1 signalname");
    sem_close(P2_sig);
    sem_unlink("P2 signalname");
    sem_close(P3_sig);
    sem_unlink("P3 signalname");
    return 0;
}

运行截图及结果
在这里插入图片描述
2. 火车票余票数ticketCount 初始值为1000,有一个售票线程,一个退票线程,各循环执行多次。添加同步机制,使得结果始终正确。要求多次测试添加同步机制前后的实验效果。(说明:为了更容易产生并发错误,可以在适当的位置增加一些pthread_yield(),放弃CPU,并强制线程频繁切换,例如售票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp-1;
pthread_yield();
ticketCount=temp;
退票线程的关键代码:
temp=ticketCount;
pthread_yield();
temp=temp+1;
pthread_yield();
ticketCount=temp;

(1) 没有信号量机制,在退票Returnticket( )的temp写回前加入sched_yield();
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sched.h>
#include <semaphore.h>

int ticketCount = 1000;
sem_t *flag = NULL;

void *SellTicket()
{ 
    for(int i=100;i>0;i--)
    {
    int temp;
    printf("当前票数为:%d\n",ticketCount);
    temp = ticketCount;
    temp = temp - 1;
    ticketCount = temp;
    }
}

void *ReturnTicket()
{
   for(int i=100;i>0;i--)
    {
    printf("当前票数为:%d\n",ticketCount);
    int temp;
    temp = ticketCount;
    temp = temp + 1;
    sched_yield();
    ticketCount = temp;
    }     
}

int main()
{
    pthread_t S,R;
    flag = sem_open("flag",O_CREAT,0666,1);
    
    pthread_create(&S,NULL,SellTicket,NULL);
    pthread_join(S,NULL);
    
    pthread_create(&R,NULL,ReturnTicket,NULL);
    pthread_join(R,NULL);

    sem_close(flag);
    sem_unlink("flag");

    printf("最终票数为:%d \n",ticketCount);
    return 0;
}

运行过程及结果:
在这里插入图片描述
在这里插入图片描述
2. 加入信号量机制

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sched.h>
#include <semaphore.h>

int ticketCount = 1000;
sem_t *flag = NULL;

void *SellTicket()
{ 
    for(int i=100;i>0;i--)
    {
    sem_wait(flag);
    int temp;
    printf("当前票数为:%d\n",ticketCount);
    temp = ticketCount;
    temp = temp - 1;
    ticketCount = temp;
    sem_post(flag);
    }
}

void *ReturnTicket()
{
   for(int i=100;i>0;i--)
    {
    sem_wait(flag);
    printf("当前票数为:%d\n",ticketCount);
    int temp;
    temp = ticketCount;
    temp = temp + 1;
    ticketCount = temp;
    sem_post(flag);
    }
     
}

int main()
{
    pthread_t S,R;
    flag = sem_open("flag",O_CREAT,0666,1);
    
    pthread_create(&S,NULL,SellTicket,NULL);
    pthread_join(S,NULL);
    
    pthread_create(&R,NULL,ReturnTicket,NULL);
    pthread_join(R,NULL);

    sem_close(flag);
    sem_unlink("flag");

    printf("最终票数为:%d \n",ticketCount);
    return 0;
}

运行过程及结果:
在这里插入图片描述
在这里插入图片描述
3. 一个生产者一个消费者线程同步。设置一个线程共享的缓冲区, char buf[10]。一个线程不断从键盘输入字符到buf,一个线程不断的把buf的内容输出到显示器。要求输出的和输入的字符和顺序完全一致。(在输出线程中,每次输出睡眠一秒钟,然后以不同的速度输入测试输出是否正确)。要求多次测试添加同步机制前后的实验效果。
输入线程 Pro
输出线程 Con
代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sched.h>
#include <semaphore.h>

sem_t *empty = NULL;
sem_t *full = NULL;
char buf[10];
int i = 0;
int j = 0;
void *Pro()
{
    while(1)
    {
        sem_wait(empty);
        char in;
        scanf("%c",in);
        buff[i++%10] = in;
        sem_post(full);
    }
}
void *Con()
{
      while(1)
    {
        sleep(1);
        sem_wait(full);
        char out = buf[j++%10];
        printf("%d:%c\n",j,out);
        sem_post(empty);
    }
}
int main()
{
    pthread_t P,C;
    empty = sem_open("Pro",O_CREAT,0666,10);
    full = sem_open("Con",O_CREAT,0666,0);
    pthread_create(&P,NULL,Pro,NULL);
    pthread_join(P,NULL);
    pthread_create(&C,NULL,Con,NULL);
    pthread_join(C,NULL);
    sem_close(empty);
    sem_unlink("empty");
    sem_close(full);
    sem_unlink("full");
    return 0;
}
  1. (2) 进程通信问题。阅读并运行共享内存、管道、消息队列三种机制的代码
    (参见 https://www.cnblogs.com/Jimmy1988/p/7706980.html
    https://www.cnblogs.com/Jimmy1988/p/7699351.html
    https://www.cnblogs.com/Jimmy1988/p/7553069.html
    实验测试
    a)通过实验测试,验证共享内存的代码中,receiver能否正确读出sender发送的字符串?如果把其中互斥的代码删除,观察实验结果有何不同?如果在发送和接收进程中打印输出共享内存地址,他们是否相同,为什么?
    验证代码结果:
    在这里插入图片描述
    结论:receiver能正确读出sender发送的字符串。

删除其中的互斥部分后:
即删除信号量后,会出现重复读的现象。

在发送和接收进程中打印输出共享内存地址:
在这里插入图片描述
可以看到sender和receiver的共享内存地址不同。我认为这是因为虚拟内存映射物理内存的缘故。
b)有名管道和无名管道通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
(1) 无名管道
多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
运行结果:
在这里插入图片描述
结论:实现了同步机制
(2)有名管道
实现了同步机制,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
在这里插入图片描述
c)消息通信系统调用是否已经实现了同步机制?通过实验验证,发送者和接收者如何同步的。比如,在什么情况下,发送者会阻塞,什么情况下,接收者会阻塞?
实验验证结果:
在这里插入图片描述
分析: 在此机制中,发送端传送的消息都会加入一个消息队列。写进程在此机制中不会被阻塞,其写入的字符串会一直被添加至队列的末端,而读进程会从队列的首端一直读取消息,消息节点一旦被读取便会移除队列。当队列中不含其需要类型的消息时便会阻塞。

  1. 阅读Pintos操作系统,找到并阅读进程上下文切换的代码,说明实现的保存和恢复的上下文内容以及进程切换的工作流程。
    在thread.h中寻找关于进程的相关代码
#ifndef THREADS_THREAD_H
#define THREADS_THREAD_H
#include "threads/synch.h"
#include <fixedpoint.h>
#include <debug.h>
#include <list.h>
#include <stdint.h>
/* States in a thread's life cycle. */
enum thread_status
{
THREAD_RUNNING, /* Running thread. */
THREAD_READY, /* Not running but ready to run. */
THREAD_BLOCKED, /* Waiting for an event to trigger. */
THREAD_DYING /* About to be destroyed. */
};
/* Thread identifier type.
You can redefine this to whatever type you like. */
typedef int tid_t;
#define TID_ERROR ((tid_t) -1) /* Error value for tid_t. */
/* Thread priorities. */
#define PRI_MIN 0 /* Lowest priority. */
#define PRI_DEFAULT 31 /* Default priority. */
#define PRI_MAX 63 /* Highest priority. */
/* A kernel thread or user process.
Each thread structure is stored in its own 4 kB page. The
thread structure itself sits at the very bottom of the page
(at offset 0). The rest of the page is reserved for the
thread's kernel stack, which grows downward from the top of
the page (at offset 4 kB). Here's an illustration:
4 kB +---------------------------------+
| kernel stack |
| | |
| | |
| V |
| grows downward |
| |
| |
| |
| |
| |
| |
| |
| |
+---------------------------------+
| magic |
| : |
| : |
| name |
| status |
0 kB +---------------------------------+
The upshot of this is twofold:
1. First, `struct thread' must not be allowed to grow too
big. If it does, then there will not be enough room for
the kernel stack. Our base `struct thread' is only a
few bytes in size. It probably should stay well under 1
kB.
2. Second, kernel stacks must not be allowed to grow too
large. If a stack overflows, it will corrupt the thread
state. Thus, kernel functions should not allocate large
structures or arrays as non-static local variables. Use
dynamic allocation with malloc() or palloc_get_page()
instead.
The first symptom of either of these problems will probably be
an assertion failure in thread_current(), which checks that
the `magic' member of the running thread's `struct thread' is
set to THREAD_MAGIC. Stack overflow will normally change this
value, triggering the assertion. */
/* The `elem' member has a dual purpose. It can be an element in
the run queue (thread.c), or it can be an element in a
semaphore wait list (synch.c). It can be used these two ways
only because they are mutually exclusive: only a thread in the
ready state is on the run queue, whereas only a thread in the
blocked state is on a semaphore wait list. */
struct thread
{
/* Owned by thread.c. */
tid_t tid; /* Thread identifier. */
enum thread_status status; /* Thread state. */
char name[16]; /* Name (for debugging purposes). */
uint8_t *stack; /* Saved stack pointer. */
int priority; /* Priority. */
int64_t wakeup_time; /* Thread wakeup time in ticks. */
struct semaphore timer_sema;
struct list_elem timer_elem; /* List element for timer_wait_list. */
/* Thread statistics. */
int niceness;
fp_t recent_cpu;
struct list_elem allelem; /* List element for all threads list. */
/* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */
#ifdef USERPROG
/* Owned by userprog/process.c. */
uint32_t *pagedir; /* Page directory. */
#endif
/* Owned by thread.c. */
unsigned magic; /* Detects stack overflow. */
};
/* If false (default), use round-robin scheduler.
If true, use multi-level feedback queue scheduler.
Controlled by kernel command-line option "-o mlfqs". */
extern bool thread_mlfqs;
void thread_init (void);
void thread_start (void);
void thread_tick (void);
void thread_print_stats (void);
typedef void thread_func (void *aux);
tid_t thread_create (const char *name, int priority, thread_func *, void *);
void thread_block (void);
void thread_unblock (struct thread *);
struct thread *thread_current (void);
tid_t thread_tid (void);
const char *thread_name (void);
void thread_exit (void) NO_RETURN;
void thread_yield (void);
/* Performs some operation on thread t, given auxiliary data AUX. */
typedef void thread_action_func (struct thread *t, void *aux);
void thread_foreach (thread_action_func *, void *);
void thread_foreach_ready (thread_action_func *, void *);
int thread_get_priority (void);
void thread_set_priority (int);
int thread_get_nice (void);
void thread_set_nice (int);
int thread_get_recent_cpu (void);
int thread_get_load_avg (void);
/* Compare two threads by their wakeup_time. If wakeup_time
same, compare thread priorities to break the tie.
If true, first thread has earlier wakeup_time and in case of
a tie, higher priority. */
bool less_wakeup (const struct list_elem *left,
const struct list_elem *right, void *aux UNUSED);
/* Comparison function that prefers the threas with higher priority. */
bool more_prio (const struct list_elem *left,
const struct list_elem *right, void *aux UNUSED);
#endif /* threads/thread.h */

(1) Pintos中定义了一个thread的结构体用于存储线程的信息(包括优先级和状态),就在以上的代码thread.h中。
(2)thread.c中的基本函数
thread_block()用于将current_thread 终止变成blocked状态,只有在调用
thread_unblock()后进入就绪队列。
thread_yield()用于直接把current_thread进入就绪队列,在任意时刻可再次被调用。
list_entry返回一个线程。
idle_thread平常是不在ready_list中的。在thread_start()即系统刚开始时,idle_thread在ready_list中。.在ready_list为空时,调用idle_thread。
running_thread()返回一个running thread
thread_current()是加上一个check的running_thread
init_thread(),初始化thread,状态设为blocked,magic值,放入all_list
next_thread_to_run(),是否就绪队列为空?返回idle_thread:返回就绪队列的第一个,并移除
thread_schedule_tail(prev),current_thread的thread_tick清0,激活process,如果prev已经dying,则销毁。
schedule(),调用这个的函数是thread_block,thread_exit,thread_yield.因为前提是current_thread状态不能是running。专门负责线程切换,执行了以后会把当前线程放进队列里并调度下一个线程。

源码全部已存至GitHub啦~,需要请移步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值