Shell脚本样例

chmod +x ./test.sh 

#! /bin/bash
mkdir osdir;
cd osdir;
touch f1.txt f2.txt f3.txt;
free > f1.txt;
pwd > f2.txt;
ls -l > f3.txt;
touch hello.c;
echo "#include <stdio.h>
int main(void) {
    printf(\"Hello World!\");
    return 0;
}">hello.c;
echo "f1.txt:";
cat f1.txt;
echo ;
echo "f2.txt:";
cat f2.txt;
echo ;
echo "f3.txt:";
cat f3.txt;
echo ;
echo "The files in this dir";
ls -al;
echo ;
echo "The files in hello.c";
cat hello.c;
gcc hello.c;
./a.out;
echo ;
cd ..;
echo ;
echo "The files in parent's dir";
ls;

#写一个shell脚本,并允许其他组用户具备可执行权限,要求创建一个目录osdir,
#在osdir中新建三个文件(f1,f2,f3),f1记录当前系统的内存占用情况,
#f2记录该文件的路径信息,
#f3 记录三个文件地详细信息(ls -l)在osdir目录下写一个hello.c程序,
#0gcc编译并输出”hello world”字样。

进程创建

进程属性

  • PID:进程号
  • PPID:父进程号
  • UID:创建者的用户标识号,父子进程UID相同
  • 进程之间不是孤立存在的,它们之间有一定的族亲关系
  • 一个进程的子进程可以多于一个
  • 一个进程只会有一个父进程

进程创建

​ 系统加电启动后,系统中只有一个进程——初始化进程,又称init进程,是所有进程的祖先进程,它的进程pid=1,在linux中除了init进程是由系统启动时创建的外,其他所有进程都是由当前进程使用系统调用fork()创建的,进程创建后父子进程在系统中并发执行

fork()

#include <unistd.h>
pid_t fork( );

//通过fork创建一个新进程,系统复制当前进程,在进程表中创建一个新的表项
//新进程几乎与原进程相同,执行代码也相同
//但有自己的数据空间,环境,文件描述符等
  • 此时一个进程“分裂”成两个进程:父进程和子进程
  • 区别:进程ID。利用getpid()来得到进程号

fork()仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值

  • 父进程返回子进程PID
  • 子进程:返回0
  • 出现错误:返回-1

image-20220519104716185

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main()
{
    int pid = fork();
    if (pid == -1)
    {
        printf("error!\n");
    }
    else if (pid == 0)
    {
        printf("This is the child process!\nchild process id = %d\n", getpid());
    }
    else
    {
        printf("This is the parent process!\nparent process id = %d\n", getpid());
    }
    return 0;
}
/*
This is the parent process!
parent process id = 17439
This is the child process!
child process id = 17440
*/

image-20220519105820735

等待进程结束

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *stat_loc)
/*
1. wait系统调用将暂停父进程直到它的子进程结束为止
2. 返回子进程的PID
3. 状态信息允许父进程了解子进程的退出状态,即子进程main函数返回值或子进程中exit函数的退出码
4. 若stat_loc不为空指针,状态信息被写入他指定的地址
*/

exec函数族

  • 父进程创建子进程后,子进程一般要执行不同的程序.为了调用系统程序,我们可以使用系统调用exec 族调用。

  • Exec函数可以把当前进程替换为一个新进程

  • exec函数包含以下五个函数:

#include<unistd.h>
int execl(constchar* path, const char* arg, …);
int execlp(constchar* file, const char* arg, …);
int execle(constchar* path, const char* arg, char* const envp[]);
int execv(constchar* path, char* const argv[]);
int execvp(constchar* file, char* const argv[]);
后缀 操作能力
l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
v 希望接收到一个以NULL结尾的字符串数组的指针
p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境

execve()

真正意义上的系统调用,其它都是在此基础上经过包装的库函数

#include<unistd.h>
Int execve(constchar* path, char* const argv[], char* const envp[]);

//path:可执行文件路径。
//argv[]:要执行的文件名或命令名。
//envp[]:环境变量,可省略

示例

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
    int n = 0, cnt, fib1, fib2, fibt;
    pid_t pid;
    scanf("%d", &n);

    if (n < 0)
        printf("ERROR INPUT\n");
    else
    {
        printf("START\n");
        pid = fork();
        if (pid > 0)
        {
            wait(NULL);
            printf("FINISH");
        }
        else if (pid == 0)
        {
            printf("THE IN PUT IS %d\n", n);
            printf("0 ");
            fib1 = 1;
            fib2 = 1;
            for (cnt = 1; cnt <= n; cnt++)
            {
                if (cnt < 3)
                {
                    printf("1 ");
                }
                else
                {
                    fibt = fib1;
                    fib1 = fib2;
                    fib2 = fibt + fib1;
                    printf("%d ", fib2);
                }
            }
            printf("\n");
        }
        else
        {
            printf("ERROR\n");
        }
    }
    printf("\n");

    return 0;
}
/*
Fibonacci序列是一组:0,1,1,2,3,5,8,……
fib0=0;
fib1=1;
fibn=fibn-1+fibn-2;
使用系统调用fork()编写一个C程序,它在其子程序中生成Fibonacci序列,序列号码将会在命令行中提供。
例如,如果提供的是5,Fibonacci序列中的前5个数将由子进程输出。
由于父进程和子进程都有他们自己的数据副本,对子进程而言,输出序列式必要的。
退出程序前,父进程调用wait()调用来等待子进程结束。
 执行必要的错误检查以保证不会接受命令行传递来的负数号码。
*/

线程创建

  • 线程:一个进程内部的一个控制序列
  • 每个进程都至少有一个执行线程

与fork区别

  • fork: 当前进程的拷贝,有自己的变量和PID,时间调度独立,执行几乎完全独立于父进程

  • **新线程:**有自己独立栈(有局部变量),但与创建者共享全局变量,文件描述,信号句柄和当前状态等

