课程设计——LINUX系统下多进程的创建与通信

##课程设计报告

( 2014 – 2015 年度第 1学期)

名 称: Unix/Linux编程课程设计
题 目:LINUX系统下多进程的创建与通信
院 系: 控制与计算机工程学院
班 级:
学 号:
学生姓名:
指导教师:
设计周数: 1

成 绩:

日期:2015 年 1月 9日

一、课程设计的目的与要求

1 目的:学习UNIX/LINUX系统下的多进程创建、控制和通信。

  1. Linux上的bash和Windows中的命令行有很大的不同。但是两者都有完成相似任务的命令,比如Linux上bash的ls命令的功能,类似于Windows命令行中的dir命令的功能。用C语言写一个简单的Linux终端软件,接收用户发出的类似于Windows命令行中的命令,转换成对应的Linux命令加以执行,并将执行的结果回显给用户。比如,用户输入“dir”,程序实际返回“ls”的内容。

  2. 软件包含前、后台两个程序,用户启动前台程序时,前台程序自行启动后台程序。前台程序提供界面,负责接收用户输入,对输入进行转换,并向后台程序发出实际要执行的指令,后台负责执行实际的指令。

2 要求:

  1. 前台程序通过fork和execl系统调用启动后台程序。
  2. 前台程序创建消息队列和命名管道,通过消息队列向后台程序发送经过转换的用户命令;通过命名管道从后台程序获取命令执行的结果,并显示在终端。后台程序可以通过popen来执行转换后的命令。
  3. 至少实现如下Windows——Linux对照命令:dir——ls,rename——mv,move——mv,del——rm,cd——cd(pwd),exit——exit。
  4. 当用户输入exit时,前台程序指示后台程序结束,在后台程序结束后,前台程序退出;在此之前,用户的输入都被作为一条命令进行处理。

二、设计正文

1 设计思路概要:

根据题目要求,需要在前台程序接收用户输入,然后将Windows命令转换成Linux可执行命令,以消息队列的形式传给后台程序,后台程序接收消息队列中的内容,并执行把结果写入管道中,前台程序读取管道内容打印到标准输出。
程序关键在于理解对管道读写,消息队列的发送与接受,以及子进程是如何在父进程中调用执行的。同时,进程间同步与异步的关系也直接影响两个进程的逻辑结构。
前期开发过程中,按照《Linux编程从入门到精通》的例子分块编写好管道的读写、消息队列的接收、进程的克隆,并调试通过。然后同样分块编写Windows与Linux命令的转换和Linux命令使用popen()管道执行。中期分析了整个程序的逻辑结构,把程序的流程大体跑通。后期测试和优化了一些输出和功能。

2 程序介绍

  1. 子进程的创建
    用fork函数创建子进程,当返回值pid为0时代表子进程,pid大于0时代表父进程。然后子进程调用函数execl执行background后台程序,父进程继续执行。

  2. 命令的转换
    用两个数组分别保存Windows命令和Linux命令,将输入的命令分割成两部分,一部分是命令,另一部分是所带的参数。将命令的部分与Windows命令的数组比较,找到对应的Linux命令,然后将其和带参数的部分拼接,就完成了命令的转换。

  3. 消息队列的收发
    消息队列直接封装成两个函数,发送和接收消息队列都要用到一个结构体保存消息队列的类型和传输的内容。消息队列的收发是异步的,但是要保证先发送消息队列然后接收,才能够让程序正常运行。为了解决这个问题,在收发之前加入一个同步的管道syn_pipe。

  4. 管道的读写
    管道的使用要先在初始化过程中创建管道文件,然后才能够读写管道。关键的一点是管道是同步通讯的,也就是读取管道的时候必须要有进程写管道,否则一直等待直到其他进程写管道。同样写管道的一方也到等到其他进程读管道,才能执行之后的程序。这在编程调试的时候是很重要的。

  5. 命令的执行
    命令的执行使用管道函数popen()执行shell命令,代码是从网上摘取,详情见附录。

3 功能特色

除了课程设计的六个命令的实现外,左边会输出当前的工作目录,用dir查看文件时会按文件名长度对齐输出,仿Linux的ls -a命令。

三、课程设计总结或结论

