《操作系统原理》实验三:线程/进程与死锁问题

一、实验目的

1)理解线程/进程的通信机制和编程;

2)理解线程/进程的死锁概念和如何解决死锁

二、实验内容

1)在Ubuntu 或Fedora 环境使用创建一对父子进程,使用共享内存的方式实现进程间的通信。父进程提供数据(1-100,递增),子进程读出来并显示。

2)(考虑信号通信机制)在Ubuntu 或Fedora 环境创建父子2 个进程A,B。进程A 不断获取用户从键盘输入的字符串或整数,通过信号机制传给进程B。如果输入的是字符串,进程B 将其打印出来;如果输入的是整数,进程B 将其累加起来,并输出该数和累加的和。当累加和大于100 时结束子进程,子进程输出“My work done!”后结束,然后父进程也结束。

3)在windows 环境使用创建一对父子进程,使用管道(pipe)的方式实现进程间的通信。父进程提供数据(1-100,递增),子进程读出来并显示。

4)(考虑匿名管道通信)在windows 环境下创建将CMD 控制台程序封装为标准的windows 窗口程序。

5)在windows 环境下,利用高级语言编程环境(限定为VS 环境或VC 环境)调用CreateThread 函数哲学家就餐问题的演示。要求:(1)提供死锁的解法和非死锁的解法;(2)有图形界面直观显示哲学家取筷子,吃饭,放筷子,思考等状态。(3)为增强结果的随机性,各个状态之间的维持时间采用随机时间,例如100ms-500ms 之间。

三、实验过程

1)共享内存实现进程通信

①任务环境

Ubuntu版本:16.04 64位

Linux内核版本:4.5.0-29-generic

②何为共享内存

共享内存就是允许两个不相关的进程访问同一个逻辑内存。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址

③函数

声明在头文件 sys/shm.h 中

④shmget函数:该函数用来创建共享内存

1.  int shmget(key_t key, size_t size, int shmflg);    

key_t key: 它有效地为共享内存段命名,shmget函数成功时返回一个与key相关的共享内存标识符(非负整数)。调用失败返回-1.

size_t size: size以字节为单位指定需要共享的内存容量

int shmflg: 共享内存的权限标志

⑤shmat函数: shmat函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间

1.  void *shmat(int shm_id, const void *shm_addr, int shmflg);    

shm_id: shm_id是由shmget函数返回的共享内存标识。

shm_addr: shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

shmflg: shm_flg是一组标志位,通常为0。

⑥shmdt函数: 该函数用于将共享内存从当前进程中分离,使该共享内存对当前进程不再可用。

1.  int shmdt(const void *shmaddr);    

参数shmaddr: 是shmat函数返回的地址指针,调用成功时返回0,失败时返回-1.

⑦shmctl函数: 与信号量的semctl函数一样,用来控制共享内存

1.  int shmctl(int shm_id, int command, struct shmid_ds *buf);    

shm_id: 是shmget函数返回的共享内存标识符。

command: 是要采取的操作,它可以取下面的三个值 :

IPC_STAT: 把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

IPC_RMID: 删除共享内存段

buf: 是一个结构指针,它指向共享内存模式和访问权限的结构。

struct shmid_ds    
{    
    uid_t shm_perm.uid;    
    uid_t shm_perm.gid;    
    mode_t shm_perm.mode;    
};   

 

2)Linux父子进程管道通信

①管道的特点:

1. 管道只允许具有血缘关系的进程间通信,如父子进程间的通信。  
2. 管道只允许单向通信。  
3. 管道内部保证同步机制,从而保证访问数据的一致性。  
4. 面向字节流  
5. 管道随进程,进程在管道在,进程消失管道对应的端口也关闭,两个进程都消失管道也消失。  

②注意事项:

1.  如果一个管道的写端一直在写,而读端的引⽤计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只写不读再次调用write会导致管道堵塞;  

2.  如果一个管道的读端一直在读,而写端的引⽤计数是否⼤于0决定管道是否会堵塞,引用计数大于0,只读不写再次调用read会导致管道堵塞;  

3.  而当他们的引用计数等于0时,只写不读会导致写端的进程收到一个SIGPIPE信号,导致进程终止,只写不读会导致read返回0,就像读到⽂件末尾⼀样。  

 

3)哲学家问题