pthread_creat()

#include<pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)
  • pthread_t *thread 该指针指向的变量中将被写入一个标识符,用该标识符来引用新线程

  • pthread_attr_t *attr设置线程属性,一般不用设为null

  • void *(*start_routine)(void*)表示线程将要启动执行的函数

  • void *arg该函数需要的参数,调用成功返回值为0,否则返回错误代码,传递多个参数需要结构体或类

void pthread_exit(void *retval)

  • 调用上面函数终止线程

  • 返回一个指向某个对象的指针

注意:不能用它来返回一个指向局部变量的指针,因为线程调用该函数后,这个局部变量就不存在了

pthread_join()

int pthread_join(pthread_t th, void **thread_return)
//等待线程结束,收集线程信息
//参数1.指定了将要等待结束的线程
//参数2. 是一个指针,指向了一个指针,后者指向线程的返回值
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
void *thread_function(void *arg);
char message[] = "Hello world";
int main()
{
    int res;
    pthread_t a_thread;
    void *thread_result;
    res = pthread_create(&a_thread, NULL, thread_function, (void *)message);
    if (res != 0)
    {
        perror("Thread creation failed");
        exit(EXIT_FAILURE);
    }
    printf("Waiting for thread to finish..\n");
    res = pthread_join(a_thread, &thread_result);
    if (res != 0)
    {
        perror("Thread join failed");
        exit(EXIT_FAILURE);
    }
    printf("Thread joined, it returned %s\n", (char *)thread_result);

    printf("Message is now %s\n", message);
    exit(EXIT_SUCCESS);
}
void *thread_function(void *arg)
{
    printf("thread_function is running. Argument was %s \n", (char *)arg);
    sleep(3);
    strcpy(message, "Bye!");
    pthread_exit("Thank you for the CPU time");
}
/*
Waiting for thread to finish...
thread_function is running. Argument was Hello world
Thread joined, it returned Thank you for the CPU time
Message is now Bye!
*/

编译须知

gcc -pthread -o file file.c 
./file
#原因:pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 	libpthread.a

示例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

struct os2
{
    int n;
    int ans[500];
};
void *thread_function(void *argv);
int main()
{
    int res;
    int n = 0, cnt;
    struct os2 tst;
    pthread_t a_thread;
    void *thread_result;
    scanf("%d", &tst.n);
    res = pthread_create(&a_thread, NULL, thread_function, &(tst));
    if (res != 0)
    {
        perror("Thread creation failed");
        exit(EXIT_FAILURE);
    }
    printf("Waiting for thread to finish..\n");
    res = pthread_join(a_thread, &thread_result);
    if (res != 0)
    {
        perror("Thread join failed");
        exit(EXIT_FAILURE);
    }
    printf("Thread joined, the result is\n");
    for (cnt = 0; cnt <= tst.n; cnt++)
    {
        printf("%d ", tst.ans[cnt]);
    }
    printf("\n");
    exit(EXIT_SUCCESS);
    printf("\n");
    return 0;
}
void *thread_function(void *argv)
{
    int cnt, fib1, fib2, fibt;
    int n;
    int *ans;
    struct os2 *a = (struct os2 *)argv;
    n = (*a).n;
    ans = (*a).ans;
    printf("\n");
    printf("START\n");
    if (n < 0)
        printf("ERROR INPUT\n");
    else
    {
        printf("THE INPUT IS %d\n", n);
        ans[0] = 0;
        fib1 = 1;
        ans[1] = 1;
        fib2 = 1;
        ans[2] = 1;
        for (cnt = 1; cnt <= n; cnt++)
        {
            if (cnt > 2)
            {
                fibt = fib1;
                fib1 = fib2;
                fib2 = fibt + fib1;
                ans[cnt] = fib2;
            }
        }
        printf("\n");
    }
}
/*
Fibonacci序列是一组:0,1,1,2,3,5,8,……
fib0=0;
fib1=1;
fibn=fibn-1+fibn-2;
使用phtread多线程编程程序来生成Fibonacci序列。程序应该这样工作:
用户运行程序时,在命令行输入要产生Fibonaci序列数,然后程序创建一个新的线程来产生Fibonacci数,
把这个序列放到线程共享的数据中(数组可能是一种最方便的数据结构)。当线程执行完成后,
父线程将输出子线程产生的序列。
由于在子线程结束前,父线程不能开始输出Fibonacci序列,因此,父线程需要等子线程结束
*/

进程通信

同步与互斥

  • 互斥:并发执行的进程共享某些类临界资源,对临界资源的访问应当采取互斥的机制。
  • 同步:并发执行的进程间通常存在相互制约的关系,进程必须遵循一定的规则来执行,同步机制可以协调相互制约的关系

PV原语

  • PV原语通过操作信号量来处理进程间的同步与互斥的问题。其核心就是一段不可分割不可中断的程序。

  • P原语:为阻塞原语,负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

  • V原语:为唤醒原语,负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

信号量

  1. Linux 中的信号量是一个整数

    • 大于或等于0 时, 代表可供并发进程使用的资源实体数;

    • 小于0 时,代表正在等待使用临界区的进程数;

    • 用于互斥的信号量初始值应大于0,即空闲资源总数;

    • 由操作系统来维护的,用户进程只能通过初始化和两个标准原语(P、V原语)来访问;

  2. 信号量元素组成:

    • 表示信号量元素的值;

    • 最后操作信号量元素的进程ID;

    • 等待信号量元素值+1 的进程数;

    • 等待信号量元素值为0 的进程数;

  3. 信号量的主要函数

  • 创建信号量
int semget(key_t key,int nSemes,int flag)