这次课程设计加深了我对书本知识的理解,Makefile文件的编写、vi编辑器的使用、以及课设中使用的函数的用途等。
课程设计让我对C语言的语法、C语言指针、内存等问题有了深刻的认识,也付出了一些的代价。
这次课程设计编写用时3天,查找错误陷入各种Bugg用时3天。所有的代码都是自己独立完成,即使是书上的代码用例和网上借鉴的小程序段都在参考文献一一注明。有些功能实现的细节并没有写在文档上。
期间对进程之间以同步异步方式通讯、子进程的调用执行、sscanf和printf函数的使用、动态申请内存有了进一步的理解。
总之,这次课程设计有一些收获,同时也能够在要求的时间完成任务。、
学完Linux这么课程,以后有时间会试着写简单的Shell脚本。

四、参考文献
[1] Linux下使用popen()执行shell命令
http://www.cnblogs.com/caosiyang/archive/2012/06/25/2560976.html
[2] 管道读写:《Linux编程》例子15-6,7 9-11
[3] 进程创建子进程:《Linux编程》例子13-11
[4] 消息队列的发送接收:例子16-4,5
[5] 用sscanf解析字符串 http://www.cnblogs.com/lyq105/archive/2009/11/28/1612677.html
[6] 用sprintf格式化输出字符串

附录

1 设计流程图

流程图

2 程序代码

main.c

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

static int order_num = 6;
char **windows_orders[6] = {"dir","rename","move","del","cd","exit"};
char **linux_orders[6] = {"ls","mv","mv","rm","cd","exit"};

char *order_change(char *windows_order)
{
	int i;
	char *temp;
	char *order_pre;
	char *order_para;
	if((temp = (char*)malloc(20*sizeof(char))) ==0)
		return 0;
	if((order_pre = (char*)malloc(20*sizeof(char))) ==0)
		return 0;
	if((order_para = (char*)malloc(20*sizeof(char))) ==0)
		return 0;
	sscanf(windows_order,"%s%[^'\n']",order_pre,order_para);
	//printf("order_pre   :%s\n",order_pre);
	//printf("order_para   :%s\n",order_para);
	if(!strcmp(order_pre,"cd") && strlen(order_para) == 0)
		return "pwd";
	else if(!strcmp(order_pre,"cd") && strlen(order_para) != 0)
	{
		chdir("home");
	}
	for(i=0;i<order_num;i++) 
	{
		if(strstr(order_pre,windows_orders[i]) != 0)
		{
			strcpy(temp,linux_orders[i]);
			strcat(temp,order_para);
			return temp;
		}
	}
	return "";
}

char *loadpath(char *pwd, char *filename)
{
	char path[1024];
	strcpy(path,pwd);
	strcat(path,"/");
	strcat(path,filename);
	return path;
}
main()
{
	pid_t pid;
	char *win_order;
	char *linux_order;
//	Why ?	Segmentation fault (core dumped)
	if((win_order = (char*)malloc(20*sizeof(char))) == 0)
		return 0;
	if((linux_order = (char*)malloc(20*sizeof(char))) ==0)
		return 0;
	char result[1001];
	char pwd[1024];
	char pro_path[1024];
	getcwd(pro_path,1024);
	create_pipe(loadpath(pro_path,"lx_pipe"));
	create_pipe(loadpath(pro_path,"syn_pipe"));
	create_pipe(loadpath(pro_path,"pwd_pipe"));
	// 最新的signal.h已经没有SIGCLD了,可能得用 signal(SIGCHLD, SIG_IGN);
	signal(SIGCLD,SIG_IGN);
	switch(pid=fork())
	{
		case -1:
			perror("fork");
			break;
		case 0:			/* 子进程 */
		{
			if(execl(loadpath(pro_path,"background"),NULL)== -1)
			{
				perror("execl");
				exit(0);
			}
			break;
		}
		default:		/* 父进程 */
		{
			while(1)
			{
				read_pipe(loadpath(pro_path,"pwd_pipe"),&pwd);
				sscanf(pwd,"%[^ ]",pwd);
				printf("%s:",pwd);
				gets(win_order);
				//
				
				if(strcmp(win_order,"exit") == 0)
				{
					kill(pid,SIGKILL);
					exit(0);
				}
				linux_order = order_change(win_order);
				write_pipe(loadpath(pro_path,"syn_pipe"),"Start Send");
				//	发送msg
				//if(strlen(linux_order) == 0)
				//{
				//	printf("Couldn't found the order,please check your order\n");
				//	continue;
				//}
				send_msg(linux_order,pro_path);
				//	读取pipe
				read_pipe(loadpath(pro_path,"lx_pipe"),&result);
				printf("%s\n",result);
			}
			break;
		}
	}
}

