[TOC]
实验一 OS启动过程、进程的概念及cmd.exe中的命令等
1、 实验目的
- 了解计算机启动过程的原理。
- CPU 加电后运行 BIOS 程序
- BIOS 进行自检和初始化
- BIOS 将磁盘引导扇区(MBR)加载到内存中
- 跳转执行引导程序BOOT,boot.bin
- 引导程序将Loader.bin加载到内存中
- 跳转执行加载程序
- 加载程序将Kernel.dll加载到内存中,并进入保护模式、启动分页机制(扩展内存可用),创建初始进程。
- 调用内核入口点,开始执行内核,进入桌面
- explorer.exe
- 计算机程序运行的机制
- 程序以进程的形式进入内存。
- 一般指令运行的过程:
- 取指周期:PC中存放现行指令的地址,该地址送到MAR并送至地址总线,由控制部件CU向存储器发出读命令,使对应MAR所指单元的内容(指令)经数据总线送至MDR,再送至IR,并由CU控制PC内容加1,形成下一条指令的地址。
- 中断周期:CU把保存程序断点的内存地址送到MAR,并送到地址总线上,由CU向存储器发出写命令,并PC的内容(程序断点)送到MDR,经数据总线存入存储器。CU还将中断服务程序的入口地址送到PC,为下一个指令周期的取指做准备。
- 创建进程、撤销进程原语程序fork()、exec()和exit()CreateProcess()和ExitProcess()
2、实验时间:2学时,分两次
3、实验任务:
- 百度fork()、exec()和exit()源程序,写出它们的功能。
- 验证:通过启动任务管理器(WIN+R-taskmgr,ctrl+shift+esc和ctrl+alt+delete)运行和撤销资源管理器:explorer.exe
- 了解cmd.exe所拥有的内部和外部命令。通过WIN+R运行了解功能,简写几个命令!
- 了解任务管理器中显示的当前运行的各个进程。了解它们的功能,简写几个进程!
- 验证命令解释程序的运行。
- 练习如何把U盘或移动硬盘的MBR分区转换成GPT分区。写出命令行。
4、实验过程:
实验一:百度fork()、exec()和exit()源程序,写出它们的功能
Fork():一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
Exec():exec有两个功能:
-
取代当前的shell,通过给出的命令程序。即命令执行完成后,会退出本shell。
比如:exec ls
在shell中执行ls,ls结束后不返回原来的shell中。
-
文件重定向,可以将文件的重定向看作是是shell程序的文件重定向 比如:比如 exec 5</dev/null;exec 5<&-
exec 3>file 将写入fd3中的内容写入到file中
exec 5<file 将file读入到fd5中
exec 5<&- 关闭fd5
最后发现当前脚本中exec的功能是执行完spark的启动脚本后,就退出shell,所以导致脚本后面的的两个命令都没有执行,结尾用echo输出也没有任何内容打印。
最后的解决方法是删除exec,文件就可以正常打印了。
Exit():0表示正常退出其他表示非正常一般用-1表示
使用 exit函数,要包含头文件” stdlib.h”。。使用exit()时,可以不论main()的返回类型。
实验二:通过启动任务管理器运行和撤销资源管理器

内部命令:我们可以直接在cmd下就可以执行的命令,例如:telnet、ftp、cd、等等,你可以在CMD下输入help进行查看
外部命令:就是cmd下不能直接运行的命令,(例如大家常用的nc)他需要在CMD下切换到他(NC)所在的目录你才能运行NC及NC的命令


实验四: 了解任务管理器中显示的当前运行的各个进程
火绒安全软件弹窗拦截程序:

