实验内容
(1)实现一个模拟的shell
编写三个不同的程序:cmd1.c, cmd2.c, cmd3.c,每个程序输出一句话,分别编译成可执行文件cmd1, cmd2, cmd3。然后再编写一个程序,模拟shell程序的功能,能根据用户输入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程的结束,然后再等待接收下一条命令。如果接收到的命令为exit,则父进程结束,如果接收到无效命令,则显示”command not found”,继续等待。
(2)实现一个管道通信程序
由父进程创建一个管道,然后再创建3个子进程,并由这三个子进程用管道与父进程之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。通信的具体内容可根据自己的需要随意设计,要求能够实验阻塞型读写过程的各种情况,并要求实现进程间对管道的互斥访问。运行程序,观察各种情况下,进程实际读写的字节数以及进程阻塞唤醒情况。
(3)利用Linux的消息队列通信机制实现两个线程间的通信
编写程序创建三个线程:sender1线程、sender2线程和receiver线程,三个线程的功能描述如下:
① sender1线程:运行函数sender1(),它创建一个消息队列,然后等待用户通过终端输入一串字符,并将这串字符通过消息队列发给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end1”,并且等待receiver的应答(老师告知可以省略这步,所以代码没有体现,sender2同),等到应答消息后,将接收到的应答信息显示在终端屏幕上,结束线程的运行。
② sender2线程:运行函数sender2(),共享sender1创建的消息队列,等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end2”,并且等待receiver的应答,等到应答消息后,将接收到的应答信息显示在终端屏幕上,结束线程的运行。
③ receiver线程:运行函数receive(),它通过消息队列接收来自sender1和sender2两个线程的消息,将消息显示在终端屏幕上,当收到内容为“end1”的消息时,就向sender1发送一个应答消息“over1”;当收到内容为“end2”的消息时,就向sender2发送一个应答消息“over2”;消息接受完成后删除消息队列,结束线程的运行。选择合适的信号量机制实现三个线程之间的同步和互斥。
(4)利用Linux的共享内存通信机制实现两个进程间的通信
编写程序sender,它创建一个共享内存,然后等待用户通过终端输入一串字符,并将这串字符通过共享内存发送给receiver,最后,等待receiver应答,等到应答消息后,它接收到的应答消息显示在终端屏幕上,删除共享内存,结束程序运行。编写receiver程序,它通过共享内存接收来自sender的消息,将消息显示在终端屏幕上,然后再通过该共享内存向sender发送一个应答消息”over”,结束程序的运行。使用有名信号量或System V信号量实现两个进程对共享内存的互斥使用。
代码实现
1、实现一个模拟的shell
cmd1.c
#include<stdio.h>
int main()
{
printf("this is the cmd1!\n");
return 0;
}
cmd2.c
#include<stdio.h>
int main()
{
printf("this is the cmd2!\n");
return 0;
}
cmd3.c
#include<stdio.h>
int main()
{
printf("this is the cmd3!\n");
return 0;
}
Linux中的shell是什么意思?
答:Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。
myshell.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#define CMD_COLLECTION_LEN 4 //命令数组的长度(有哪几个命令)
//command index
#define INVALID_COMMAND -1 //无效命令返回-1
#define EXIT 0
#define CMD_1 1
#define CMD_2 2
#define CMD_3 3
//bool
#define TRUE 1
char *cmdStr [CMD_COLLECTION_LEN]= {"exit","cmd1","cmd2","cmd3"};
//对比所有命令参数,如果有一样的,就返回对应数字,用于后面执行
int getCmdIndex(char *cmd){
int i;
for(i=0;i<CMD_COLLECTION_LEN;i++){
if (strcmp(cmd,cmdStr[i])==0){
return i;
}
}
return -1;
}
/*
创建子进程,这里使用了execl,后面的l表示list,即参数列表。
第一参数为path(要执行的文件路径),最后一个参数必须是NULL,中间的为要传送的参数列表。
当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。
因为调用exec并不创建新进程,所以前后的进程ID并未改变。
exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
*/
void myFork(int cmdIndex){
pid_t pid;
if((pid = fork())<0){
printf("创建子进程错误\n");
exit(0);
}
else if (pid == 0){
int execl_status = -1;
printf("子进程正在运行\n");
switch(cmdIndex){
case CMD_1:
execl_status = execl("./cmd1","cmd1",NULL);
break;
case CMD_2:
execl_status = execl("./cmd2","cmd2",NULL);
break;
case CMD_3:
execl_status = execl("./cmd3","cmd3",NULL);
break;
default:
printf("无此命令!!!\n");
break;
}
if(execl_status<0){
printf("创建错误\n");
exit(0);
}
printf("运行完毕!\n");
exit(0);
}else{
return;
}
}
//运行cmd
void runCMD(int cmdIndex)
{
switch(cmdIndex){
case INVALID_COMMAND:
printf("Command Not Found \n"); //没有找到该命令
break;
case EXIT: //exit命令返回0
exit(0);
break;
default:
myFork(cmdIndex); //创建子进程运行
break;
}
}
int main(){
pid_t pid;
char cmdStr[30]; //命令数组(最长30)
int cmdIndex; //用于显示运行哪个数据
while(TRUE){
printf("\n输入命令\n>>:");
scanf("%s",cmdStr);
cmdIndex = getCmdIndex(cmdStr);
runCMD(cmdIndex); //根据数字运行不同的cmd
wait(0);
}
return 0;
}
Makefile
all: myshell cmd1 cmd2 cmd3
.PHONY : clean
myshell.o : myshell.c
clean :
rm cmd1 cmd2 cmd3
rm myshell
rm *.o
运行命令
(1)make
(2)./myshell
执行效果
2、实现一个管道通信程序
pipe.c
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/wait.h>
#define READ 0 //描述符
#define WRITE 1
#define MAX_SIZE 16384
/*16KB, 当管道进行写入操作的时候,如果写入的数据小于128K则是非原子的,
如果大于128K字节,缓冲区的数据将被连续地写入*/
int main(int argc, char** argv){
int fd[2];
ssize_t status;
int flag=0;
char buf[MAX_SIZE], str[MAX_SIZE];
pid_t pid1,pid2,pid3;
//定义三个互斥信号量指针
sem_t *write_mutex;
sem_t *read_mutex1;
sem_t *read_mutex2;
//创建有名信号量
write_mutex = sem_open("pipe_test_wm", O_CREAT | O_RDWR, 0666, 0);
read_mutex1 = sem_open("pipe_test_rm_1", O_CREAT | O_RDWR, 0666, 0);
read_mutex2 = sem_open("pipe_test_rm_2", O_CREAT | O_RDWR, 0666, 0);
status = pipe(fd);
/*返回值:成功,返回0,否则返回-1。
参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道,fd[1]:写管道。*/
if(status < 0){
fprintf(stderr, "create Pipe Error %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
status = 0;
//创建子进程1, 测试管道默认大小
if((pid1 = fork()) == -1){
printf("child process 1 fork() failed.\n");
exit(1); //异常退出
}
if (pid1 == 0) {
printf("\n>> child process 1 [pid=%d, ppid=%d]:\n", getpid(), getppid());
int count = 0;
close(fd[READ]); //关闭读端口
//F_GETFL,获取pipefd[1]的文件状态标志
int flags = fcntl(fd[WRITE], F_GETFL);
//F_SETFL,设置pipefd[1]的文件状态标志 非阻塞方式
fcntl(fd[WRITE], F_SETFL, flags | O_NONBLOCK);
while (!flag) {
//write把buf指针所指内存的16384个字节写入管道中,返回实际写入的字节数
status = write(fd[WRITE], buf, MAX_SIZE);
//管道已满,不能写入
if (status == -1) {
flag = 1;
} else {
count++; //记录写入的次数
printf("child process 1 write %ldB.\n", status);
}
}
//输出管道的默认大小
printf("pipe size = %dKB.\n\n", (count * MAX_SIZE) / 1024);
exit(0);
}
status = 0;
//创建子进程2
if((pid2 = fork()) == -1){
printf("child process 2 fork() failed.\n");
exit(1); //异常退出
}
if (pid2 == 0) {
sem_wait(write_mutex);
close(fd[READ]);
printf(">> child process 2 [pid=%d, ppid=%d]:\n", getpid(), getppid());
status = write(fd[WRITE], "This is child process 2.\n", strlen("This is child process 2.\n"));
printf("child process 2 write %ldB.\n\n", status);
sem_post(write_mutex);
sem_post(read_mutex1);
exit(0);
}
status = 0;
//创建子进程3
if((pid3 = fork()) == -1){
printf("child process 3 fork() failed.\n");
exit(1); //异常退出
}
if (pid3 == 0) {
sem_wait(write_mutex);
close(fd[READ]);
printf(">> child process 3 [pid=%d, ppid=%d]:\n", getpid(), getppid());
status = write(fd[WRITE], "This is child process 3.\n", strlen("This is child process 3.\n"));
printf("child process 3 write %ldB.\n\n", status);
sem_post(write_mutex);
sem_post(read_mutex2);
exit(0);
}
//父进程
wait(0);
close(fd[WRITE]);
printf(">> father process [pid=%d]:\n", getpid());
int flags = fcntl(fd[READ], F_GETFL);
fcntl(fd[READ], F_SETFL, flags | O_NONBLOCK);
while (!flag) {
//read把管道中的MAX_SIZE个字节读到str指针所指的内存中
status = read(fd[READ], str, MAX_SIZE);
if (status == -1) {
flag = 1;
} else {
//输出读出的字节数
printf("father process read %ldB.\n", status);
}
}
printf("\n");
sem_post(write_mutex);
//子进程23全部写完后开始读
sem_wait(read_mutex1);
sem_wait(read_mutex2);
printf(">> father process [pid=%d]:\n", getpid());
status = read(fd[READ], str, MAX_SIZE);
//输出子进程23写入的内容
printf("father process read %ldB.\n", status);
for (int i = 0; i < status; i++) {
printf("%c", str[i]);
}
printf("\n");
//关闭信号量
sem_close(write_mutex);
sem_close(read_mutex1);
sem_close(read_mutex2);
//从系统中删除信号量
sem_unlink("pipe_test_wm");
sem_unlink("pipe_test_rm_1");
sem_unlink("pipe_test_rm_2");
return 0;
}
Makefile
.PHONY : clean
pipe : pipe.o
cc -pthread -o pipe pipe.o
clean:
rm pipe
rm *.o
运行命令
(1)make
(2)./pipe
执行效果
3、利用Linux的消息队列通信机制实现三个线程间的通信
message_queue.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/sem.h>
#include<sys/shm.h>
#include<fcntl.h>
#include<unistd.h>
#define MSG_MAX 100
/*函数的msgp位是指向消息缓冲区的指针,
此位置用来暂时存储发送和接收的消息,
是一个用户可定义的通用结构*/
struct my_msgbuf{
long int mtype;
char mtext[MSG_MAX];
};
void *sender1(){
int n;
struct my_msgbuf message;
char buf[MSG_MAX];
//创建有名信号量
sem_t *mutex = sem_open("mutex", O_CREAT | O_RDWR, 0666, 0);
sem_t *sender1_over = sem_open("sender1_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *receive1_over = sem_open("receive1_over", O_CREAT | O_RDWR, 0666, 0);
/*获取关联的键为"8088"的消息队列的标识,
以及消息队列的建立标志(key指定的消息队列已存在IPC_CREAT)和存取权限*/
int msqid = msgget((key_t)8088, 0666 | IPC_CREAT);
if( msqid == -1){
printf("create failed");
exit(-1);
}
while(1){
sem_wait(mutex);
printf("sender1线程等待用户输入字符:");
scanf("%s", buf);
//printf("\n");
message.mtype = 1;
if(strcmp(buf,"exit") == 0){
strcpy(message.mtext,"end1");
/*将一个新的消息写入队列(消息队列识别码, 消息缓冲区指针, 消息的大小, 忽略)*/
n = msgsnd(msqid, (void *)&message, MSG_MAX, 0);
sem_wait(receive1_over);
/*从消息队列中读取消息(消息队列识别码, 消息缓冲区指针, 消息的大小, 消息类型, 忽略)
消息类型大于0,则返回其类型为mtype的第一个消息。*/
n = msgrcv(msqid, (void *)&message, MSG_MAX, 2, 0);
printf("%s\n", message.mtext);
sem_post(sender1_over);
sem_post(mutex);
sleep(1);
return 0;
}else{
strcpy(message.mtext,buf);
n = msgsnd(msqid, (void *)&message, MSG_MAX, 0);
sem_post(mutex);
sleep(1);
}
}
}
void *sender2(){
int n;
struct my_msgbuf message;
char buf[MSG_MAX];
sem_t *mutex = sem_open("mutex", O_CREAT | O_RDWR, 0666, 0);
sem_t *sender2_over = sem_open("sender2_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *receive2_over = sem_open("receive2_over", O_CREAT | O_RDWR, 0666, 0);
int msqid = msgget((key_t)8088, 0666 | IPC_CREAT);
if( msqid == -1){
printf("create failed");
exit(-1);
}
while(1){
sem_wait(mutex);
printf("sender2线程等待用户输入字符:");
scanf("%s", buf);
//printf("\n");
message.mtype = 1;
if(strcmp(buf,"exit") == 0){
strcpy(message.mtext,"end2");
n = msgsnd(msqid, (void *)&message, MSG_MAX, 0);
sem_wait(receive2_over);
n = msgrcv(msqid, (void *)&message, MSG_MAX, 3, 0);
printf("%s\n", message.mtext);
sem_post(sender2_over);
sem_post(mutex);
sleep(1);
return 0;
}else{
strcpy(message.mtext,buf);
n = msgsnd(msqid, (void *)&message, MSG_MAX, 0);
sem_post(mutex);
sleep(1);
}
}
}
void *receive(){
int n;
int over1=0;
int over2=0;
struct my_msgbuf message;
char buf[MSG_MAX];
//创建有名信号量
sem_t *sender1_over = sem_open("sender1_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *receive1_over = sem_open("receive1_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *sender2_over = sem_open("sender2_over", O_CREAT | O_RDWR, 0644, 0);
sem_t *receive2_over = sem_open("receive2_over", O_CREAT | O_RDWR, 0666, 0);
//获取消息队列
int msqid = msgget((key_t)8088, 0666 | IPC_CREAT);
if( msqid == -1){
printf("create failed");
exit(-1);
}
while(1){
n = msgrcv(msqid, (void *)&message, MSG_MAX, 0, 0);
if(n > 0){
printf("\nReceiver线程收到内容“%s”\n", message.mtext);
if(strcmp(message.mtext,"end1") == 0){
message.mtype = 2;
strcpy(message.mtext,"over1");
n = msgsnd(msqid, (void *)&message, MSG_MAX, 0);
if(n == 0){ //当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
sem_post(receive1_over);
sem_wait(sender1_over);
}
over1 = 1;
}else if(strcmp(message.mtext,"end2") == 0){
message.mtype = 3;
strcpy(message.mtext,"over2");
n = msgsnd(msqid, (void *)&message, MSG_MAX, 0);
if(n == 0){
sem_post(receive2_over);
sem_wait(sender2_over);
}
over2 = 1;
}
}
if(over1==1 && over2==1){
//获取和设置消息队列的属性, IPC_RMID:把一个段标记为删除
msgctl(msqid, IPC_RMID, 0);
exit(0);
}
}
}
int main(){
int msqid = msgget((key_t)8088, 0666 | IPC_CREAT);
msgctl(msqid, IPC_RMID, 0);
sem_unlink("mutex");
sem_unlink("sender1_over");
sem_unlink("sender2_over");
sem_unlink("receive1_over");
sem_unlink("receive2_over");
sem_t *mutex = sem_open("mutex", O_CREAT | O_RDWR, 0666, 0);
sem_t *sender1_over = sem_open("sender1_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *receive1_over = sem_open("receive1_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *sender2_over = sem_open("sender2_over", O_CREAT | O_RDWR, 0666, 0);
sem_t *receive2_over = sem_open("receive2_over", O_CREAT | O_RDWR, 0666, 0);
pthread_t pt1,pt2,pt3; //线程id
pthread_create(&pt1, NULL, sender1, NULL); //创建线程,调用sender1
pthread_create(&pt2, NULL, sender2, NULL); //创建线程,调用sender2
pthread_create(&pt3, NULL, receive, NULL); //创建线程,调用receive
sem_post(mutex);
pthread_join(pt1, NULL); //等待线程退出
pthread_join(pt2, NULL); //等待线程退出
pthread_join(pt3, NULL); //等待线程退出
return 0;
}
Makefile
.PHONY : clean
message_queue : message_queue.o
cc -pthread -o message message_queue.o
clean:
rm message_queue
rm *.o
运行命令
(1)make
(2)./message
执行效果
4、利用Linux的共享内存通信机制实现两个进程间的通信
sender.c
//为消息发送程序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>
static const char * MUTEX_NAME = "mutex_shm";
static const char * FULL_NAME = "full_shm";
#define INPUT_SIZE 1024 //输入的最大长度
#define KEY_NUM 8848
void P(sem_t *semPtr){
sem_wait(semPtr);
}
void V(sem_t *semPtr){
sem_post(semPtr);
}
int main(int argc, char** argv){
key_t key = KEY_NUM; //为共享内存段命名
char input[INPUT_SIZE];
char reply[INPUT_SIZE];
int shmid;
char* shmptr;
//创建共享内存
shmid = shmget(key, INPUT_SIZE, IPC_CREAT | 0666);
if(shmid < 0)
{
perror("Receiver: Shmget Error");
exit(EXIT_FAILURE);
}
//启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
shmptr = shmat(shmid, NULL, 0);
sem_t* mutex = sem_open(MUTEX_NAME,O_CREAT); //共享内存只能同时一个程序访问
sem_t* full = sem_open(FULL_NAME,O_CREAT); //共享内存的消息数量
printf("请输入一串字符:");
scanf("%s",input);
P(mutex);
strcpy(shmptr,input);
V(mutex);
V(full);
printf("消息已发送给receiver!\n");
//把共享内存从当前进程中分离
if(shmdt(shmptr) == -1){
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
return 0;
}
receiver.c
//为消息接收程序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<pthread.h>
#include<semaphore.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>
static const char * MUTEX_NAME = "mutex_shm";
static const char * FULL_NAME = "full_shm";
#define RESULT_SIZE 1024
#define KEY_NUM 8848
void P(sem_t *semPtr){
sem_wait(semPtr);
}
void V(sem_t *semPtr){
sem_post(semPtr);
}
int main(int argc, char** argv){
key_t key = KEY_NUM;
char result[RESULT_SIZE];
int shmid;
char* shmptr;
shmid = shmget(key, RESULT_SIZE, IPC_CREAT | 0666);
if(shmid < 0)
{
perror("Receiver: Shmget Error");
exit(EXIT_FAILURE);
}
shmptr = shmat(shmid, NULL, 0);
sem_t* mutex = sem_open(MUTEX_NAME,O_CREAT);
sem_t* full = sem_open(FULL_NAME,O_CREAT);
P(full);
P(mutex);
strcpy(result,shmptr);
V(mutex);
printf("接收到来自sender的消息是: %s\n",result);
printf("over\n");
//把共享内存从当前进程中分离
if(shmdt(shmptr) == -1){
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
shmctl(shmid, IPC_RMID, NULL); //控制共享内存, IPC_RMID表示删除共享内存段
sem_t * mutexPtr = sem_open(MUTEX_NAME,O_CREAT);
sem_t * fullPtr= sem_open(FULL_NAME,O_CREAT);
sem_close(mutexPtr);
sem_unlink(MUTEX_NAME);
sem_close(fullPtr);
sem_unlink(FULL_NAME);
return 0;
}
Makefile
all : sender receiver
.PHONY : clean
sender : sender.o
cc -pthread -o sender sender.o
receiver : receiver.o
cc -pthread -o receiver receiver.o
clean :
rm receiver
rm sender
rm *.o
运行命令
(1)make
(2)./sender
(3)./receiver