background.c

#include <stdio.h>
#include <string.h>
#include <unistd.h> 
//execute shell command
//执行一个shell命令,输出结果逐行存储在resvec中,并返回行数
void myexec(const char *cmd, char *resvec) {

    int line_num = 0;
    char path[1024];
    strcpy(resvec,"");
    if(strstr(cmd,"cd") != 0)
    {
	sscanf(cmd,"%*s%s",path);
	int error = chdir(path);
	if(error == -1)
		strcpy(resvec,"error: couldn't find the filepath");
	return;
    }
    FILE *pp = popen(cmd, "r"); //建立管道
    if (!pp) {
        return;
    }
    char tmp[1024]; //设置一个合适的长度,以存储每一行输出
    while (fgets(tmp, sizeof(tmp), pp) != NULL) {
        if (tmp[strlen(tmp) - 1] == '\n') {
            tmp[strlen(tmp) - 1] = '\0'; //去除换行符
       }
	if(strlen(tmp) > 12)
	{
		sprintf(tmp, "%-24s", tmp);
		line_num++;
	}
	else
		sprintf(tmp, "%-12s", tmp);	//标准格式
        strcat(resvec,tmp);
	if(++line_num % 6 == 0)
		strcat(resvec,"\n");
    }
    pclose(pp); //关闭管道
    return;
}
char *loadpath(char *pwd, char *filename)
{
	char path[1024];
	strcpy(path,pwd);
	strcat(path,"/");
	strcat(path,filename);
	return path;
}
main()
{
	char resvec[1000];
	char rev_msg[256];
	char rcv_p[20];
	char pro_path[1024];
	getcwd(pro_path,1024);
	while(1)
	{
		myexec("pwd",&resvec);
		write_pipe(loadpath(pro_path,"pwd_pipe"),resvec);
		read_pipe(loadpath(pro_path,"syn_pipe"),&rcv_p);
		receive_msg(&rev_msg,pro_path);
		myexec(rev_msg,&resvec);
		//printf("%s\n",resvec);//打印容器的内容
		write_pipe(loadpath(pro_path,"lx_pipe"),resvec);
		
	}
}

message.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
typedef struct
{
	long int nType;
	char szText[256];
}MSG;

char *load(char *pwd, char *filename)
{
	char path[1024];
	strcpy(path,pwd);
	strcat(path,"/");
	strcat(path,filename);
	return path;
}
void send_msg(char *content, char *pro_path)
{
	key_t IKey;
	int nMsgId;
	MSG msg;
	if((IKey = ftok (load(pro_path,"profile"),2)) == -1)
	{
		printf("send\n");
		perror("ftok");
		exit(1);
	}
	// 队列存在则退出,否则创建新的队列
	if((nMsgId = msgget(IKey,IPC_CREAT|IPC_EXCL|0666)) == -1)
	{
		if(errno != EEXIST)		/* 消息队列创建失败 */
		{
			perror("msgget");
			exit(2);
		}
		if((nMsgId = msgget(IKey,0)) == -1)  /* 消息队列已存在 */
		{
			perror("msgget");
			exit(3);
		}
	}
	memset(&msg,0x00,sizeof(MSG));		/* 清空队列结构 */
	msg.nType = 2;
	memcpy(msg.szText,content,strlen(content));
	if(msgsnd(nMsgId,(const void *)&msg,strlen(msg.szText),IPC_NOWAIT) <0)
	{
		printf("error\n");
		perror("msgsnd");
	}
}
void receive_msg(char *rcv_msg, char *pro_path)
{
	key_t IKey;
	int n,nMsgId;
	MSG msg;
	if((IKey = ftok (load(pro_path,"profile"),2)) == -1)
	{
		printf("receive\n");
		perror("ftok");
		exit(1);
	}
	if((nMsgId = msgget(IKey,0)) == -1)  /* 消息队列已存在 */
	{
		printf("receive\n");
		perror("ftok");
		exit(2);
	}
	memset(&msg,0x00,sizeof(MSG));		/* 清空队列结构 */
	if((n = msgrcv(nMsgId,(const void *)&msg,sizeof(msg.szText),2L,0)) <0)
	{
		perror("msgrcv");
	}
	else
	{	
		strcpy(rcv_msg,msg.szText);
//		return msg.szText;
	}
}