应用程序 拦截广告的
Csrss: windows 的核心进程之一。
Idle:任务管理器看到显示99%占用率 表示目前还有99%的性能等待你使用。
转换分区的方法练习:
方法一:convert(文件系统修改命令)
语法:convert [Volume] /fs:ntfs
Convert将文件分配表 (FAT) 和 FAT32 卷转换为 NTFS 文件系统,而现有的文件和文件夹完好无损。被转换为 NTFS 文件系统的卷无法再转换回 FAT 或 FAT32。
方法二:
1. cmd
2. diskpart
3. listdisk
4. select disk=*
5. clean
6. convert GPT
会出现异常情况:除C,其他分区都显示不出来了。
解决方法 :重启
运行:diskmgmt.msc
实验五 : 验证命令解释程序的运行
附一个命令解释程序的源程序:
一个命令解释程序:
#include<stdio.h>
#include<string.h>
void main()
{
char mingling[15][15]= {"dir","cd","md","rd","cls","date","time","ren","cope","help","quit"};
char ch[15];
printf("Microsoft Windows XP [版本 5.1.2600]\n");
printf("<c> 版权所有 1985-2001 Microsoft Corp.\n");
printf("\nc:\\Users\\Administrator>");
while(1)
{
gets(ch);//读入字符串
if(strcmp(ch,mingling[0])==0)
{
printf("dir 内部命令 显示一个目录中的文件和子目录\n");
}
else if(strcmp(ch,mingling[1])==0)
{
printf("cd 内部命令 显示当前目录的名称或将其更改。\n");
}
else if(strcmp(ch,mingling[2])==0)
{
printf("md 内部命令 创建一个目录。\n");
}
else if(strcmp(ch,mingling[3])==0)
{
printf("rd 内部命令 删除目录。\n");
}
else if(strcmp(ch,mingling[4])==0)
{
printf("cls 内部命令 清除屏幕\n");
}
else if(strcmp(ch,mingling[5])==0)
{
printf("date 内部命令 显示或设置日期\n");
}
else if(strcmp(ch,mingling[6])==0)
{
printf("time 内部命令 内部命令显示或设置系统时间\n");
}
else if(strcmp(ch,mingling[7])==0)
{
printf("ren 内部命令 重新命名文件\n");
}
else if(strcmp(ch,mingling[8])==0)
{
printf("cope 内部命令 将至少一个文件复制到另一个位置\n");
}
else if(strcmp(ch,mingling[9])==0)
{
printf("dir 内部命令 显示一个目录中的文件和子目录\n");
printf("cd 内部命令 显示当前目录的名称或将其更改。\n");
printf("md 内部命令 创建一个目录。\n");
printf("rd 内部命令 删除目录。\n");
printf("cls 内部命令 清除屏幕\n");
printf("date 内部命令 显示或设置日期\n");
printf("time 内部命令 内部命令显示或设置系统时间\n");
printf("ren 内部命令 重新命名文件\n");
printf("cope 内部命令 将至少一个文件复制到另一个位置\n");
printf("help 外部命令 提供Windows命令的帮助信息\n");
printf("quit 外部命令 退出命令解释程序\n");
}
else if(strcmp(ch,mingling[10])==0)
return;
else
{
printf("你输入错误的信息,请重新输入:\n");
}
}
}
实验六:练习如何把U盘或移动硬盘的MBR分区转换成GPT分区
- diskpart(启动Diskpart程序)
- list disk (查看电脑中有哪些磁盘)
- select disk 0(选中编号为0的磁盘)
- clean(清除磁盘所有分区)
- convert gpt(将磁盘转换成GPT格式)
- list partition(查看当前磁盘分区情况)
- create partition efi size=100(默认大小为M)
- create partition msr size =128
- create partition primary size =102400(此处为你想设置C盘的大小)
- 两次输入exit
实验二 P、V算法
1、 实验目的
一、 Dekker算法
设有进程P0和P1,两者谁要访问临界区,就让对应的flag=true(例如P0要访问临界区,就让flag[0]=true),相当于“举手示意我要访问”。初始值为0表示一开始没人要访问
turn用于标识当前允许谁进入,turn=0则P0可进入,turn=1则P1可进入。
二、 Peterson算法:
Peterson算法是一个实现互斥锁的并发程序设计算法,可以控制两个线程访问一个共享的单用户资源而不发生访问冲突。Gary L. Peterson于1981年提出此算法
三、生产者和消费者问题。
(1)加深对进程概念的理解,明确进程和程序的区别。
(2)进一步理解同步关系的含义。
(3)验证用信号量机制实现进程互斥的方法。
(4) 验证用信号机制实现进程同步的方法。
四、 读者和写者算法
(1) 写者优先
(2) 读者优先
五、哲学家就餐算法
六、验证参考程序
七、 编写自己的程序
2、实验时间:4学时(2次)
3、实验任务:
(1)验证参考程序Java,观察程序运行的结果,理解信号量的作用,从运行结果中体现出三种同步情况。
(2)实现方法:多进程实现和多线程实现
(3)编写自己的程序(可用其他语言编写)
4、实验过程:
一、Dekker算法
P0的逻辑
do{
flag[0] = true;// 首先P0举手示意我要访问
while(flag[1]) // 看看P1是否也举手了
{
if(turn==1) // 如果P1也举手了,那么就看看到底轮到谁
{
flag[0]=false;// 如果确实轮到P1,那么P0先把手放下(让P1先)
while(turn==1);// 只要还是P1的时间,P0就不举手,一直等
flag[0]=true;// 等到P1用完了(轮到P0了),P0再举手
}
flag[1] = false; // 只要可以跳出循环,说明P1用完了,应该跳出最外圈的while
}
visit();// 访问临界区
turn = 1;// P0访问完了,把轮次交给P1,让P1可以访问
flag[0]=false;// P0放下手
}
P1的逻辑
do{
flag[1] = true;// 先P1举手示意我要访问
while(flag[0]) // 如果P0是否也举手了
{
if(turn==0) // 如果P0也举手了,那么久看看到底轮到谁
{
flag[1]=false;// 如果确实轮到P0,那么P1先把手放下(让P0先)
while(turn==0);// 只要还是P0的时间,P1就不举手,一直等
flag[0]=true;// 等到P0用完了(轮到P1了),P1再举手
}
}
visit();// 访问临界区
turn = 0;// P1访问完了,把轮次交给P0,让P0可以访问
flag[1]=false;// P1放下手
}
实现方法:
#include<iostream>
using namespace std;
bool flag[2];
int turn;
void procedure0()
{
while(true)
{
flag[0]=true;
while(flag[1]==true)
{
if(turn==1)
{
flag[0]=false;
while(turn==1)
{
/*do nothing*/
}
flag[0]=true;
}
}
/*critical section*/ ;
turn=1;
flag[0]=false;
/*remainder section*/
}
}
void procedure1()
{
while(true)
{
flag[1]=true;
while(flag[0]==true)
{
if(turn==0)
{
flag[1]=false;
while(turn==0)
{
/*do nothing*/
}
flag[1]=true;
}
}
/*critical section*/ ;
turn=0
flag[1]=false;
/*remainder section*/
}
}
int main()
{
flag[0]=flag[1]=0;
turn=1;
//先让P1运行
/*start procedure0 and procedure1*/ ;
return 0;
}
二、Peterson算法
boolean flag[2];
int turn;
void procedure0()
{
while(true)
{
flag[0]=true;
turn=1;
while(flag[1]&&turn==1) /*若flag[1]为false,P0就进入临界区;若flag[1]为tureP0循环等待,只要P1退出临界区,P0即可进入*/
{
/* donothing*/
}
visit();/*访问临界区*/
flag[0]=false;/*访问临界区完成,procedure0释放出临界区*/
/*remainder section*/
}
}
void procedure1()
{
while(true)
{
flag[1]=true;
turn=0;
while(flag[0]&&turn==0)
{
/* donothing*/ ;
}
visit();/*访问临界区*/
flag[1]=false;/*访问临界区完成,procedure1释放出临界区*/
/*remainder section*/
}
}
void main()
{
flag[0]=flag[1]=false;
/*start procedure0 and procedure1*/ ;
}
进程P0,一旦它设置flag[0]=true,则P1不能进入临界区。如果P1已经进入临界区,那么flag[1]=true,P0被阻塞不能进入临界区。
另一方面,互相阻塞也避免了。假设P0在while里被阻塞了,表示flag[1]为true且turn=1,则此时P1可以执行。
三、生产者和消费者问题
考虑有一些生产者和消费者进程,生产者进程生产信息并把它们放入缓冲池中,消费者从缓冲池中取走信息。生产者—消费者问题是相互合作的进程关系的一种抽象,如在输入时,输入进程是生产者,计算进程是消费者;而在输出时,则计算进程是生产者,打印进程是消费者。请使用信号量机制来解决生产者—消费者问题。
归结生产者和消费者之间的关系:
(1) 诸进程之间互斥访问缓冲池
(2) 缓冲池满时,生产者等待
(3) 缓冲池空时,消费者等待
package com.loan.entity;
public class Store
{
private final int MAX_SIZE=2;//仓库总共可存放货物
private int count=0;//当前仓库货物
public synchronized void add() throws InterruptedException
{
while(count>=MAX_SIZE)
{
System.out.println("仓库已满");
System.out.println(Thread.currentThread().getName()+"等待中。。。。");
this.wait();
}
count++;
System.out.println(Thread.currentThread().getName()+"存入仓库,当前货物数:"+count);
this.notify();
}
public synchronized void remove() throws InterruptedException
{
while(count<=0)
{
System.out.println("仓库空了");
System.out.println(Thread.currentThread().getName()+"等待中。。。。");
this.wait();
}
count--;
System.out.println(Thread.currentThread().getName()+"取出货物,当前货物数:"+count);
this.notify();
}
public static void main(String[] args)
{
Store s=new Store();
Thread producer1=s.new Producer(s);//成员内部类需通过对象访问
Thread producer2=s.new Producer(s);
Thread consumer1=s.new Consumer(s);
Thread consumer2=s.new Consumer(s);
producer1.setName("producer1");//利用Thread中的方法
producer2.setName("producer2");
consumer1.setName("consumer1");
consumer2.setName("consumer2");
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
}
class Producer extends Thread
{
private Store store;
Producer(Store s)
{
store=s;
}
public void run()
{
while(true)
{
try
{
store.add();
Thread.sleep(10000);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer extends Thread
{
private Store store;
Consumer(Store s)
{
store=s;
}
public void run()
{
while(true)
{
try
{
store.remove();
Thread.sleep(15000);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
四、读者和写者算法
C程序:
代码中信号量解析:
设置五个信号量,分别是RWMutex, mutex1, mutex2, mutex3, wrt,两个全局整型变量writeCount, readCount
信号量mutex1在写者的进入区和退出区中使用,使得每次只有一个写者对其相应进入区或推出区进行操作,主要原因是进入区和退出区存在对变量writeCount的修改,每个写者其进入区中writeCount加1,退出区中writeCount减1。信号量RWMutex则是读者和写者两个之间的互斥信号量,保证每次只读或者只写。写者优先中,写者的操作应该优先于读者,则信号量一直被占用着,直到没有写者的时候才会释放,即当writeCount等于1的时候,申请信号量RWMutex,其余的写者无需再次申请,但是写者是不能同时进行写操作的,则需要设置一个信号量wrt来保证每次只有一个写者进行写操作,当写者的数量writeCount等于0的时候,则证明此时没有没有读者了,释放信号量RWMutex。信号量mutex2防止一次多个读者修改readCount。当readCount为1的时候,为阻止写者进行写操作,申请信号量wrt,则写者就无法进行写操作了。信号量mutex3的主要用处就是避免写者同时与多个读者进行竞争,读者中信号量RWMutex比mutex3先释放,则一旦有写者,写者可马上获得资源。
4.1 写者优先
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <sys/types.h>
# include <pthread.h>
# include <semaphore.h>
# include <string.h>
# include <unistd.h>
//semaphores
sem_t RWMutex, mutex1, mutex2, mutex3, wrt;
int writeCount, readCount;
struct data
{
int id;
int opTime;
int lastTime;
};
//读者
void* Reader(void* param)
{
int id = ((struct data*)param)->id;
int lastTime = ((struct data*)param)->lastTime;
int opTime = ((struct data*)param)->opTime;
sleep(opTime);
printf("Thread %d: waiting to read\n", id);
sem_wait(&mutex3);
sem_wait(&RWMutex);
sem_wait(&mutex2);
readCount++;
if(readCount == 1)
sem_wait(&wrt);
sem_post(&mutex2);
sem_post(&RWMutex);
sem_post(&mutex3);
printf("Thread %d: start reading\n", id);
/* reading is performed */
sleep(lastTime);
printf("Thread %d: end reading\n", id);
sem_wait(&mutex2);
readCount--;
if(readCount == 0)
sem_post(&wrt);
sem_post(&mutex2);
pthread_exit(0);
}
//写者
void* Writer(void* param)
{
int id = ((struct data*)param)->id;
int lastTime = ((struct data*)param)->lastTime;
int opTime = ((struct data*)param)->opTime;
sleep(opTime);
printf("Thread %d: waiting to write\n", id);
sem_wait(&mutex1);
writeCount++;
if(writeCount == 1)
{
sem_wait(&RWMutex);
}
sem_post(&mutex1);
sem_wait(&wrt);
printf("Thread %d: start writing\n", id);
/* writing is performed */
sleep(lastTime);
printf("Thread %d: end writing\n", id);
sem_post(&wrt);
sem_wait(&mutex1);
writeCount--;
if(writeCount == 0)
{
sem_post(&RWMutex);
}
sem_post(&mutex1);
pthread_exit(0);
}
int main()
{
//pthread
pthread_t tid; // the thread identifier
pthread_attr_t attr; //set of thread attributes
/* get the default attributes */
pthread_attr_init(&attr);
//initial the semaphores
sem_init(&mutex1, 0, 1);
sem_init(&mutex2, 0, 1);
sem_init(&mutex3, 0, 1);
sem_init(&wrt, 0, 1);
sem_init(&RWMutex, 0, 1);
readCount = writeCount = 0;
int id = 0;
while(scanf("%d", &id) != EOF)
{
char role; //producer or consumer
int opTime; //operating time
int lastTime; //run time
stanf("%c%d%d", &role, &opTime, &lastTime);
struct data* d = (struct data*)malloc(sizeof(struct data));
d->id = id;
d->opTime = opTime;
d->lastTime = lastTime;
if(role == 'R')
{
printf("Create the %d thread: Reader\n", id);
pthread_create(&tid, &attr, Reader, d);
}
else if(role == 'W')
{
printf("Create the %d thread: Writer\n", id);
pthread_create(&tid, &attr, Writer, d);
}
}
sem_destroy(&mutex1);
sem_destroy(&mutex2);
sem_destroy(&mutex3);
sem_destroy(&RWMutex);
sem_destroy(&wrt);
return 0;
}
4.2 读者优先
#include <windows.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <accctrl.h>
#include <time.h>
unsigned int readcount = 0;
bool p_ccontinue = true; //控制程序结束
HANDLE RSemaphore; //改变readcount值时互斥
HANDLE WSemaphore; //写互斥
DWORD WINAPI Writer(LPVOID); //写者线程
DWORD WINAPI Reader(LPVOID); //读者线程
int main()
{
RSemaphore = CreateSemaphore(NULL,1,1,NULL);
WSemaphore = CreateSemaphore(NULL,1,1,NULL);
const unsigned short Writer_COUNT = 3;//写者数量
const unsigned short Reader_COUNT = 8;//读者数量
const unsigned short THREADS_COUNT = Writer_COUNT+Reader_COUNT;
HANDLE hThreads[THREADS_COUNT]; //各线程的 handle
DWORD writerID[Writer_COUNT]; //写者线程的标识符
DWORD readerID[Reader_COUNT]; //读者线程的标识符
srand(time(0));
int writer_count = 0;
int reader_count = 0;
for(int i = 0; i < Writer_COUNT + Reader_COUNT; i++)
{
int ran = rand()%10;
if((ran < 5) && (writer_count < Writer_COUNT))//Writer
{
hThreads[i]=CreateThread(NULL,0,Writer,NULL,0,&writerID[writer_count]);
if (hThreads[i]==NULL) return -1;
writer_count++;
}
else if((ran >= 5) && (reader_count < Reader_COUNT))//Reader
{
hThreads[i]=CreateThread(NULL,0,Reader,NULL,0,&readerID[reader_count]);
if (hThreads[i]==NULL) return -1;
reader_count++;
}
Sleep(500);//等待500ms
}
while(p_ccontinue)
{
if(getchar()) //按回车后终止程序运行
{
p_ccontinue = false;
}
}
return 0;
}
//写者
DWORD WINAPI Writer(LPVOID lpPara)
{
while(p_ccontinue)
{
printf("\n%d is waiting to write...\n",GetCurrentThreadId());
WaitForSingleObject(WSemaphore,INFINITE);//P(w)在读或在写时,写者阻塞
printf("\n** writer %d got the control\n",GetCurrentThreadId());
//write...
printf("\n%d is writing...\n",GetCurrentThreadId());
Sleep(1500);
printf("\n--- %d have finished the writing\n",GetCurrentThreadId());
ReleaseSemaphore(WSemaphore,1,NULL);//V(w)写完,唤醒阻塞的读者或写者
return 0;//结束此线程
}
return 0;
}
//读者
DWORD WINAPI Reader(LPVOID lpPara)
{
while(p_ccontinue)
{
printf("\n%d is waiting to read...\n",GetCurrentThreadId());
WaitForSingleObject(RSemaphore,INFINITE);//P(x)临界区互斥,所有读者修改readcount
readcount++;
if(readcount == 1)//第一个读,要等已经在写的写者写完才可以开始读
WaitForSingleObject(WSemaphore,INFINITE);//P(w)有写时,读者阻塞
//同时读,故要在Read()之前V(x)
ReleaseSemaphore(RSemaphore,1,NULL);//V(x)
//read...
printf("\n%d is reading...\n",GetCurrentThreadId());
Sleep(1500);
printf("\n--- %d have finished the reading\n",GetCurrentThreadId());
WaitForSingleObject(RSemaphore,INFINITE);//P(x)
readcount--;
if(readcount== 0)
{
printf("\n----- All Readers done\n");
ReleaseSemaphore(WSemaphore,1,NULL);//V(w)都不读,唤醒阻塞的写者
}
ReleaseSemaphore(RSemaphore,1,NULL);//V(x)
return 0;//结束此线程
}
return 0;
}
五、哲学家就餐算法
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <pthread.h>
#include <errno.h>
#include <math.h>
//筷子作为mutex
pthread_mutex_t chopstick[6] ;
void *eat_think(void *arg)
{
char phi = *(char *)arg;
int left,right; //左右筷子的编号
switch (phi)
{
case 'A':
left = 5;
right = 1;
break;
case 'B':
left = 1;
right = 2;
break;
case 'C':
left = 2;
right = 3;
break;
case 'D':
left = 3;
right = 4;
break;
case 'E':
left = 4;
right = 5;
break;
}
int i;
for(;;)
{
usleep(3); //思考
pthread_mutex_lock(&chopstick[left]); //拿起左手的筷子
printf("Philosopher %c fetches chopstick %d\n", phi, left);
if (pthread_mutex_trylock(&chopstick[right]) == EBUSY) //拿起右手的筷子
{
pthread_mutex_unlock(&chopstick[left]); //如果右边筷子被拿走放下左手的筷子
continue;
}
// pthread_mutex_lock(&chopstick[right]); //拿起右手的筷子,如果想观察死锁,把上一句if注释掉,再把这一句的注释去掉
printf("Philosopher %c fetches chopstick %d\n", phi, right);
printf("Philosopher %c is eating.\n",phi);
usleep(3); //吃饭
pthread_mutex_unlock(&chopstick[left]); //放下左手的筷子
printf("Philosopher %c release chopstick %d\n", phi, left);
pthread_mutex_unlock(&chopstick[right]); //放下左手的筷子
printf("Philosopher %c release chopstick %d\n", phi, right);
}
}
int main()
{
pthread_t A,B,C,D,E; //5个哲学家
int i;
for (i = 0; i < 5; i++)
pthread_mutex_init(&chopstick[i],NULL);
pthread_create(&A,NULL, eat_think, "A");
pthread_create(&B,NULL, eat_think, "B");
pthread_create(&C,NULL, eat_think, "C");
pthread_create(&D,NULL, eat_think, "D");
pthread_create(&E,NULL, eat_think, "E");
pthread_join(A,NULL);
pthread_join(B,NULL);
pthread_join(C,NULL);
pthread_join(D,NULL);
pthread_join(E,NULL);
return 0;
}