key_t key,//标识信号量的关键字
int nSemes,//信号量集中元素个数
int flag//IPC_CREAT;IPC_EXCL 只有在信号量集不存在时创建
作用:创建一个新的信号量或取得一个已有信号量的键
成功:返回信号量句柄
失败:返回-1
  • 控制信号量
semctl(int semid,int semnum,int cmd,/*union senum arg */)

semctl(
int semid,//信号量集的句柄
int semnum,//信号量集的元素数
int cmd,//命令
/*union senum arg */.../
)

允许我们直接控制信号量
成功:返回相应的值
失败:返回-1

命令CMD 参数详细说明:
IPC_RMID 删除一个信号量
IPC_EXCL 只有在信号量集不存在时创建
IPC_SET 设置信号量的许可权
SETVAL 设置指定信号量的元素的值为 agc.val
GETVAL 获得一个指定信号量的值
GETPID 获得最后操纵此元素的最后进程ID
GETNCNT 获得等待元素变为1 的进程数
GETZCNT 获得等待元素变为0 的进程数
  • 对信号量 +1 或 -1 或测试是否为0
int semop(int semid,struct sembuf *sops, unsigned short nsops )

int semop(
int semid,
struct sembuf *sops, //指向元素操作数组
unsigned short nsops //数组中元素操作的个数
)

改变信号量的值

struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
}
第一个参数:信号量编号,一般为0
第二个参数:信号量在一次操作中需要改变的数值,通常会用到两个值,一个是-1,也就是P操作,它等待信号量变为可用;一个是+1,也就是V操作
第三个参数:通常被置为SEM_UNDO。将使得系统跟踪当前进程对这个信号量的修改情况

生产者消费者问题

producer(生产者进程):
Item_Type item;
{
    while (true)
    {
        p(empty);
        p(mutex);
        送数据如缓冲区某单元
        v(mutex);
        v(full);
    }
}

consumer(消费者进程):
Item_Type item;
{
    while (true)
    {
        p(full);
        p(mutex);
        取缓存区中某单元数据
        v(mutex);
        v(empty);
    }
}

管道

​ linux系统中进程间通信机制,无名管道只能用于具有亲缘关系的进程之间,命名管道可以用于不具有亲缘关系的进程之间

当写进程向管道中写入时,它使用的是标准的库函数write,系统会根据库函数传递进来的文件描述符找到该文件的file结构,前面已经说到file结构会指向一个临时的VFS索引节点,我们知道这个写入地址后,kernel就调用写函数完成写操作,在写入函数在向内存中写入数据前,必须检查VFS索引节点中的信息,

(1) 内存中有足够空间容纳要写入的数据

(2) 内存没有被读程序锁定

同时满足这两个情况时才可以进行实际的内存复制工作,这时写入函数首先会“锁定”那部分内存,然后从写进程的地址空间中复制数据到内存,如果上面两个条件没满足的话,写进程就会休眠在VFS索引节点的等待队列中。等啊等,大家都知道内核会调用调度程序来调度其他进程,当条件满足的时候(也就是有空间或者读进程解锁)这时候读进程会唤醒写进程,写进程将收到“信号”,然后写入完成后,内存自然会被解锁,这次轮到写进程来唤醒所有读进程了。

#include <stdio.h>
#include <unistd.h>
int main()
{
    int n, fd[2]; // fd保存开辟的pipe所关联的两个文件描述符
    pid_t pid;    //子进程id
    char line[100];
    if (pipe(fd) < 0) //创建匿名管道,named pipe使用函数int mkfifo(const char *filename, mode_t mode); 来创建
        printf(“pipe create error \n”);
    if ((pid = fork()) < 0)
        printf(“fork error \n”);
    else if (pid > 0)
    {
        close(fd[0]);
        write(fd[1],”hello world\n”, 11);
    }
    else
    {
        close(fd[0]);
        n = read(fd[0], line, 100);
        write(STDOUT_FILENO, line, n);
    }
}

共享内存

​ 共享内存是被多个进程共享的一部分物理内存。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。

共享内存的实现分为三个步骤:

  1. 创建共享内存,使用shmget函数。
  2. 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
  3. 共享内存解除映射,当一个进程不再需要共享内存时,需要把它从进程地址空间中解除映射。使用int shmdt(char *shmaddr)

创建共享内存

int shmget(key_t key ,int size,int shmflg)
key标识共享内存的键值:0/IPC_PRIVATE。当key的取值为IPC_PRIVATE,则函数shmget将创建一块新的共享内存;如果key的取值为0,而参数中又设置了IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
返回值:如果成功,返回共享内存表示符,如果失败,返回-1。

映射共享内存

# 1
int shmat(int shmid,char *shmaddr,int flag)
参数:
shmid:shmget函数返回的共享存储标识符
flag:决定以什么样的方式来确定映射的地址(通常为0)
返回值:
如果成功,则返回共享内存映射到进程中的(物理)地址;如果失败,则返回-1。
    
# 2 MMAP
功能:将一个文件或者其它对象映射进(虚拟)内存。
用法:
#include <sys/mman.h>  
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
    
start:映射区的开始地址。  
length:映射区的长度。  
prot:期望的内存保护标志,不能与文件的打开模式冲突。是书上的某个值,可以通过or运算合理地组合在一起。
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个位的组合体。  
fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。  
offset:被映射对象内容的起点。 

共享内存解除映射

当一个进程不再需要共享内存时,需要把它从进程地址空间中解除映射。

int shmdt(char *shmaddr)

实例

/*
    编程使用 Linux 中的信号量完成操作系统中的生产者消费者问题:
本程序有一个生产者进程,有两个消费者进程。生产者产生1-20 的20 个数。两个消费者从共享内存中取数。
*/
#include<string.h>
#include <sys/time.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/sem.h>
#include<sys/select.h>
#include<sys/wait.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<time.h>
#define SEM_ID1 225//设置冲突信号量关键字
#define SEM_ID2 97
#define SEM_ID3 234
#define SHMKEY 77
#define proNum 1
#define conNum 2
#define N 20