任务环境:Windows 10 x64 + Visual Studio 2017 Enterprise

饥饿解法:

 

1.	#define N 5   
2.	  
3.	void philosopher(int i)   
4.	{   
5.	    while (true) {   
6.	        think() ;   
7.	        take_fork(i) ;   
8.	        take_fork((i+1)%N) ;   
9.	        eat() ;   
10.	        put_fork(i) ;   
11.	        put_fork((i+1)%N) ;   
12.	    }   
13.	}  

 

非死锁解法:

 

#define N 5  // 哲学家的数目   
#define LEFT (i+N-1)%N // i的左邻居编号   
#define RIGHT (i+1)%N // i的右邻居编号   
#define THINKING 0 // 哲学家在思考   
#define HUNGRY 1 // 哲学家试图拿起叉子   
#define EATING 2 // 哲学家就餐   
typedef int semaphore ; // 信号量是一种特殊的整型数据   
int state[N] ; // 数组用来跟踪记录每位哲学家的状态,全局变量初始化为0,即是说哲学家的初始化状态是THINKING ;    
semaphore mutex = 1 ; // 临界区的互斥   
semaphore s[N] ; // 每个哲学家一个信号量   
  
void philosopher(int i) { // i:哲学家编号,从0到i-1   
    while (true) {   
        think() ;   
        take_forks(i) ;   
        eat() ;   
        put_forks(i) ;   
    }                           
}     
  
void take_forks(int i) {   
    wait(mutex) ; // 进入临界区   
    state[i] = HUNGRY ; // 记录哲学家i处于饥饿的状态   
    test(i) ; // 尝试获得2把尺子   
    signal(mutex) ; // 离开临界区   
    wait(s[i]) ; // 如果得不到需要的尺子则阻塞   
}    
  
void put_forks(int i) {   
    wait(mutex) ; // 进入临界区   
    state[i] = THINKING ; // 记录哲学家i已经就餐完毕   
    test(LEFT) ; // 检查左边的邻居现在可以吃吗    
    test(RIGHT) ; // 检查右边的邻居现在可以吃吗   
    signal(mutex) ; // 离开临界区   
}   
  
void test(int i) {   
    if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {   
        state[i] = EATING ;   
        signal(s[i]) ;          
    }   
}   

 

 

四、实验结果

1)共享内存实现进程通信

#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <sys/shm.h>  
#include <string.h>  
#include <errno.h>    
  
#define TEXT_SZ 101  
  
struct shared_use_st  
{  
    int written;       //作为一个标志,非0:表示可读,0表示可写  
    int text[TEXT_SZ]; //记录写入和读取的文本  
};  
  