pipe.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


void create_pipe(char *pipe_name)
{
	if(access(pipe_name, R_OK|W_OK) != -1)
		return;
	if(mkfifo(pipe_name,0644) < 0)
	{
		perror("mkfifo");
		exit(-1);
	}
}
void read_pipe(char *pipe_name,char *result)
{
	int fd;
	if((fd = open(pipe_name,O_RDONLY,0)) < 0)
	{
		perror("open");
		exit(-1);
	}
//	printf("start read pipe\n");
	if(fd != -1)
	{
		if(read(fd,result,1000) != -1)
		{
			return;
		}
	}
	close(fd);
	return;
}
void write_pipe(char *pipe_name,char *content)
{
	int fd;
	if((fd = open(pipe_name,O_WRONLY,0)) < 0)
	{
		perror("open");
		exit(-1);
	}
//	printf("start write pipe\n");
	if(fd != -1)
	{
		if(write(fd,content,1000) == -1)
			return;
	}
	close(fd);
	return;
}

Makefile

all :demo background profile pipe
profile:
	touch profile
pipe:
	rm -f *_pipe
background:background.o message.o pipe.o
	gcc background.o message.o pipe.o -o background
demo:main.o message.o pipe.o
	gcc main.o message.o pipe.o -o demo
clean:
	rm *.o demo background

3 开始界面

dir命令
rename命令
move命令
del命令
cd命令
exit命令

4 命令介绍

dir 只可查询当前目录或者用户主目录。可以使用dir :显示文件的信息。
rename 格式:rename 旧文件 新文件。建立文件时要写文件后缀(可改变文件的后缀),但是旧的文件必须有后缀名,新的可没有。
move 格式:move 1.txt ./1(文件夹) 注:数字就必须加./,字母的名字可不用
move ./a ./b 文件夹,移动的文件可以没有后缀名。可从文件夹下移出,move ./a/d.c ./。
del 格式:del 文件名。不能删文件夹。可以删文件夹下的某个文件a/a.c。可以递归删除文件夹内容,如:del –r ./a将a文件夹及其文件夹下的内容全删掉。可删上一级目录,如:del …/1.c
pwd 显示工作目录的路径名称
exit 退出

包含源代码以及测试说明 题目如下: 1. 系统管理员每天要做大量任务,请编shell脚本来减轻工作负担吧。要求如下: (1)首先编一个主文本菜单,通过输入各菜单项的编号,调用以下(2)到(6)小题的功能; (2)添加“账号管理”子菜单。其有3项功能:a.添加帐号,允许交互式输入账号名和密码;b.删除账号,允许交互式输入账号名,需要验证账号是否存在;c.从使用者指定的文件批量添加20个账号。 (3)添加“磁盘管理”子菜单。其有3项功能:a.查看当前系统硬盘分区情况;b.监控根分区磁盘容量,小于给定值(该值可由使用者手动设置)时通过邮件向管理员报警;c.计算各账户家目录占用磁盘空间的大小,找出前十名账户,生成一个以当前日期命名的报告。 (4)添加“网络管理”子菜单。其有2项功能:a.测试本地局域网整个网段哪些主机处于开机状态,哪些主机处于关机状态;b.实时显示本地网卡(使用者可指定)发送的数据包流量。 (5)自动备份功能:要允许设置时间周期,需要备份的源文件目录,生成的归档文件要求是一个以日期时间命名的压缩文件。 (6)添加“文件管理”子菜单。其有3项功能:a. 统计某目录(由使用者指定)下有多少个文件,并显示这些文件名;b. 给某目录下(由使用者指定)的所有文件批量改名;c. 修改指定文件的权限。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值