int flag=0; 
struct buf {
    char buffer[N];
    int productID;
    int nextc;
    int read;
    int write;
};

void P(int s)//p操作 
{
    struct sembuf sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = -1;
    sem_op.sem_flg = 0;
    semop(s, &sem_op, 1);
}
void V(int s)//v操作 
{
    struct sembuf sem_op;
    sem_op.sem_num = 0;
    sem_op.sem_op = 1;
    sem_op.sem_flg = 0;
    semop(s, &sem_op, 1);
 
}
void pro()//生产者
{
    int shmid;
    int sem_mutex, sem_empty, sem_full;
    void* addr;
    struct buf* op;
    struct sembuf sem_op;//指向操作数组
    sem_mutex = semget(SEM_ID1, 1, 0600);
    sem_empty = semget(SEM_ID2, 1, 0600);
    sem_full = semget(SEM_ID3, 1, 0600);
    shmid = shmget(SHMKEY, sizeof(struct buf), 0777);
    addr = shmat(shmid, 0, 0);
    while (1)
    {
        //P(empty)
        P(sem_empty);
        //P(mutex)
        P(sem_mutex);
        if(flag==0){
        op = (struct buf*)addr;
        op->productID++;
        if(op->productID==20)
            flag=1;
        op->buffer[op->write] = 1;
        op->write = (op->write + 1) % N;
 
        printf("当前生产的新产品ID%d,缓冲区位置:%d\n",op->productID, op->write);
    
        }
        //V(mutex)
        V(sem_mutex);
        //V(full)
        V(sem_full);
        sleep(1);
    }
    shmdt(addr);
}
void con()//消费者
{
    int tim, shmid;
    int sem_mutex, sem_empty, sem_full;
    void* addr;
    struct buf* op;
    struct sembuf sem_op;
    sem_mutex = semget(SEM_ID1, 1, 0600);
    sem_empty = semget(SEM_ID2, 1, 0600);
    sem_full = semget(SEM_ID3, 1, 0600);
    shmid = shmget(SHMKEY, sizeof(struct buf), 0777);
    addr = shmat(shmid, 0, 0);
    while (1)
    {
    
        //P(full)
        P(sem_full);
        //P(mutex)
        P(sem_mutex);
 
        op = (struct buf*)addr;
        op->nextc++;
        op->buffer[op->read] = 0;
        op->read = (op->read + 1) % N;
 
        printf("\t\t\t当前消费者进程:%d 消费的产品ID:%d,缓冲区位置:%d\n",getpid(), op->nextc, op->read);
        if(op->nextc==20)
            exit(0);
        //V(mutex)
        V(sem_mutex);
        //V(empty)
        V(sem_empty);
        
        sleep(2);
    }
    shmdt(addr);
}
 
int main()
{
    int sem_mutex, sem_empty, sem_full, shmid,i;
    void* addr;
    union semun {
        int val;
    }empty, full, mutex;
 
    //建立信号量 
    sem_mutex = semget(SEM_ID1, 1, IPC_CREAT | 0600);
    sem_empty = semget(SEM_ID2, 1, IPC_CREAT | 0600);
    sem_full = semget(SEM_ID3, 1, IPC_CREAT | 0600);
 
    full.val = 0;
    empty.val = N;
    mutex.val = 1;
 
    semctl(sem_mutex, 0, SETVAL, mutex);
    semctl(sem_empty, 0, SETVAL, empty);
    semctl(sem_full, 0, SETVAL, full);
 
    //建立共享内存并进行映射 
    shmid = shmget(SHMKEY, sizeof(struct buf), 0777 | IPC_CREAT);
    if (shmid==-1)
    {
        printf("建立共享内存失败\n");
        exit(0);
    }
    addr = shmat(shmid, 0, 0);
    memset(addr, 0, sizeof(struct buf));
 
    //执行生产者进程 
    for (i = 0; i < proNum; i++)
        if (fork() == 0)
        {
            pro();
            exit(0);
        }
    //执行消费者进程 
    for (i = 0; i < conNum; i++)
        if (fork() == 0)
        {
            con();
            exit(0);
        }
 
    while (wait(0)!=-1);
    semctl(sem_mutex, 0, IPC_RMID);
    semctl(sem_empty, 0, IPC_RMID);
    semctl(sem_full, 0, IPC_RMID);
    shmdt(addr);//共享内存解除
}

image-20220526151821187

/*
爸爸给女儿和儿子喂水果。爸爸随机的挑选橘子或苹果放在盘子里。
女儿只吃橘子,儿子只吃苹果。盘子可以装下2个水果。
用信号量协调进程,用打印的方式表明吃到对应的水果。
*/