int main()  
{  
    int pRunning = 1;             //程序是否继续运行的标志(父)  
    int cRunning = 1;             //程序是否继续运行的标志(子)  
    void *shm = NULL;             //分配的共享内存的原始首地址  
    struct shared_use_st *shared; //指向shm  
    char buffer[BUFSIZ + 1];      //用于保存输入的文本  
    int shmid;                    //共享内存标识符  
    //创建共享内存  
    shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);  
    if (shmid == -1)  
    {  
        fprintf(stderr, "shmget failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //将共享内存连接到当前进程的地址空间  
    shm = shmat(shmid, 0, 0);  
    if (shm == (void *)-1)  
    {  
        fprintf(stderr, "shmat failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //设置共享内存  
    shared = (struct shared_use_st *)shm;  
    shared->written = 0;  
    pid_t pid = fork();  
    if (pid < 0)  
    {  
        //printf('子进程创建失败');  
        fprintf(stderr, "can't fork ,error %d\n", errno);  
        exit(1);  
    }  
    else if (pid == 0) //子进程  
    {  
        while (cRunning) //读取共享内存中的数据  
        {  
            //没有进程向共享内存定数据有数据可读取  
            if (shared->written != 0)  
            {  
                int i = 0;  
                while (i < 100)  
                {  
                    printf("child  read: %d\n", shared->text[i]);  
                    i++;  
                }  
                //读取完数据,设置written使共享内存段可写  
                shared->written = 0;  
                //退出循环(程序)  
                cRunning = 0;  
            }  
            else //有其他进程在写数据,不能读取数据  
                sleep(1);  
        }  
    }  
    else //父进程  
    {  
        int i = 1;  
        while (pRunning) //向共享内存中写数据  
        {  
            //数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本  
            while (shared->written == 1)  
            {  
                sleep(1);  
            }  
            //向共享内存中写入数据  
            shared->text[i - 1] = i;  
            printf("parent write: %d\n", shared->text[i - 1]);  
            i++;  
            if (i > 100)  
            {  
                //写完数据,设置written使共享内存段可读,退出循环(程序)  
                shared->written = 1;  
                pRunning = 0;  
            }  
        }  
    }  
    //把共享内存从当前进程中分离  
    if (shmdt(shm) == -1)  
    {  
        fprintf(stderr, "shmdt failed\n");  
        exit(EXIT_FAILURE);  
    }  
    //删除共享内存  
    if (shmctl(shmid, IPC_RMID, 0) == -1)  
    {  
        fprintf(stderr, "shmctl(IPC_RMID) failed\n");  
        exit(EXIT_FAILURE);  
    }  
    exit(EXIT_SUCCESS);  
} 

 

2)Linux父子进程管道通信

#include <stdio.h>  
#include <unistd.h>  
#include <string.h>  
#include <errno.h>  
  
int main()  
{  
    int fd[2];  
    int ret = pipe(fd);  
    if (ret == -1)  
    {  
        perror("pipe error \n");  
        return 1;  
    }  
    pid_t id = fork();  
    if (id > 0) //parent  
    {  
        int i = 1;  
        close(fd[0]);  
        while (i < 101)  
        {  
            write(fd[1], &i, 1);  
            //每隔 1 秒打印一次  
            sleep(1);  
            i++;  
        }  
    }  
    else if (id == 0) //child  
    {  
        close(fd[1]);  
        int msg;  
        int j = 0;  
        while (j < 100)  
        {  
            ssize_t s = read(fd[0], &msg, sizeof(msg));  
            printf("%d\n", msg);  
            j++;  
        }  
    }  
    else  
    { //error  
        perror("fork error \n");  
        return 2;  
    }  
    return 0;  
}  

3)哲学家问题

饥饿解法:

#define N 5   
  
void philosopher(int i)   
{   
    while (true) {   
        think() ;   
        take_fork(i) ;   
        take_fork((i+1)%N) ;   
        eat() ;   
        put_fork(i) ;   
        put_fork((i+1)%N) ;   
    }   
}  

非死锁解法:

 

#define N 5  // 哲学家的数目   
#define LEFT (i+N-1)%N // i的左邻居编号   
#define RIGHT (i+1)%N // i的右邻居编号   
#define THINKING 0 // 哲学家在思考   
#define HUNGRY 1 // 哲学家试图拿起叉子   
#define EATING 2 // 哲学家就餐   
typedef int semaphore ; // 信号量是一种特殊的整型数据   
int state[N] ; // 数组用来跟踪记录每位哲学家的状态,全局变量初始化为0,即是说哲学家的初始化状态是THINKING ;    
semaphore mutex = 1 ; // 临界区的互斥   
semaphore s[N] ; // 每个哲学家一个信号量   
  
void philosopher(int i) { // i:哲学家编号,从0到i-1   
    while (true) {   
        think() ;   
        take_forks(i) ;   
        eat() ;   
        put_forks(i) ;   
    }                           
}     
  
void take_forks(int i) {   
    wait(mutex) ; // 进入临界区   
    state[i] = HUNGRY ; // 记录哲学家i处于饥饿的状态   
    test(i) ; // 尝试获得2把尺子   
    signal(mutex) ; // 离开临界区   
    wait(s[i]) ; // 如果得不到需要的尺子则阻塞   
}    
  
void put_forks(int i) {   
    wait(mutex) ; // 进入临界区   
    state[i] = THINKING ; // 记录哲学家i已经就餐完毕   
    test(LEFT) ; // 检查左边的邻居现在可以吃吗    
    test(RIGHT) ; // 检查右边的邻居现在可以吃吗   
    signal(mutex) ; // 离开临界区   
}   
  
void test(int i) {   
    if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {   
        state[i] = EATING ;   
        signal(s[i]) ;          
    }   
} 

 

五、体会

通过本次实验,我对操作系统线程/进程的通信机制和编程、操作系统线程/进程的死锁概念有了更深刻的理解,掌握了如何解决死锁,对操作系统的功能与原理有了进一步的了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值