个人博客地址
一、实验内容
- 模拟操作系统中进程同步和互斥。
二、实验目的
- 熟悉临界资源、信号量及PV操作的定义与物理意义
- 了解进程通信的方法
- 掌握进程互斥与进程同步的相关知识
- 掌握用信号量机制解决进程之间的同步与互斥问题
- 实现生产者-消费者问题,深刻理解进程同步问题
三、实验题目
进程实现
在Linux操作系统下用C实现经典同步问题:生产者—消费者,具体要求如下:
- 一个大小为10的缓冲区,初始状态为空。
- 2个生产者,随机等待一段时间,往缓冲区中添加数据,若缓冲区已满,等待消费者取走数据之后再添加,重复10次。
- 2个消费者,随机等待一段时间,从缓冲区中读取数据,若缓冲区为空,等待生产者添加数据之后再读取,重复10次。
提示
本实验的主要目的是模拟操作系统中进程同步和互斥。在系统进程并发执行异步推进的过程中,由于资源共享和进程间合作而造成进程间相互制约。进程间的相互制约有两种不同的方式。
- 间接制约。这是由于多个进程共享同一资源(如CPU、共享输入/输出设备)而引起的,即共享资源的多个进程因系统协调使用资源而相互制约。
- 直接制约。只是由于进程合作中各个进程为完成同一任务而造成的,即并发进程各自的执行结果互为对方的执行条件,从而限制各个进程的执行速度。
生产者和消费者是经典的进程同步问题,在这个问题中,生产者不断的向缓冲区中写入数据,而消费者则从缓冲区中读取数据。生产者进程和消费者对缓冲区的操作是互斥,即当前只能有一个进程对这个缓冲区进行操作,生产者进入操作缓冲区之前,先要看缓冲区是否已满,如果缓冲区已满,则它必须等待消费者进程将数据取出才能写入数据,同样的,消费者进程从缓冲区读取数据之前,也要判断缓冲区是否为空,如果为空,则必须等待生产者进程写入数据才能读取数据。
在这个问题当中,我们采用信号量机制进行进程之间的通信,设置两个信号量,空的信号量和满的信号量。在Linux系统中,一个或多个信号量构成一个信号量集合。使用信号量机制可以实现进程之间的同步和互斥,允许并发进程一次对一组信号量进行相同或不同的操作。每个P、V操作不限于减1或加1,而是可以加减任何整数。在进程终止时,系统可根据需要自动消除所有被进程操作过的信号量的影响
-
缓冲区采用循环队列表示,利用头、尾指针来存放、读取数据,以及判断队列是否为空。缓冲区中数组大小为10;
-
利用随机函数rand()得到A~Z的一个随机字符,作为生产者每次生产的数据,存放到缓冲区中;
-
使用shmget()系统调用实现共享主存段的创建,shmget()返回共享内存区的ID。对于已经申请到的共享段,进程需把它附加到自己的虚拟空间中才能对其进行读写。
-
信号量的建立采用semget()函数,同时建立信号量的数量。在信号量建立后,调用semctl()对信号量进行初始化,例如本实验中,可以建立两个信号量SEM_EMPTY、SEM_FULL,初始化时设置SEM_EMPTY为10,SEM_FULL为0。使用操作信号的函数semop()做排除式操作,使用这个函数防止对共享内存的同时操作。对共享内存操作完毕后采用shmctl()函数撤销共享内存段。
-
使用循环,创建2个生产者以及2个消费者,采用函数fork()创建一个新的进程。
-
一个进程的一次操作完成后,采用函数fflush()刷新缓冲区。
-
程序最后使用semctl()函数释放内存。
- 主程序流程图:
- 生产者进程流程图
- 消费者进程流程图
- P操作流程图
- V操作流程图
思考题 线程实现
采用线程来实现
四、实验设计与过程
进程实现
预备知识
与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。
1、shmget()函数
该函数用来创建共享内存,它的原型为:
int shmget(key_t key, size_t size, int shmflg);
-
第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
-
第二个参数,size以字节为单位指定需要共享的内存容量
-
第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
2、shmat()函数
– at:attach
第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
-
第一个参数,shm_id是由shmget()函数返回的共享内存标识。
-
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
-
第三个参数,shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
3、shmdt()函数
– dt:detach
该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:
int shmdt(const void *shmaddr);
参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.
4、shmctl()函数
– ctl:control
与信号量的semctl()函数一样,用来控制共享内存,它的原型如下:
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是一个结构指针,它指向共享内存模式和访问权限的结构。
shmid_ds结构 至少包括以下成员:
struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; };
数据结构和符号说明
//信号集合内的每个信号量的索引,
#define SEM_FULL 0
#define SEM_EMPTY 1
#define MUTEX 2
//缓冲区结构
struct my_buffer
{
int out;
int in;
char str[MAX_BUFFER_SIZE];
int num; //缓冲区里字母数量
int is_empty;
};
const int N_CONSUMER = 2; //消费者数量
const int N_PRODUCER = 2; //生产者数量
const int N_BUFFER = 10; //缓冲区容量
const int N_WORKTIME = 10; //工作次数
int shm_id = -1;//信号量id
int sem_id = -1;//共享内存id
//子进程的id
pid_t consumer_id;
pid_t producer_id;
//得到10以内的一个随机数
int get_random()
//sem_id 表示信号量集合的 id
//sem_num 表示要处理的信号量在信号量集合中的索引
//P操作
void waitSem(int sem_id, int sem_num)
//V操作
void signalSem(int sem_id, int sem_num)
//打印进程运行时间
void printTime()
//消费者进程的消费和信息打印
void consume_print(int i, struct my_buffer *shmptr)
//消费者进程的消费和信息打印
void consume_print(int i, struct my_buffer *shmptr)
源代码
在main函数中,首先初始化信号量(mutex表示缓冲区的互斥访问,设为1;SEM_FULL表示缓冲区满的数量,设为0;SEM_EMPTY表示缓冲区空的数量,设为10),分配共享内存,初始化缓冲区,创建生产者与消费者,释放内存再退出。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_BUFFER_SIZE 10
#define SHM_MODE 0600
#define SEM_MODE 0600
//信号集合内的每个信号量的索引,
#define SEM_FULL 0
#define SEM_EMPTY 1
#define MUTEX 2
//缓冲区结构
struct my_buffer
{
int out;
int in;
char str[MAX_BUFFER_SIZE];
int num; //缓冲区里字母数量
int is_empty;
};
const int N_CONSUMER = 2; //消费者数量
const int N_PRODUCER = 2; //生产者数量
const int N_BUFFER = 10; //缓冲区容量
const int N_WORKTIME = 10; //工作次数
//信号量id
int shm_id = -1;
//共享内存id
int sem_id = -1;
//子进程的id
pid_t consumer_id;
pid_t producer_id;
//得到10以内的一个随机数
int get_random()
{
int digit;
digit = rand() % 3;
return digit;
}
//得到A~Z的一个随机字母
char getRandChar()
{
char letter;
letter = (char)((rand() % 26) + 'A');
return letter;
}
//sem_id 表示信号量集合的 id
//sem_num 表示要处理的信号量在信号量集合中的索引
//P操作
void waitSem(int sem_id, int sem_num)
{
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1; //表示要把信号量减一
sb.sem_flg = 0; //
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if (semop(sem_id, &sb, 1) < 0)
{
perror("waitSem failed");
exit(1);
}
}
//V操作
void signalSem(int sem_id, int sem_num)
{
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1;
sb.sem_flg = 0;
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if (semop(sem_id, &sb, 1) < 0)
{
perror("signalSem failed");
exit(1);
}
}
//打印进程运行时间
void printTime()
{
//打印时间
time_t now;
struct tm *timenow; //实例化tm结构指针
time(&now);
timenow = localtime(&now);
printf("| %02d:%02d:%02d | ", timenow->tm_hour, timenow->tm_min, timenow->tm_sec);
}
//生产者进程的生产和信息打印
void produce_print(int i, struct my_buffer *shmptr)
{
//sleep(get_random()); //随机睡眠一段时间,相当于人为扩大进程执行操作时间
printTime(); //程序运行时间
//生产产品
char c = getRandChar(); //随机获取字母
shmptr->str[shmptr->in] = c;
shmptr->in = (shmptr->in + 1) % MAX_BUFFER_SIZE;
shmptr->is_empty = 0; //写入新产品
shmptr->num++;
//打印缓冲区
int p = (shmptr->in - 1 >= shmptr->out) ? (shmptr->in - 1) : (shmptr->in - 1 + MAX_BUFFER_SIZE);
for (p; !(shmptr->is_empty) && p >= shmptr->out; p--)
{
printf("%c", shmptr->str[p % MAX_BUFFER_SIZE]);
}
for (int j = 0; j < 10 - shmptr->num; j++)
{
printf("%c", '-');
}
printf(" | "); //控制输出格式
printf("producer_%d | ", i + 1); //进程id信息
printf("+ %c |\n", c); //进程操作
fflush(stdout);
}
//消费者进程的消费和信息打印
void consume_print(int i, struct my_buffer *shmptr)
{
//sleep(get_random());
printTime(); //程序运行时间
/*生产产品*/
char lt = shmptr->str[shmptr->out];
shmptr->out = (shmptr->out + 1) % MAX_BUFFER_SIZE;
shmptr->is_empty = (shmptr->out == shmptr->in); //
shmptr->num--;
//打印缓冲区
int p = (shmptr->in - 1 >= shmptr->out) ? (shmptr->in - 1) : (shmptr->in - 1 + MAX_BUFFER_SIZE);
for (p; !(shmptr->is_empty) && p >= shmptr->out; p--)
{
printf("%c", shmptr->str[p % MAX_BUFFER_SIZE]);
}
for (int j = 0; j < 10 - shmptr->num; j++)
{
printf("%c", '-');
}
printf(" | "); //控制输出格式
printf("consumer_%d | ", i + 1); //进程id信息
printf("- %c |\n", lt); //进程操作
fflush(stdout);
}
int main(int argc, char **argv)
{
srand((unsigned)(getpid() + time(NULL)));
shm_id = shmget(IPC_PRIVATE, MAX_BUFFER_SIZE, SHM_MODE); //申请共享内存
if (shm_id < 0)
{
perror("create shared memory failed");
exit(1);
}
struct my_buffer *shmptr;
shmptr = shmat(shm_id, 0, 0); //将申请的共享内存附加到申请通信的进程空间
if (shmptr == (void *)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
if ((sem_id = semget(IPC_PRIVATE, 3, SEM_MODE)) < 0)
{ //创建三个信号量,SEM_EMPTY,SEM_FULL和MUTEX
perror("create semaphore failed! \n");
exit(1);
}
if (semctl(sem_id, SEM_FULL, SETVAL, 0) == -1)
{ //将索引为0的信号量设置为0-->SEM_FULL
perror("sem set value error! \n");
exit(1);
}
if (semctl(sem_id, SEM_EMPTY, SETVAL, 10) == -1)
{ //将索引为1的信号量设置为10-->SEM_EMPTY
perror("sem set value error! \n");
exit(1);
}
if (semctl(sem_id, MUTEX, SETVAL, 1) == -1)
{ //将索引为3的信号量设置为1-->MUTEX
perror("sem set value error! \n");
exit(1);
}
//初始化缓冲区
shmptr->out = 0;
shmptr->in = 0;
shmptr->is_empty = 1;
shmptr->num = 0;
printf("---------------------Process Execution Table-----------------\n");
printf("-------------------------------------------------------------\n");
printf("| time | buffer data |current process| operation |\n");
printf("-------------------------------------------------------------\n");
for (int i = 0; i < N_PRODUCER; i++)
{
sleep(get_random());
producer_id = fork();
if (producer_id < 0)
{
perror("the fork failed");
exit(1);
}
else if (producer_id == 0) //是子进程则执行生产操作
{
for (int j = 0; j < N_WORKTIME; j++)
{
sleep(get_random());
waitSem(sem_id, SEM_EMPTY);
waitSem(sem_id, MUTEX);
produce_print(i, shmptr);
signalSem(sem_id, MUTEX);
signalSem(sem_id, SEM_FULL);
}
exit(0);
}
}
for (int i = 0; i < N_CONSUMER; i++)//
{
sleep(get_random());
consumer_id = fork();
if (consumer_id < 0) //调用fork失败
{
perror("the fork failed");
exit(1);
}
else if (consumer_id == 0) //是子进程则执行消费操作
{
for (int j = 0; j < N_WORKTIME; j++)
{
sleep(get_random());
waitSem(sem_id, SEM_FULL);
waitSem(sem_id, MUTEX);
consume_print(i, shmptr);
signalSem(sem_id, MUTEX);
signalSem(sem_id, SEM_EMPTY);
}
exit(0);
}
}
//主进程最后退出
while (wait(0) != -1)
;
//将共享段与进程之间解除连接
shmdt(shmptr);
//对共享内存区执行控制操作
shmctl(shm_id, IPC_RMID, 0); //当cmd为IPC_RMID时,删除该共享段
printf("-------------------------------------------------------------\n");
printf("主进程运行结束!\n");
fflush(stdout);
exit(0);
return 0;
}
程序初值和运行结果
进程
线程实现
预备知识
pthread_create
pthread_create是UNIX环境创建线程函数
头文件:
#include<pthread.h>
函数声明:
int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);
参数:
- 第一个参数为指向线程标识符的指针,可用如下方式定义 pthread_t thread1;
- 第二个参数用来设置线程属性。
- 第三个参数是线程运行函数的地址,即函数名,函数内包括循环。
- 最后一个参数是运行函数的参数。
返回值:
若成功则返回0,否则返回出错编号
注意:
在编译时注意加上-l pthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。
pthread_join
函数简介:
函数pthread_join用来等待一个线程的结束。pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
头文件:
#include<pthread.h>
函数声明:
int pthread_join(pthread_t thread, void **retval);
参数:
-
thread: 线程标识符,即线程ID,标识唯一线程,为被等待的线程标识符。
-
retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值:
如果执行成功,将返回0,如果失败则返回一个错误号。
数据结构和符号说明
const int N_WORKTIME = 10; //工作次数
//三个信号量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //线程间互斥
sem_t full; //填充的个数
sem_t empty; //空槽的个数
struct my_buffer
{
int out;
int in;
char str[MAX_BUFFER_SIZE];
int num; //缓冲区里字母数量
int is_empty;
} buffer; //缓冲区结构体
//struct my_buffer *buffer
int get_random()//得到3以内的一个随机数
char getRandChar()//得到A~Z的一个随机字母
void printTime() //打印时间
void produce_print(int *x)//生产者进程的生产和信息打印
void consume_print(int *x)//消费者进程的生消费和信息打印
void *produce(void *arg)//生产者线程
{
int i;
int *x = (int *)arg;
for (i = 0; i < N_WORKTIME; i++)
{
sleep(get_random());
sem_wait(&empty); //若空槽个数低于0阻塞
pthread_mutex_lock(&mutex);
produce_print(x);
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
return (void *)1;
}
void *consume(void *arg)//消费者线程
{
int i;
int *x = (int *)arg;
for (i = 0; i < N_WORKTIME; i++)
{
sleep(get_random());
sem_wait(&full); //若填充个数低于0阻塞
pthread_mutex_lock(&mutex);
consume_print(x);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
return (void *)2;
}
源代码
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAX_BUFFER_SIZE 10
const int N_WORKTIME = 10; //工作次数
//三个信号量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //线程间互斥
sem_t full; //填充的个数
sem_t empty; //空槽的个数
struct my_buffer
{
int out;
int in;
char str[MAX_BUFFER_SIZE];
int num; //缓冲区里字母数量
int is_empty;
} buffer; //缓冲区结构体
//struct my_buffer *buffer;
//得到3以内的一个随机数
int get_random()
{
int digit;
digit = rand() % 3;
return digit;
}
//得到A~Z的一个随机字母
char getRandChar()
{
char letter;
letter = (char)((rand() % 26) + 'A');
return letter;
}
void printTime() //打印时间
{
time_t now;
struct tm *timenow; //实例化tm结构指针
time(&now);
timenow = localtime(&now);
printf("| %02d:%02d:%02d | ", timenow->tm_hour, timenow->tm_min, timenow->tm_sec);
}
void produce_print(int *x)//生产者进程的生产和信息打印
{
//sleep(get_random());
printTime(); //程序运行时间
/*生产产品*/
char c = getRandChar(); //随机获取字母
buffer.str[buffer.in] = c;
buffer.in = (buffer.in + 1) % MAX_BUFFER_SIZE;
buffer.is_empty = 0; //写入新产品
buffer.num++;
//打印缓冲区
int p = (buffer.in - 1 >= buffer.out) ? (buffer.in - 1) : (buffer.in - 1 + MAX_BUFFER_SIZE);
for (p; !(buffer.is_empty) && p >= buffer.out; p--)
{
printf("%c", buffer.str[p % MAX_BUFFER_SIZE]);
}
for (int j = 0; j < 10 - buffer.num; j++)
{
printf("%c", '-');
}
printf(" | "); //控制输出格式
printf("producer_%d | ", *x); //进程id信息
printf("+ %c |\n", c); //进程操作
}
void consume_print(int *x)//消费者进程的生消费和信息打印
{
//sleep(get_random());
printTime(); //程序运行时间
/*生产产品*/
char c = buffer.str[buffer.out];
buffer.out = (buffer.out + 1) % MAX_BUFFER_SIZE;
buffer.is_empty = (buffer.out == buffer.in); //
buffer.num--;
//打印缓冲区
int p = (buffer.in - 1 >= buffer.out) ? (buffer.in - 1) : (buffer.in - 1 + MAX_BUFFER_SIZE);
for (p; !(buffer.is_empty) && p >= buffer.out; p--)
{
printf("%c", buffer.str[p % MAX_BUFFER_SIZE]);
}
for (int j = 0; j < 10 - buffer.num; j++)
{
printf("%c", '-');
}
printf(" | "); //控制输出格式
printf("consumer_%d | ", *x); //进程id信息
printf("- %c |\n", c); //进程操作
}
void *produce(void *arg)
{
int i;
int *x = (int *)arg;
for (i = 0; i < N_WORKTIME; i++)
{
sleep(get_random());
sem_wait(&empty); //若空槽个数低于0阻塞
pthread_mutex_lock(&mutex);
produce_print(x);
pthread_mutex_unlock(&mutex);
sem_post(&full);
}
return (void *)1;
}
void *consume(void *arg)
{
int i;
int *x = (int *)arg;
for (i = 0; i < N_WORKTIME; i++)
{
sleep(get_random());
sem_wait(&full); //若填充个数低于0阻塞
pthread_mutex_lock(&mutex);
consume_print(x);
pthread_mutex_unlock(&mutex);
sem_post(&empty);
}
return (void *)2;
}
int main(int argc, char *argv[])
{
srand(time(NULL)); //随机化时间种子
pthread_t thid1;
pthread_t thid2;
pthread_t thid3;
pthread_t thid4;
int ret1;
int ret2;
int ret3;
int ret4;
//初始化信号量
sem_init(&full, 0, 0);
sem_init(&empty, 0, 10);
//线程id
int t1 = 1;
int t2 = 2;
//初始化缓冲区
buffer.out = 0;
buffer.in = 0;
buffer.is_empty = 1;
buffer.num = 0;
printf("---------------------Thread Execution Table------------------\n");
printf("-------------------------------------------------------------\n");
printf("| time | buffer data | current thread | operation |\n");
printf("-------------------------------------------------------------\n");
//创建线程
pthread_create(&thid1, NULL, produce, (void *)&t1);
pthread_create(&thid2, NULL, consume, (void *)&t1);
pthread_create(&thid3, NULL, produce, (void *)&t2);
pthread_create(&thid4, NULL, consume, (void *)&t2);
//阻塞直到该线程结束
pthread_join(thid1, (void **)&ret1);
pthread_join(thid2, (void **)&ret2);
pthread_join(thid3, (void **)&ret3);
pthread_join(thid4, (void **)&ret4);
printf("-------------------------------------------------------------\n");
printf("线程全部执行完毕,主进程运行结束!\n");
return 0;
}
程序初值和运行结果
线程
五、实验总结
本次实验中我学习到了如何利用共享内存通信来实现进程间的同步和互斥,开始时对shmget、shmat、shmdt、shmctl这些函数不是很了解,之后通过查阅资料了解了他们的用法,在编写代码的过程也遇到许多困难,在最后调试的时候还遇到了进程死锁的问题,后来看了半天才发现pv操作中给信号量设计的参数错误,导致每次进程结束都会被重置,这导致了死锁。但好在最后都发现了错误,顺利解决。
思考题让我们用线程实现,这比进程要容易很多,只需要申请一个全局变量就可以作为共享内存了,对于线程需要学习pthread_create 和pthread_join用法,最后大部分函数设计沿用进程的设计,成功实现!