#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int NumOfEmptyPlate = 0; 
int NumOfApple = 0;      
int NumOfOrange = 0;     
int mutex = 0;       //互斥信号量
int *plate = NULL;              
struct sembuf P, V;             
void father_do() //1apple, 2orange
{                
    int i = 0;
    semop(NumOfEmptyPlate, &P, 1); // P(空盘子)
    if (rand() % 10 + 1 > 5)//orange
    {
        sleep(1);                       
        semop(mutex, &P, 1); // P(盘子)
        printf("The father placed an orange\n");
        for (i = 0; i < 2; i++)
        {
            if (plate[i]==0) //找空盘子放水果
            {
                plate[i] = 2;
                break;
            }
        }
        semop(NumOfOrange, &V, 1); // V(橘子)
        semop(mutex, &V, 1); // V(盘子)
    }
    else //apple
    {
        sleep(1);                     
        semop(mutex, &P, 1); // P(盘子)
        printf("The father placed an apple\n");
        for (i = 0; i < 2; i++)
        {
            if (plate[i]==0) 
            {
                plate[i] = 1;
                break;
            }
        }
        semop(NumOfApple, &V, 1); // V(苹果)
        semop(mutex, &V, 1); // V(盘子)
    }
}
void girl_do() 
{
    int i = 0;
    semop(NumOfOrange, &P, 1); // P(橘子)
    sleep(1);                         
    semop(mutex, &P, 1);   // P(盘子)
    for (i = 0; i < 2; i++)
    {
        if (2 == plate[i])
        {
            plate[i] = 0; //把盘子清空
            break;
        }
    }
    semop(NumOfEmptyPlate, &V, 1); // V(空盘子)
    printf("The daughtor ate an orange.\n");
    semop(mutex, &V, 1); // V(盘子)
}
void boy_do()
{
    int i = 0;
    semop(NumOfApple, &P, 1); // P(苹果)
    sleep(1);                        
    semop(mutex, &P, 1);  // P(盘子)
    for (i = 0; i < 2; i++)
    {
        if (1 == plate[i])
        {
            plate[i] = 0;
            break;
        }
    }
    semop(NumOfEmptyPlate, &V, 1); // V(空盘子)
    printf("The son ate an apple.\n");
    semop(mutex, &V, 1); // V(盘子)
}
int main()
{

    pid_t father_id;
    pid_t girl_id;
    pid_t boy_id;
    char op = ' ';
    srand(time(0)); 
    plate = (int *)mmap(NULL, sizeof(int) * 2, PROT_READ | PROT_WRITE,
                        MAP_SHARED | MAP_ANONYMOUS, -1, 0); // GET SHARE MEMORY
    plate[0] = 0;
    plate[1] = 0;
    NumOfApple = semget(IPC_PRIVATE, 1, IPC_CREAT);
    NumOfEmptyPlate = semget(IPC_PRIVATE, 1, IPC_CREAT);
    NumOfOrange = semget(IPC_PRIVATE, 1, IPC_CREAT);
    mutex = semget(IPC_PRIVATE, 1, IPC_CREAT);
    //控制信号量
    if (semctl(NumOfApple, 0, SETVAL, 0)==-1 ||semctl(NumOfEmptyPlate, 0, SETVAL, 2) ==-1||semctl(NumOfOrange, 0, SETVAL, 0)==-1 ||semctl(mutex, 0, SETVAL, 1)==-1)
    {
        exit(0);
    }
    //对信号量 +1 或 -1 或测试是否为0
    V.sem_num = 0;
    V.sem_op = 1;
    V.sem_flg = SEM_UNDO;
    P.sem_num = 0;
    P.sem_op = -1;
    P.sem_flg = SEM_UNDO;
    father_id = fork();
    if (father_id==0)
    {
        while (1)
            father_do();
    }
    else
    {
        girl_id = fork();
        if (girl_id==0) // girl do
        {
            while (1)
                girl_do();
        }
        else
        {
            boy_id = fork();
            if (boy_id==0)
            {
                while (1)
                    boy_do();
            }
            else 
            {
                do
                {
                    op = getchar();
                } while (op != 'q');
                exit(0);
            }
        }
    }
    return 0;
}

image-20220526151856670

消息队列

概念

​ 消息队列就是一些消息的列表。用户可以从消息队列中添加消息和读取消息等。从这点上看,消息队列具有一定的FIFO特性,但是它可以实现消息的随机查询,比FIFO具有更大的优势。同时,这些消息又是存在于内核中的,由“队列ID”来标识。

​ 消息的结构。受到两方面约束,长度必须小于系统规定上限,其次,它必须以一个长整型变量开始,接受函数将用这个成员变量来确定消息类型

struct my_message
{
 long message_type;
 /*data*/
}

操作

  • 创建或打开消息队列: msgget(),数量会受到系统消息队列数量的限制。

  • 添加消息: msgsnd(),把消息添加到已打开的消息队列末尾;

  • 读取消息: msgrcv(),把消息从消息队列中取走,与FIFO不同的是,这里可以指定取走某一条消息;

  • 控制消息队列: msgctl(),可以完成多项功能。

//所需头文件
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

int msgget(key_t key,int msgflg);
key:消息队列键值,多个进程可以通过它访问同一个消息队列,其中有个特殊值IPC_PRIVATE,用于创建当前进程的私有消息队列
msgflg:权限标志位
返回值 成功:消息队列ID,失败:-1    
    
int msgrcv(int msgid,void *msgp,size_t msgsz,long int msgtyp,int msgflg);
msgid:消息队列的队列ID
msgp:消息缓冲区,同于msgsnd()函数的msgp
msgsz:消息正文的字节数(不包括消息类型指针变量)
msgtyp: 
0:接收消息队列的第一个消息
>0:接收消息队列的第一个类型为msgtyp的消息
<0:接收消息队列中第一个类型值不小于msgtyp绝对值且类型值最小的消息
msgflg:
MSG_NOERROR:若返回消息比msgsz字节多,消息就会截短刀msgsz字节,且不通知消息发送进程
IPC_NOWAIT:若在消息队列中并没有相应类型的消息可以接收,则函数立即返回
0:msgsnd()调用阻塞直到接收一条相应类型的消息为止
返回值 成功0,出错-1    
    
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg)''
msqid:消息队列ID
msgp:指向消息结构的指针,该消息结构msgbuf通常为
struct msgbuf
{
    long mtype; //消息类型,该结构必须从这个域开始
    char mtext[1];//消息正文
}
msgsz:消息正文的字节数(不包括消息类型指针变量)
msgflg:
IPC_NOWAIT若消息无法立即发送(比如消息队列已满),函数立即返回
0:msgsnd调用阻塞直到发送成功为止
返回值 成功:0 出错:-1
    
int msgctl(int msgqid,int cmd,struct msqid_ds *buf);
msqid:消息队列ID
cmd:
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm域(IPC操作权限描述结构)值,值取值buf
IPC_RMID:从系统内核中删除消息队列
buf:描述消息队列的msgqid_ds结构类型变量
返回值 成功:0 出错:-1    

样例

创建队列

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
int main ( void )
{
    int     qid;
    key_t       key;
   
    key = 113;
    qid=msgget( key, IPC_CREAT | 0666 ) ;        /* 创建一个消息队列 */
    if ( qid < 0 ) {                            /* 创建一个消息队列失败 */
        perror ( "msgget" );
        exit (1) ;
    }
   
    printf ("created queue id : %d /n", qid );  /* 输出消息队列的 ID */
   
    system( "ipcs -q" );                        /* 查看系统 IPC 的状态 */
    exit ( 0 );
}

删除队列

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
int main ( int argc ,char *argv[] )
{
    int qid ;
    if ( argc != 2 ){ /* 命令行参数出错 */
        puts ( "USAGE: del_msgq.c <queue ID >" );
        exit ( 1 );
    }   
    qid = atoi ( argv[1] ); /* 通过命令行参数得到组 ID */
    system( "ipcs -q");
   
    if ( ( msgctl( qid, IPC_RMID, NULL ) ) < 0 ){ /* 删除指定的消息队列 */
        perror ("msgctl");
        exit (1 );
    }
    system( "ipcs -q");
    printf ( "successfully removed %d  queue/n", qid ); /* 删除队列成功 */
    exit( 0 );
}

发送队列

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
struct msg{                     /* 声明消息结构体 */
    long msg_types;             /* 消息类型成员 */   
    char msg_buf[511];          /* 消息 */
};
int main( void ) {
    int     qid;
    int         pid;
    int         len;
    struct msg pmsg;            /* 一个消息的结构体变量 */
   
    pmsg.msg_types = getpid();  /* 消息类型为当前进程的 ID*/
    sprintf (pmsg.msg_buf,"hello!this is :%d/n/0", getpid() ); /* 初始化消息 */
    len = strlen ( pmsg.msg_buf );   /* 取得消息长度 */
   
    if ( (qid=msgget(IPC_PRIVATE, IPC_CREAT | 0666)) < 0 ) {  /* 创建一个消 
                                                                   息队列 */
        perror ( "msgget" );
        exit (1) ;
    }
   
    if ( (msgsnd(qid, &pmsg, len, 0 )) < 0 ){   /* 向消息队列中发送消息 */
        perror ( "msgsn" );
        exit ( 1 );
    }
    printf ("successfully send a message to the queue: %d /n", qid);
    exit ( 0 ) ;
}

接收队列

#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFSZ 4096
struct msg{             /* 声明消息结构体 */
    long msg_types;     /* 消息类型成员 */   
    char msg_buf[511];  /* 消息 */
};
int main( int argc, char * argv[] ) 
{
    int     qid;
    int         len;
    struct msg pmsg;
    if ( argc != 2 ){  /**/
        perror ( "USAGE: read_msg <queue ID>" );
        exit ( 1 );
	}
qid = atoi ( argv[1] );   /* 从命令行中获得消息队列的 ID*/
    /* 从指定队列读取消息 */
    len = msgrcv ( qid, &pmsg, BUFSZ, 0, 0 );
   
    if ( len > 0 ){
        pmsg.msg_buf[len] = '/0';                       /* 为消息添加结束符 */
        printf ("reading queue id :%05ld/n", qid ); /* 输出队列 ID*/
        /* 该消息类型就是发送消息的进程 ID*/
        printf ("message type : %05ld/n", pmsg.msg_types );
        printf ("message length : %d bytes/n", len );   /* 消息长度 */
        printf ("mesage text: %s/n", pmsg.msg_buf); /* 消息内容 */
    }
    else if ( len == 0 )
        printf ("have no message from queue %d/n", qid );
    else {
        perror ( "msgrcv");
        exit (1);
    }
    system("ipcs -q")  
    exit ( 0 ) ;
}

示例

ipcs –q查看消息队列信息

/*
编写一对程序recv.c和send.c,前者负责接收并打印出消息,后者负责发送消息(由用户输入消息),允许两个进程都可以创建消息队列,但只有接收进程可以删除队列,当接收数据为“end”时结束。
*/
//recv
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>   
#include <sys/msg.h>
#include <errno.h>
#include <unistd.h>

/*
消息队列比作信箱
消息比作信
消息类型比作信的目标地址
消息内容比作信的真实内容
ipcs -q
ipcrm -q msgid
*/
#define MSGKEY 1024   

struct msgstru  
{  
    long msgtype;  
    char msgtext[2048];   
};  

int main(int argc, char *argv[])
{
    struct msgstru msgs;  
    int msgid,ret_value;  
    char str[512];  

    printf( "This is a read_process\r\n");

    while(1){  
        msgid = msgget(MSGKEY,IPC_EXCL );/*检查消息队列是否存在 */  
        if(msgid < 0){  
            printf("msq not existed! errno=%d [%s]\n",errno,strerror(errno));  
            sleep(2);  
            continue;  
        } 
        
		/*接收消息队列 msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
		* msgtyp:从消息队列内读取的消息形态。如果值为零,
		* 则表示消息队列中的所有消息都会被读取
		* msgflg : 0 表示阻塞等待,IPC_NOWAIT不等待
		*/  
        ret_value = msgrcv(msgid,&msgs,sizeof(struct msgstru)-sizeof(long), 0, 0);  
        printf("read_process read_msg:ret_value=[%d],type=[%ld],text=[%s]\n",ret_value,msgs.msgtype, 
        msgs.msgtext);  
   }     
   return 0;  
}

//send
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>   
#include <sys/msg.h>
#include <errno.h>

#define MSGKEY 1024   


struct msgstru  
{  
    long msgtype;  //消息类型type 的长度改不了
    char msgtext[2048];//消息内容可修改大小
};  

int main(int argc, char *argv[])
{ 
    struct msgstru msgs;  
    int msg_type;  
    char str[256];  
    int ret_value;  
    int msqid;  
    
    printf( "This is a write_process\n");  

    msqid=msgget(MSGKEY,IPC_EXCL);  /*检查消息队列是否存在*/  
    if(msqid < 0){  
         printf("msg not exist, so try to create one msg\r\n");
         msqid = msgget(MSGKEY,IPC_CREAT|0666);/*创建消息队列ipcs -q 显示消息队列的使用情况*/  
         if(msqid <0){  
         	printf("failed to create msq | errno=%d [%s]\n",errno,strerror(errno));  
         	return -1;
         }  
    }   
    
    while (1){  
        printf("input message type(end:0):");  
        scanf("%d",&msg_type);  
        if (msg_type == 0)  
            break;  
        printf("input message to be sent:");  
        scanf ("%s",str);  
        msgs.msgtype = msg_type;  
        strcpy(msgs.msgtext, str);  
		/* 发送消息队列 
		*  当msgflg为0时,msgsnd()及msgrcv()
		*  在队列呈满或呈空的情形时,采取阻塞等待的处理模式。IPC_NOWAIT
		*/  
        ret_value = msgsnd(msqid,&msgs,sizeof(struct msgstru)-sizeof(long),0); 
		printf("write_process=%d\r\n", ret_value);
        if ( ret_value < 0 ) {  
            printf("write_process write msg failed,errno=%d[%s]\n",errno,strerror(errno));  
            //return -1;
        }  
    }  
    printf("del the msg\r\n");
    msgctl(msqid,IPC_RMID,0); //删除消息队列   
    
   return 0;  
}


/*
创建server.c和client.c,客户端负责输入两个操作数和一个操作符,服务器端负责接收并根据操作符对两个操作数进行运算,最后将操作结果返回给客户端输出。如客户端输入2+4,服务器端将结果6返回给客户端。
*/
//client
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<time.h>
#include<sys/types.h>
struct msgbuf
{
   long mtype;
   char mtext[500];
};
int main()
{
	int msqid;
	int msqid1;
	int i;
	struct msgbuf buf;
	int flag;
	int sendlength;
	msqid = msgget((key_t)1111 , IPC_CREAT|0666);
	if(msqid<0)
	printf("ERROR");
	
	buf.mtype = 1;
	sendlength = sizeof(struct msgbuf) - sizeof(long);
	sprintf(buf.mtext,"Hello World");
	msgsnd(msqid,&buf,sendlength,0);
	msgrcv(msqid,&buf,sendlength,1,0);
	printf("%s",buf.mtext);
	for(i = 0 ; i<3;i++)
	{
	scanf("%s",buf.mtext);
	flag = msgsnd(msqid,&buf,sendlength,0);
	if(i == 3)
	break;
	}
	return  0;
}

//server
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<time.h>
#include<sys/types.h>
struct msgbuf
{
   long mtype;
   char mtext[500]; 
};
int main()
{
	int msqid;
	int msqid1;
	struct msgbuf buf;
	int flag;
	int number1,number2,sum,i;
	char *a;
	int recvlength;
	msqid = msgget(1111 , 0);
	if(msqid<0)
	printf("ERROR");
	buf.mtype = 1;
	recvlength = sizeof(struct msgbuf) - sizeof(long);
	for(i=0;i<3;i++)
	{
	flag = msgrcv(msqid,&buf,recvlength,1,0);
	if(i==0)
	{
	number1 = atoi(buf.mtext);
	printf("%d\n",number1);
	}
	else if(i==1)
	{
	number2 = atoi(buf.mtext);
	printf("%d\n",number2);
	}
	else if(i == 2)
	{
	  a = buf.mtext;
	  printf("%s\n",a);
	}
	}
	  if(strcmp(a, "*") == 0)
	  printf("%d\n",i = number1*number2);
	  else if(strcmp(a, "/") == 0)
	  printf("%d\n",i=number1/number2);
	  else if(strcmp(a, "+") == 0)
	  printf("%d\n",i=number1+number2);
	  else if(strcmp(a, "-") == 0)
	  printf("%d\n",i=number1-number2);
	  else
	  printf("ERROR!!!\n");
	  sprintf(buf.mtext,"%d",i);
	  msgsnd(msqid,&buf,recvlength,0);
	  printf("OVER\n");
	return  0;
}

image-20220602091030671

页面置换算法

#include <stdio.h>
#define FRAME_NUM 3
#define LEN 20
int a[LEN] = {7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1};
int c[FRAME_NUM] = {-1, -1, -1};
int lru[FRAME_NUM] = {3, 2, 1}; //访问时间计数
// 搜索帧序列中有要访问的页
int SEARCH(int n)
{
	for (int i = 0; i < FRAME_NUM; i++)
	{
		if (c[i] == n)
			return i;
	}
	return -1;
}
//输出操作后的帧序列
void SHOW(int n)
{
	printf("%d,", n);
	for (int i = 0; i < FRAME_NUM; i++)
	{
		if (c[i] == -1)
			printf(" *");
		else
			printf(" %d", c[i]);
	}
	printf("\n");
}
void RESET()
{
	c[0] = c[1] = c[2] = -1;
}
void FIFO()
{
	int p = 0;
	int count = 0;
	for (int i = 0; i < LEN; i++)
	{
		if (SEARCH(a[i]) >= 0)
		{ // 未缺页
			printf("%d ,\n", a[i]);
		}
		else
		{ // 缺页
			c[p] = a[i];
			count++;
			SHOW(a[i]);
			p = (p + 1) % FRAME_NUM;
		}
	}
	printf("错误率:%d\n", count);
}
// 在访问时间计数数组中搜索访问时间最长的
int LRUSEARCH()
{
	int max = -1, k = 0;
	for (int i = 0; i < FRAME_NUM; i++)
	{
		if (max < lru[i])
		{
			max = lru[i];
			k = i;
		}
	}
	return k;
}
void LRU()
{
	int count = 0;
	for (int i = 0; i < LEN; i++)
	{
		if (SEARCH(a[i]) >= 0)
		{ // 未缺页
			printf("%d ,\n", a[i]);
			for (int j = 0; j < FRAME_NUM; j++)
			{
				lru[j]++;
			}
			lru[SEARCH(a[i])] = 1;
		}
		else
		{ // 缺页
			for (int j = 0; j < FRAME_NUM; j++)
			{
				lru[j]++;
			}
			c[LRUSEARCH()] = a[i];
			lru[LRUSEARCH()] = 1;
			count++;
			SHOW(a[i]);
		}
	}
	printf("错误率:%d\n", count);
}
// 前向搜索最长时间不用的
int OPTSEARCH(int n)
{
	int max = 0, k = 0, count;
	if (c[n] == -1 && n < 3)
		return n;
	for (int i = 0; i < FRAME_NUM; i++)
	{
		count = 1;
		for (int j = n + 1; j < LEN; j++)
		{
			if (a[j] == c[i])
			{
				break;
			}
			else
			{
				count++;
			}
		}
		if (max < count)
		{
			max = count;
			k = i;
		}
	}
	return k;
}
void OPT()
{
	int count = 0;
	for (int i = 0; i < LEN; i++)
	{
		if (SEARCH(a[i]) >= 0)
		{ // 未缺页
			printf("%d,\n", a[i]);
		}
		else
		{ // 缺页
			c[OPTSEARCH(i)] = a[i];
			count++;
			SHOW(a[i]);
		}
	}
	printf("错误率:%d\n", count);
}
int main()
{
	RESET();
	printf("FIFO:\n");
	FIFO();
	RESET();
	printf("LRU:\n");
	LRU();
	RESET();
	printf("OPT:\n");
	OPT();
}

操作系统第六次实验

问题描述:

​ 假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。

image-20220616101518173

​ 哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。

​ 即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。

​ 这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。

问题要求

  • 5名哲学家,同时进入餐桌

  • 共计5个资源,且一名哲学家仅能获取其中固定的2个

  • 哲学家必须获取2个资源才能吃饭

  • 在哲学家获取、释放资源时打印信息(可以封装在PV操作中)

解题思路

  1. 首先建立五个哲学家的状态
    • 等待
    • 吃饭
    • 思考
  2. 建立叉子的互斥信号量和访问互斥锁
  3. 当哲学家当前状态为等待,并且周围两边叉子未被占用,则可以吃饭,状态改变为吃饭,如果叉子被占用,则需等待对应哲学家放回叉子才可以吃饭
  4. 吃饭结束恢复思考状态
  5. 创建线程并初始化信号量

代码

#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <pthread.h>
#include <time.h>


pthread_t tid[5]; //线程ID
pthread_mutex_t mutex; // 互斥锁
pthread_cond_t chopstick[5];//建立叉子互斥信号量
int id[5] = {0, 1, 2, 3, 4};
enum
{
    thinking,
    waiting,
    eating
} state[5];//建立哲学家状态
void eat(int n)
{
    pthread_mutex_lock(&mutex);//互斥锁加锁
    if ((state[(n + 4) % 5] != eating) &&(state[(n + 1) % 5] != eating)&&(state[n] == waiting))// 两边餐叉可用
    { 
        state[n] = eating;
        printf("%d 哲学家拿起了餐叉,开始吃饭\n", n);
        pthread_cond_signal(&chopstick[n]);//对叉子加锁
    }
    pthread_mutex_unlock(&mutex);//互斥锁减锁
}
void wait_chopstick(int n)
{
    state[n] = waiting;
    eat(n);
    pthread_mutex_lock(&mutex);
    if (state[n] != eating)//eat操作后没有顺利拿到叉子
    {                                             
        pthread_cond_wait(&chopstick[n], &mutex); // 等待别的哲学家放回叉子
    }
    pthread_mutex_unlock(&mutex);
}
void return_chopstick(int n)
{
    state[n] = thinking;
    printf("哲学家 %d 放回了餐叉\n", n);
    // 筷子左右的哲学家尝试拿叉子
    eat((n + 4) % 5);
    eat((n + 1) % 5);
}
void *run(void *arg)
{
    int id = *(int *)arg;
    while (1)
    {
        sleep(3); // 思考时间
        wait_chopstick(id);
        sleep(2); // 吃饭时间
        return_chopstick(id);
    }
}
int main()
{
    int i,j;
    pthread_mutex_init(&mutex, NULL); // 初始化信号量
    for (i = 0; i < 5; i++)
    {
        state[i] = thinking;                    // 初始化为思考状态
        pthread_cond_init(&chopstick[i], NULL); // 初始化条件变量
    }
    for (i = 0; i < 5; i++)
    {
        pthread_create(&tid[i], NULL, run, &id[i]); // 创建线程
    }
    for (j = 0; j < 5; j++)
    {
        pthread_join(tid[j], NULL); // 等待线程
    }
    return 0;
}
/*
5名哲学家,同时进入餐桌
共计5个资源,且一名哲学家仅能获取其中**固定**的2个
哲学家必须获取2个资源才能吃饭
在哲学家获取、释放资源时打印信息(可以封装在PV操作中)
*/

运行截图

image-20220616134039136

44b1e0f75a9663017dcc1a31e53bf3db