题目1 shell 程序设计

题目1  shell 程序设计
题目1  shell 程序设计

题目1 shell 程序设计

1.1 实验目的

Linux操作系统中shell是用户与系统内核沟通的中介,它为用户使用操作系统的服务提供了一个命令界面。用户在shell提示符($或#)下输入的每一个命令都由shell先解释,然

后传给内核执行。本实验要求用C语言编写一个简单的shell程序,希望达到以下目的:

●用C语言编写清晰易读、设计优良的程序,并附有详细的文档。

●熟悉使用Linux下的软件开发工具,例如gcc、gdb和make。

●在编写系统应用程序时熟练使用man帮助手册。

●学习使用POSIX/UNIX系统调用、对进程进行管理和完成进程之间的通

信,例如使用信号和管道进行进程间通信。

●理解并发程序中的同步问题。

●锻炼在团队成员之间的交流与合作能力。

1.2 实验要求

1.2.1 ysh解释程序的重要特征

本实验要实现一个简单的命令解释器,也就是Linux中的shell程序。实验程序起名为ysh,要求其设计类似于目前流行的shell解释程序,如bash、csh、tcsh,但不需要具备那么复杂的功能。ysh程序应当具有如下一些重要的特征:

●能够执行外部程序命令,命令可以带参数。.。

●能够执行fg、bg、cd、history、exit等内部命令。

●使用管道和输入输出重定向。

●支持前后台作业,提供作业控制功能,包括打印作业的清单,改变当前

运行作业的前台/后台状态,以及控制作业的挂起、中止和继续运行。

除此之外,在这个实验中还须做到:

●使用make工具建立工程。

●使用调试器gdb来调试程序。

●提供清晰、详细的设计文档和解决方案。

1.2.2 ysh解释程序的具体要求

1. Shell程序形式

本实验的ysh程序设计不包括对配置文件和命令行参数的支持。如果实现为像bash那样支持配置文件,当然很好,但本实验并不要求。ysh应提供一个命令

提示符,如ysh>,表示接收用户的输入,每次执行完成后再打印下一个命令提示符ysh>。当用户没有输入时,ysh需要一直处于随时等待输入状态,同时在屏幕上显示一些必要的信息。

2. 外部命令和内部命令

在大多数情况下,用户输入的命令是执行存储在文件系统中的可执行程序,我们叫做外部命令或外部程序。ysh应当支持在执行这些程序时可以将输入输出重新定向到一个文件,并允许若干个程序使用管道串联起来。从本实验的角度来讲,我们把由管道连接起来的复合命令以及单独使用的命令统称为作业。

外部命令的形式是一系列分隔的字符串。第一个字符串是可执行程序的名字,其他的是传给这个外部程序的参数。如果第一个字符串所声明的可执行文件并不存在或者不可执行.则认为这个命令是错误的。

解释器还须支持一些内部命令,这些命令在ysh程序内部实现了特定的动作,下面是一些内部命令,如果用户提交了一个内部命令,ysh应当按照下面的描述执行相应动作。

●exit:结束所有的子进程并退出ysh。

●jobs:打印当前正在后台执行的作业和挂起的作业信息。输出信息应采

用便于用户理解的格式。jobs自身是一条内部命令,所以不需要显示在

输出上。

●fg %:把所标识的作业放到前台运行。如果这个作业原来已

经停止,那么让它继续运行。shell应当在打印新的命令提示符之前等待

前台运行的子进程结束。

●bg %:在后台执行标识的已挂起的进程。

3.命令行

当用户在提示符后面输入命令时,输入的整行内容叫做“命令行字符串”,ysh应当保存每一条命令行字符串,直到它表示的作业执行结束,其中包括后台作业和被挂起的作业。

ysh应当给每一个命令行字符串赋一个非负整数标识符。这个整数用来标识存储作业的数据结构,作业的数据结构应包含整个命令行字符串所表示的内容。一旦命令行字符串代表的作业执行结束,ysh就要删掉表示这个作业的数据结构。标识符可以循环使用。

对于包含内部命令的命令行字符串,不需要为它们建立作业的数据结构,因为它们本身的内容全部包含在ysh程序中。

4.前台和后台作业

ysh应当能够执行前台和后台作业。shell在前台作业执行结束之前要一直等待。而在开始执行后台作业时要立刻打印出提示符ysh>,让用户输入下一条命令。

前台作业的执行总是优先于执行一个后台作业,ysh不需要在打印下一个提示符前等待后台作业的完成,无论是否有后台作业的执行,只要完成一个前台作业,便立即输出提示符ysh>。一个后台作业结束时,ysh应当在作业执行结束后立刻打印出一条提示信息。下面语法中会在命令语法分析程序中介绍相应的语法来支持后台作业。

5.特殊键

又称组合键。通过终端驱动程序,特殊的组合键可以产生信号给ysh,程序应当对这些信号做出适当的响应。

●Ctrl+Z:产生SIGTSTP信号,这个信号不是挂起ysh,而是让shell挂起

在前台运行的作业,如果没有任何前台作业,则该特殊键无效。

●Ctrl+C:产生SIGINT信号,这个信号不是中止ysh,而是通过ysh发出

信号杀死前台作业中的进程。如果没有任何前台作业,则该特殊键无效。6.分析用户输入

1) 分隔符和特殊字符

分析用户输入的语法分析器应具有下面介绍的功能,它能够检查用户的输入错误。如果用户输入的某些地方出错了,ysh应提供合理的出错信息。

就像商业级别的shell一样,ysh每次接受用户输入的一行命令,在用户按下回车键(Enter)后开始执行分析动作。空命令不产生任何操作,而只是打印一个新提示符。

定义空格符为分隔符,ysh应能处理命令行中间和前后出现的重复空格符。

某些字符被称做“元字符",它们在用户输入的上下文中具有特定的含义。这些字符包括“&、| 、<、>“。shell假设这些字符不会出现在程序名、参数名和文件名中,它们是ysh的保留字符。下面几小节会解释这些元字符的含义。2) 内部命令

如果命令行字符串符合前面介绍的内部命令的格式,它就作为一个内部命令被解释。如果不是,就要考虑可能是外部程序的执行,或者是错误的。

3) I/O重定向

一个程序命令后面可能还跟有元字符“<”或“>”,它们是重定向符号,而在重定向符号后面还跟着一个文件名。在“<”的情况下,程序的输入被重定向到一个指定的文件中。在“>”的情况下,程序的输出被重定向到一个指定的文件中。如果输出文件不存在,需要创建一个输出文件。如果输入文件不存在,则认为是出现了错误。

4) 管道和协同程序

在一条命令行中当若干个命令被元字符“|”分隔开时,这个元字符代表管道符号。在这种情况下,ysh为每一个子命令都创建一个进程,并把它们的输入/输出用管道连接起来。例如下面这条命令行:

progA argA1 argA2outfile

应生成progA和progB两个进程,progA的输入来自文件infile,progA的输出是progB的输入,并且progB的输出是文件outfile。这种命令行可以通过进程间通信中的管道来实现。

含有一个和多个管道的命令会在如下几种情况下产生错误:

●当其任何一个子程序执行出错时。

●除了第一个子程序以外的其他子程序的输入被重定向。

●除了最后一个子程序以外的其他子程序的输出被重定向。

由管道连接的多个进程所组成的作业只有当其所有的子进程都执行完毕后才算结束。

5)后台作业

当用户需要在后台执行一个作业时,可以在作业命令的后面加上元字符“&”。用户以该种方式输入的作业命令都必须放在后台执行,同时并不影响用户与终端的交互。

6)语法

下面给出的语法规则描述图提供了控制用户输入的一种更规范的描述形式。如果使用Linux中的现有工具lex和yacc来建立命令行分析器,还需要对这个语法进行修改,以便使它支持LALR(1)(Look Ahead Left Reduction)分析方法。这个语法并不包括特殊键,因为它们不会在用户输入时显示出来,而是需要单独处理。

CommandLine代表用户的合法输入,它作为ysh要执行的一条“指令”。这里假设存在一个词法分析器,它将空格符作为分隔符,并识别元字符作为一个词法记号等。

CommandLine := NULL

FgCommandLine

FgCommandLine&

FgCommandLine := SimpleCommand

FirstCommand MidCommand LastCommand SimpleCommand := ProgInvocation InputRedirect OutputRedirect

FirstCommand := ProgInvocation InputRedirect

MidCommand := NULL

| ProgInvocation MidCommand

LastCommand := |ProgInvocation OutputRedirect

ProgInvocation := ExecFi le Args .

InputRedirect := NULL,

OutputRedirect := NULL

>STRING

ExecFile := STRING

Args := NULL

STRING Args

STRING := NULL

CHAR STRING

CHAR := 0 |1 |…| 9| a |b |…| z | A | B |…| Z

7.实验步骤建议

(1)阅读关于fork、exec、wait和exit系统调用的man帮助手册。

(2)编写小程序练习使用这些系统调用。

(3)阅读关于函数tcsetpgrp和setpgid的man帮助手册。

(4)练习编写控制进程组的小程序,要注意信号SIGTTIN和SIGTTOU。

(5)设计命令行分析器(包括设计文档)。

(6)实现命令行分析器。

(7)使用分析器,写一个简单的shell程序,使它能执行简单的命令。

(8)增加对程序在后台运行的支持,不必担心后台作业运行结束时要打印一条信息(这属于异步通知)。增加jobs命令(这对于调试很有帮助)。

(9)增加输入输出重定向功能。

(10)添加代码支持在后台进程结束时打印出一条信息。

(11)添加作业控制特征,主要实现对组合键Ctrl+Z、Ctrl+C的响应,还有实现fg和bg命令功能。

(12)增加对管道的支持。

(13)实现上面的所有细节并集成。

(14)不断测试。

(15)写报告。

(16)结束。

1.3相关基础知识

1.3.1 shell与内核的关系

shell是用户和Linux内核之间的接口程序,如果把Linux内核想象成一个球体的中心,shell就是包围内核的外壳,如图5—1所示。当从shell或其他程序向Linux传递命令时,内核会做出相应的反应。shell是一个命令语言解释器,它拥有自己内建的shell命令集,shell也能被系统中其他应用程序所调用。用户在提示符ysh>下输入的命令都是由shell先解释后传给Linux核心的

1.3.2 系统调用

系统调用是一个“函数调用”,它控制状态的改变。系统调用与普通函数过程的区别在于系统调用的执行会引起特权级的切换,因为被调用的函数处于操作

系统内核中,是内核的一部分。

操作系统定义了一个系统调用集合。为了安全起见,调用操作系统内部的函数必须谨慎地控制,这种控制是由硬件通过陷阱向量执行的。只有那些在操作系统启动时填入陷阱向量的地址,才是正当而且有效的系统调用地址。因此,系统调用就是一种在受约束的行为下进入保护核心的“函数调用”。

因为操作系统负责进程控制和调度,ysh就需要调用操作系统内部的函数来控制它的子进程。这些函数叫做系统调用。在Linux中,我们可以区分系统调用和用户应用层次的库函数,因为系统调用函数手册在“帮助”手册的第二部分,而库函数在手册的第三部分。在Linux中可以通过man命令查询“帮助”手册。例如,使用命令man fork会给出手册第二部分关于fork系统调用的描述,而命令man 2 exec会给出exec系统调用族的描述(2表示手册的第二部分)。还有很多其他的系统调用,都可以通过man命令来查阅,你会发现man是很有用的查阅参考手册的命令。

下面是在实验中会用到的重要的UNIX系统调用。

●pid_t fork(void):创建一个新的进程,它是原来进程的一个副本。在

fork成功返回后,父进程和子进程都要继续执行fork后的指令。这

两个进程通过fork的返回值进行区分,对父进程fork的返回值是子

进程的进程号,对子进程的返回值是0。

●int execvp(const char*file,char*const argv[]):加载一个可执行程序到

调用进程的地址空间中,然后执行这个程序。如果成功,它就会覆

盖当前运行的进程内容。有若干个类似的exec系统调用。

●void exit(int status):退出程序,使调用进程退出,程序结束。它把

status作为返回值返回父进程,父进程通过wait系统调用获得返回

值。链接器会为每一个程序结尾链接一个exit系统调用。

●int wait(int*stat_loc):如果有退出的子进程,则返回退出的子进程的

状态;如果没有任何子进程在运行,则返回错误。如果当前有子进

程正在运行,则函数会一直阻塞直到有一个子进程退出。

●pid_t waitpid(pid_t pid,int*stat_loc,int options):类似于函数wait,但

允许用户等待某个进程组的特定进程,并可以设置等待选项,例如

WNOHANG。

●int tcsetpgrp(int fildes,pid_t pgid_id):将前台进程组ID设置为pgid_id,

fildes是与控制终端相联系的文件描述符。终端通常指标准输入、标

准输出和标准错误输出(文件描述符为0、1、2)。

●int setpgid(pid_t pid,pid_t pgid):把pid进程的进程组ID设置为pgid。

●int dup2(int fildes,int flides2):把ftildes文件描述符复制到fildes2。如

果fildes2已经打开,则先将其关闭,然后进行复制,使filedes和fildes2

指向同一文件。

●int pipe(int fildes[2]):创建一个管道,把管道的读和写文件描述符放

到数组fildes中。

1.进程创建

使用fork系统调用创建新的进程。fork克隆了调用进程,两者之间只有很少的差别。新进程的进程号pid和父进程号ppid与原来的进程不同。其他不同之处可以查看man手册得到。

fork的返回值是程序中惟一能够区别父进程和子进程的地方。fork对父进程

返回子进程的进程号,对子进程则返回0。利用这个细小的区别可以使两个进程执行不同的程序段。

wait函数族允许父进程等待子进程执行结束。在ysh创建一个前台进程时会用到它。

须特别注意的是wait函数族会在子进程状态改变时返回,而不仅仅是在子进程运行结束或者退出时才返回,其中有些状态的变化可以被忽略。在man手册中有关于函数waitpid的参数说明,其中有WNOHANG和其他一些有用的参数(WNOHANG指定在没有子进程退出时,父进程不阻塞等待)。

下面的例子介绍创建进程和等待子进程运行结束。

int main(int argc,char*argv[])

{

int status;

int pid;

char*prog_arv[4];

/*建立参数表*/

prog_argv[0]=”/bin/ls”;

prog_argv[1]=”-1”;

prog_argv[2]=”/”;

prog_argv[3]=NULL;

/*

*为程序ls创建进程

*/

if((pid=fork())<0)

{

perror(”Fork failed”);

exit(errno);

}

if(!pid)

{

/*这是子进程,执行程序ls*/

execvp(prog_argv[0],prog_argv);

}

if(pid)

{

/*

*这是父进程,等待子进程执行结束

*/

waitpid(pid,NULL,0);

}

}

shell程序等待子进程执行结束是很重要的。对一个作业等待子进程发出信号进行处理可以采用阻塞等待的方式,或是采用非阻塞的方式。尽管在进程死亡时它的许多资源都会被释放,但是进程控制块和其他的一些信息还没有释放,这种状态被称为defunct。进程控制块包含了退出的状态信息,它可以通过wait函数

族获得。在wait pid函数调用完之后,进程控制块就被释放了。如果父进程在子进程之前结束,那么子进程就会成为init进程的孩子,init进程会等待任何子进程的结束,释放进程控制块。那些已经终止,但父进程尚未对其进行状态搜集的进程就成为僵尸进程。

2.exec系统调用

exec函数族允许当前进程执行另外一个程序。典型的应用是一个程序调用fork生成自身的一个副本,然后子进程调用exec执行另外一个程序。

exec有许多不同形式,它们最终都是调用内核中的同一个函数,只是它们给用户提供了更多的调用形式。

调用exec的进程不是和原来完全不同,而是调用后进程继承了原来的父进程标识、组标识和信号掩码,但不包括信号处理程序。详细信息可以查看man手册。

除非产生错误,否则exec函数从来不返回(从此时开始执行新的程序代码)。上面的例子介绍了函数execvp被系统调用的使用方法。

3. I/0重定向

为了实现I/0重定向,需要使用函数dup2:

int dup2(int fiides,int flides2);

每个进程都有一张它所打开的文件描述符表,每个表项包含了文件描述符标识和指向系统文件表中相对应表项的指针。这个系统文件表是由内核维护,它记录了系统当前打开的所有文件的信息,其中包括打开这个文件的进程数目、文件状态标志(读、写、非阻塞等)、当前文件指针、指向该文件inode节点表项的指针。

还应当认识到许多非文件类的机制也使用文件接口,只是它们的操作被包装了。例如很多场合终端也被当做文件来操作。默认情况下,进程的文件列表中的前三个入口都指向终端:标准输入(0),标准输出(1)和标准错误输出(2)。

为实现I/O重定向,我们要打开一个文件,并把它的文件描述符入口复制给标准输入或者标准输出(或者标准错误输出)。如果需要在后面恢复原来的入口项,我们可以事先把它保存在文件列表中别的地方。

4.信号

信号是最简单的进程间通信(IPC)原语。信号允许一个进程在某一事件发生时与另一个进程通信。信号的值表明发生了哪种事件。

信号对本实验来说是很重要的,它们指出了后台运行的子进程状态发生的变化,比如子进程的正常终止。当一个进程接收到一个信号时,它会采取某些动作。许多信号都有默认的动作。比如,某些信号默认产生core dumps,或者进程自身挂起。

我们也可以声明让自己的进程处理某个信号。通过声明一个信号处理程序可以做到这一点。Linux主要有两个函数实现对信号的处理,即signal和sigaction。其中signal是库函数,在可靠信号系统调用的基础上实现。它只有两个参数,不支持信号传递信息;而sigaction是较新的函数,有三个参数,支持信号传递信息,同样支持非实时信号的安装。函数sigaction优于signal,主要体现在支持信号带有参数。

1)函数signal

格式

#include

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,sighandler_t handler);

参数说明

signum指定信号的值。

handler指定针对前面信号值的处理,可以忽略该信号(参数设置为SIG-IGN);可以采用系统默认方式处理信号(参数设置为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

如果signal调用成功,返回最后一次为安装信号signum而调用signal时的handler值;失败则返回SIG_ERR。

2)函数sigaction

梧式

#include

int sigaction(int signum,const struct sigaction*act,struct sigaction

*oldact));

参数说明

sigaction函数用于改变进程接收到特定信号后的行为。

signum为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致错误)。

act是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以默认方式对信号处理。这个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽哪些函数。

oldact是指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把signum和act都设置为NULL,那么该函数可用于检查信号的有效性。

sigact i on结构定义

struct sigaction {

void(*sa_handler)(int);

void(*sa_sigaction)(int,siginfo_t*,void*);

sigset_t sa_mask;

unsigned long sa_flags;

void(*sa_restorer)(void);

}

数据结构中的两个元素sa_hanlder和sa_sigaction是指定信号关联函数。除了可以是用户自定义的处理函数外,还可以为SIGDFL(采用默认的处理方式),也可以为SIG_DFL(忽略信号)

参数说明

sa_handler:由它指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息

sa_sigaction:由它指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),这个信号处理函数的第一个参数(int)为信号值,第三个参数(void*)没有使用(POSIX标准中没有规范使用该参数的标准),第二个参数(siginfo_t* )是指向siginfo_t结构的指针,结构中包含信号携带的数据值,

参数所指向的结构如下:

siginfo_t {

int si_signo;/*信号值*/

int si_errno;

int si_code;

pid_t si_pid;/*发送信号的进程ID‘/

uid_t si_uid;

int si_status;

clock_t si_utime ;

clock_t si_stime ;

sigval_t si_value ;

int si_int ;

void* si_ptr ;

void* si_addr ;

int si_band ;

int si_fd ;

}

sa_mask:指定在信号处理程序执行过程中,哪些信号应当屏蔽,如果不指定SA_NODEFER或者SA_NOMASK标志位,默认情况下则屏蔽当前信号,防止信号的嵌套发送。

sa_flags :其中包含了许多标志位,包括前面提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是SA_SIGNFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不是为sa_handler指定信号处理函数,否则设置该标志无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGNFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segment fault)。

sa_restorer :现在已经不是用了,POSIX标准中并未定义它。

下面是一个信号处理程序的例子:

void ChildHandler (int sig,siginfo_t*sip, void*notused)

{

int status;

printf ( “The process generating the signal is PID:%d\n”,sip->si-pid);

fflush(stdout);

status=0;

/*WNOHANG标识如果没有子进程退出,就不等待*/

if(sip->si_pid==waitpid(sip->si_pid,&status,WNOHANG))

{

/*SIGCHLD并不意味着子进程执行结束*/

i f(WIFEXITED(status)||WTERMSIG(status))

printf(“The child is gone\n”);/*子进程退出*/

else

printf (“Uninteresting\n”);/*alive*/

}

else

{

printf (“Uninteresting\n”);

}

}

int main()

{

struct sigaction action;

action.sa_sigaction=ChildHandler; /*注册信号处理函数*/

sigfillset(&action.sa_mask);

action.sa_flags=SA_SIGINFO; /*向处理函数传递信息*/

sigaction(SIGCHLD,&action,NULL;

fork();

while(1)

{

printf(“PID:%d\n”,getpid());

sleep(1);

}

}

5.管道

将一个程序或命令的输出作为另一个程序或命令的输入,有两种方法,一种是通过一个临时文件将两个命令或程序结合在一起,这种方法由于需要临时文件而显得很累赘;另一种是Linux所提供的管道功能,这种方法比前一种方法好。管道是更加复杂的IPC(进程间通信)工具,它允许数据从一个进程向另一个进程单向地流动。

管道实际上是利用文件系统中的循环缓冲区。我们以生产者-消费者模式来使用它,写进程相当于生产者,而读进程相当于消费者。一个进程向管道写数据,如果缓冲区写满了,则阻塞写进程。另一个进程从管道中读数据,如果管道为空,则读进程阻塞。当写进程关闭了管道或者该进程死掉时,读进程就会失败。如果读进程关闭管道或者该进程死掉时,写进程就会失败。

下面说明管道如何工作。我们在父进程中使用pipe系统调用创建一个管道,系统调用的参数是包含两个文件描述符数组:pfd[0]和pfd[1]。

(1)和文件描述符一样,使用pfd[0]作为管道输入描述符,使用pfd[1]作为管道输出描述符。

(2)fork产生子进程。

(3)现在父进程和子进程都共享了管道文件描述符。每个进程都关闭管道的一端(至于哪一端则取决于谁作为读进程,谁作为写进程)。

(4)每个进程使用dup2把打开的管道描述符复制给标准输入或者标准输出,然后关闭管道描述符(如果后面还要恢复标准输入或者标准输出,则要把它们事先保留起来)。

(5)现在两个进程可以使用管道通过标准输入和标准输出进行通信了。

如果我们在fork和exec之间完成这些动作,那么可以将进程通过管道连接起来。

下面是一个管道的例子:

int main(int argc,char*argv[])

{

int status;

int pid[2];

int pipe_fd[2];

char*progl_argv[4];

char*prog2_argv[2];

/*建立参数表*/

progl_argv[0]=”*/usr/local/bin/ls”;

progl_argv[1]=”-1”;

progl_argv[2]=”/”;

progl_argv[3]=NULL;

prog2_argv[0]=”/usr/bin/more”;

prog2_argv[1]=NULL;

/*创建管道*/

if(pipe(pipe_fd)<0)

{

perror(”pipe failed”);

exit(errno);

}

/*为ls命令创建进程*/

i f((pid[0]=fork())<0)

{

perror(”Fork failed”);

exit(errno);

}

if(!pid[0])

{

/*将管道的写描述符复制给标准输出,然后关闭*/

close(pipe_fd[0]);

dup2(pipe_fd[1],1);

close(pipe_fd[1]);

/*执行ls命令*/

execvp(progl—argv[0],progl_argv);

if(pid[0])

{

/*父进程*/

/*为命令more创建子进程*/

if((pid[1]=fork())<0)

{

perror(“Fork failed”);

exit(errno);

}

if(!pid[1])

{

/*在子进程*/

/*将管道的读描述符复制给标准输入*/

close(pipe_fd[1]);

dup2(pipe_fd[0],0);

close(pipe_fd[0]);

/*执行more命令*/

execvp(prog2一argv[0],prog2_argv);

}

/*父进程*/

close(pipe_fd[0]);

close(pipe_fd[1]);

waitpid(pid[1],&status,0);

printf(”Done waiting for more.\n”);

}

}

6.进程组、会话和进程组、会话和作业控制

当用户登录操作系统以后,操作系统为会话(sesson)分配一个终端。会话是指进程运行的环境,即与进程相关的控制终端。shell被分配到前台的进程组中。进程组是指若干个相关进程的集合——它们通常是通过管道连接的。一个终端至多能与一个进程组相关联。前台进程组是会话中能够访问控制终端的进程组。因为每个会话只有一个控制终端,所以只存在一个前台进程组。

前台进程组中的进程可以访问标准输入和标准输出。这也意味着组合键会使控制终端把产生的信号发送给前台进程组中的所有进程;在Ctrl+C的操作下信号SIGINT会发送给前台的每一个进程;在Crtl+Z的操作下信号SIGTSTP会发送给前台的每一个进程。这些组合键不会显示在终端上。

同时还存在着后台进程组,它们不能访问会话中控制终端的进程组。因为它们不能访问控制终端,所以不能进行终端的I/O操作。如果一个后台进程组试图与控制终端交互,则产生SIGTTOU或者SIGTTIN信号。默认情况下,这些信号像SIGTSTP一样会挂起进程。ysh必须处理其子进程中发生的这些变化。

进程可以使用函数setpgid,使其加入到某一进程组中。进程组ID是以组长的进程ID命名的。进程组长是创建该组的第一个进程,用它的进程号作为进程组的组号。进程组长死后进程组仍然可以存在。

一个进程组可以使用函数tcsetpgrp成为前台进程组。这个函数调用使指定的进程组成为前台进程组,这不仅会影响到组本身也会影响到该组的任何一个子进程。

如果进程调用函数setsid创建了一个新的会话,那么它就成为了会话组长和进程组组长。对于一个要与终端交互的新会话来说,必须为它分配一个新的终端。由于是在系统shell(csh、sh、bash等)下执行ysh,这会使ysh代替原来的shell,成为前台进程组中的惟一进程。

让某个进程组成为前台进程组,不仅保证它能够与控制终端保持联系,还保证了前台进程组中的每一个进程都能从终端接收到控制信号,如SIGTSTP。简

单的方法是,把所有的子进程放在同一个进程组中,在创建它们时屏蔽SIGTSTP 信号,这样只有shell才能接收到这个信号,然后shell发出与SIGTSTP作用相同的(但没有屏蔽的)SIGSTOP信号给某个子进程。这种办法在某些时候会使程序运行失败,比如在自己的ysh中再次运行ysh,但这种办法对于本实验来说是不涉及的。

下面是一个进程组的例子:

#include

#include

#include

#include

#include

#include

/*本程序体现了函数tcsetgrp和setpgrp的用法,但没处理SIGTTIN、SIGTTOU信号*/

int main()

{

int status;

int cpid;

int ppid;

char buf[256];

sigset_t blocked;

ppid=getpid();

if (!(cpid=fork()))

{

setpgid(0,0);

tcsetpgrp (0,getpid());

execl (“/bin/vi”,”vi”,NULL);

exit (-1);

}

if(cpid<0)

exit(-1);

setpgid(cpid,cpid);/*设置进程组*/

tcsetpgrp(0,cpid);/*设置控制终端为子进程拥有*/

waitpid(cpid,NULL,0);

tcsetpgrp(0,ppid)j

while(1)

{

memset(buf,0,256);

fgets(buf,256,stdin);

puts(”ECHO:”);

puts(buf);

puts(”\n”);

}

}

1.4实验环境

本实验的程序用C语言编写,使用makefile文件编译整个程序,生成一个名为ysh可执行程序,在终端输入“./ysh”即可执行。

makefile文件的内容如下:

ysh:ysh.c

cc ysh.c—o ysh

1.5程序的实现

1.5.1数据结构

在这个程序中,我们用到的数据结构主要有循环数组和链表。

1.循环数组

在history命令中,用数组来存放我们输入过的历史命令。假设我们设定一个能够记录12条历史纪录的数组如图5.2所示。数组的定义如下:

typedef struct ENV_HISTROY{

int start=0;

int end=0;

char his_cmd[12][100];

}ENV_HISTORY;

ENV_HISTORY envhis:

可以看到,每个his_cmd[i]对应图中一块圆环(end不一定为12),一共12块,能存放十二条命令。当用户输入一命令时,只须执行如下语句即可将输入命令存放进相应数组中:

envhis.end=envhisend+l;

strcpy(his_cmd[envhis.end],input)。

但是还需要考虑如图5.3所示的情况

在这种情况下,end=12,当我们在输入一条命令时,如果还是用上述两条命令进行处理end=end+l,则end=13就会出错。所以应对程序进一步修改:envhis.end=(envhis.end+1)%12;

if(envhis.end==envhis.start){

envhis.start=(envhis.start+1)%12;

}

strcpy(envhis.his_cmd[envhis.end],input);

经过这样的处理,就可以达到循环的目的了

2.链表

由于我们把作业以链表的形式保存起来,所以在处理jobs命令时,实际上就是对链表的操作。

首先定义链表的节点:

typedef struct NODE(

pid_t pid;/*进程号*/

char cmd[100];/*命令名*/

char state[10];/*作业状态*/

struct NODE 1ink;/*下一节点指针*/

)NODE;

NODE*head,*end;

head指针指向链表表头,end指针指向链表尾

1.5.2程序结构

在shell命令里,我们将ysh中的命令分成4种:普通命令,重定向命令,管道命令和内部命令。这4种命令的分析和执行各有不同,每一种命令的分析执行程序都应包括:初始化环境,打印提示符,获取用户输入的命令,解析命令,寻找命令文件和执行命令几个步骤,具体程序流程如图 5.4所示。

1.初始化环境

程序一开始,需要对一些环境变量进行初始化。比如将查找路径放入envpath[]中,初始化history和jobs的头尾指针等。这部分工作在程序中由函数init_environ 来完成。

void init_environ()

{

int fd,n,i;

char buf[8 0];

if((fd=open(”ysh_profile”,O_RDONLY,660))==-1)

{

printf(”init environ variable error\n”);

exit(1);

}

while(n=getline(fd,buf)){

getenviron(n,buf);

}

envhis.start=0;

envhis.end=0;

head=end=NULL;

}

可以看到这个函数打开一个名为ysh_profile的文件,它是我们定义的配置文件。然后调用了另外两个函数getline(fd,buf)和getenviron(n,buf)。getline(fd,buf)的作用是读取一行的信息到buf中。getenviron(n,buf)的作用是将getline(fd,buf)读到buf中的信息以冒号分开,分别放于envpath[]中(见ysh.h中的定义),为后面查找命令做准备。这样命令查找的准备工作就做好了。接下来函数返回到函数init__environ。接下来的语句将envhis.start和envhis.end置0。这两条语句的作用是初始化保存history命令链表的头尾指针。而语句head=end=NULL是初始化保存jobs命令链表的头尾指针。至此,程序的初始化工作基本完成。

接下来就进入一个while循环,就和一般的shell一样,当一个命令执行完或放到后台后,shell就可获取新的用户输入命令。

2.解析命令

在这里对读到input数组中的命令进行分析,以获取命令和参数。ysh中的命令分成4种:普通命令,重定向命令,管道命令和内部命令。我们对管道和重定向命令单独处理。

for(i=0,j=0,k=0;i<=input_len;i++){

i f(input[i]==??|| input[i]==?|?){

if(input[i]==?|?){

pipel(input,input_len);

add_history(input);

free(input);

}else{

redirect(input,input_len);

add_history(input);

free(input);

}

is_pr=l;

break;

}

}

该for循环就把带“>”、“<’’和‘‘|’’符号的管道和重定向命令单独处理。同时把is_pr这个管道和重定向命令的标志置l。然后分别调用redirect(input,inpuuen)和pipel(input, input_len)两个函数来处理这两类命令。这两个函数在后面会讨论。

对于一般命令,当is_pr的值为零时,则继续执行下步程序。下面是分析和处理普通命令和内部命令。

for(i=0,j=0,k=0;i<=input_len;i++){

if(input[i]==?…||input[i]==?\0?){

if(j==0) /*这个条件略去连在一起的多个空格*/

continue;

else{

buf[j++]=?\0?;

arg[k]=(char*)malloc(sizeof(char)*j);

strcpy(arg[k++],buf);/*将指令或参数复制到arg中*/

j=0;/*准备取下一参数*/

}

}else{

/*如果字符串最后是&,将后台命令标志置1*/

if(input[i]==?&?。&&input[i+1]==?\0?){

is_bg=l;

continue;

}

buf[j++]=input[i];

}

}

该for循环的作用是对input中的输入命令进行解释。用户的输入以空格区分命令和参数,如:“ls-1”。这个循环的作用就是分析这条输入命令,把命令ls放进arg[0]中,而参数-l则在arg[1]中。注意,整个程序对input的分析都是遵循这样的原则:以空格分段,分别放在arg[i]中。

经过这个for循环,arg[0]中的命令对我们来说就至关重要了。我们可以通过判断arg[0]来判断用户的输入是普通命令还是内部命令(history、jobs、exit、cd 等)。如果是内部命令,则根据不同的命令,执行不同的动作。这里先不讨论这些内部命令。

3.查找外部程序

既然不是管道和重定向命令,也不是内部命令,那么一定是普通命令(即外部命令)了。对于普通命令的处理就是查找命令的可执行文件。

if(is_pr==0{/*非管道、重定向命令*/

/*在使用exec执行命令时,最后的参数必须是NULL指针,所以将其置空*/ arg[k]=(char*)malloc(sizeof(char));

arg[k]=NULL;

if (is_founded(arg[0] )==0){/ *查找arg[0]中的命令是否存在*/

printf(”This command is not founded!\n”);

for(i=0;i<=k;i++)

free(arg[i]);

continue;

}

}

这里的关键是调用了一个查找命令文件的函数is_founded来判断输入的命令是否存在。下面我们看看如下函数:

int is_founded(char*cmd)

{

int k=0;

while(envpath[k]!=NULL){

/*查找路径已在程序初始化时设置在envpath[i]中*/

strcpy(buf,envpath[k]);

strcat(buf,cmd);

if(access(buf,F_OK)==0) /*文件被找到*/

return 1;

k++;

}

return 0;

}

回顾前面程序初始化时,已经将命令可能存在的路径置于envpath[i]数组中,函数is_founded做的工作就是在相应的路径下查找、判断命令是否存在。如果找到返回1,没有则返回0。判断时用到了系统调用函数access。

格式

#include

int access(const char*pathname,int mode);

参数说明

pathname是文件名称。

mode是我们要判断的属性,可以取以下值或者是它们的组合。R_OK表示文件可以读,W_OK表示文件可以写,X_OK表示文件可以执行,F_OK表示文件存在。当我们测试成功时,函数返回零,否则如果有一个条件不符时,返回一1。

如果命令找到,就在函数is_founded中把路径和命令存放到buf数组中。下面就可以开始执行命令的工作了。

4.执行命令

当命令文件在指定的路径下查找成功时,就可以执行命令了。通过调用函数fork创建一个子进程,在子进程中执行命令。函数fork创建一个新的子进程,其子进程会复制父进程的数据与堆栈空间并继承父进程的用户ID、组ID、环境变量,以及打开的文件、工作目录和资源限制等。

Linux使用COW(copy on write)}技术,只有当其中一个进程试图修改欲复制的空间时才会做真正的复制动作。由于这些继承的信息是复制而来的,并非指向相向的内存空间,因此应当注意:子进程中对变量的修改,在父进程中不能与其同步!也就是说,即使子进程中修改了一个变量的值,在父进程中,这个变量的值仍为修改前的值!

如果函数fork创建成功,则父进程返回新建子进程的ID(pid)。值得注意的是:pid在子进程中为0,而在父进程中pid则为子进程真实的ID,如果失败则返回-1。

这一段程序如下:

if((pld=fork())==0) /*子进程*/

execv(buf,arg);

else /*父进程*/

if(is_bg==0) /*非后台执行命令*/

waitpid(pid,&status,0);

上面几行语句就可以执行shell的普通命令了。这里用到了两个系统调用函数execv和waitpid。

函数waitpid格式:

简单的C语言程序设计实验报告完美版

本科实验报告专用纸 课程名称C语言程序设计成绩评定 实验项目名称简单的C语言程序设计 实验项目编号实验项目类型验证型 实验地点指导教师 学生姓名学号 学院专业 一、实验目的 1.掌握各种类型数据的输入输出的方法,能正确使用各种格式转换符。 2.学会正确使用逻辑运算符和逻辑表达式。 3.熟练掌握if语句和switch语句。 4.结合程序掌握一些简单的算法。 二、实验内容和实验要求 实验内容1::试编写程序,用getchar函数读入两个字符给c1,c2,然后分别用putchar函数和printf函数 输出这个字符。 实验要求:(1) 输入事先已编好的程序,并运行该程序。分析运行结果是否正确。 (2)比较用Printf函数和putchar函数输出字符的特点。 实验内容2:给出一百分制成绩,要求输出成绩等 级’A’,’B’,’C’,’D,’E’。90分以上为’A’,80-89分 为’B’,70-79分为’C’,60-69分为’D’,60分以下 为’E’。 第1页(共11页)

实验要求:(1)事先编写好程序,要求分别用if语句和switch 语句来实现。 (2)输入程序,并运行该程序。分析运行结果是 否正确。 (3)再运行一次程序,输入分数为负值(如-70), 这显然是输入是出错,不应该给出等级,修改程序, 使之能正确处理任何数据,当输入数据大于100和 小于0时,通知用户“输入数据错”,程序结束。 三、主要仪器设备 仪器:计算机 实验环境:windowsXP+visual c++6.0 四、实验过程 实验内容1: 原理:1)用getchar函数输入两个字符赋给c1,c2; 2) 用putchar函数输出这两个字符; 3) 用printf函数输 出这两个字符。 (1)源程序

实验一简单程序设计实验

实验一:简单程序设计实验 (1)编写一个 32 位无符号数除法的程序,要求将存放在 NUM1 中的 32 位无符号数与存放 在 NUM2 中的 16 位无符号数相除,结果存放在 NUM3 和 NUM4 中。 程序流程图略。 参考源程序: DATA SEGMENT NUM1 DD 2A8B7654H NUM2 DW 5ABCH NUM3 DW ? NUM4 DW ? DATA ENDS CODE SEGMENT ASSUME DS:DATA, CS:CODE START: MOV AX,DATA ;数据段寄存器初始化 MOV DS,AX MOV AX, WORD PTR NUM1 MOV DX, WORD PTR NUM1+2 DIV NUM2 MOV NUM3,AX MOV NUM4,DX MOV AH,4CH ;正常返回DOS 系统 INT 21H CODE ENDS END START (2)编写一个拆字程序。要求将存放在 ARY 单元的 2 位十六进制数 X 1X 2 拆为 X 1 和 X 2 两 部分,并以 0X 1 和 0X 2 的形式分别存入 ARY+1 和 ARY+2 单元中。 程序流程图略。 参考源程序: DATA SEGMENT ARY DB 2AH,?,? DATA ENDS CODE SEGMENT ASSUME DS:DATA, CS:CODE START: MOV AX,DATA MOV DS,AX MOV SI,OFFSET ARY ;取ARY 的偏移地址 MOV AL,[SI] ;取16进制数至AL

MOV BL,AL AND AL,0F0H ;取16进制数的高四位,即X1 SHR AL,4 MOV [SI+1],AL ;存0X1 MOV AL,BL AND AL,0FH ;取16进制数的低四位,即X2 MOV [SI+2],AL ;存0X2 MOV AH,4CH INT 21H CODE ENDS END START

实验3简单的程序设计

实验3简单的程序设计 实验目的: 1.掌握表达式、赋值语句的正确书写规则。 2.掌握VB变量的定义和使用,表达式和常用函数的使用。 3.掌握InputBox与MsgBox的使用。 实验3.1函数考察 实验任务: 考察下列函数的值。 Round(-3.5) Round(3.5) Chr(66) Asc ("c") Asc(Chr(99)) Chr(Asc("K")) Ucase$("abcdefg") Lcase(“ABC”) Str(123.45) Val(“123AB”) Len(“123程序设计ABC”) LenB(“123程序设计ABC”) Ltrim(“ ABC”) String(3, “ABC”) Instr(“EFABCDEFG”, “ef”) Instr(2,“EFABCDEFG”, “ef”,1) Date() Now() Time() 实验步骤: 先自己分析以上函数的功能和可能的结果,然后在立即窗口用Print方法求出相应函数的值,对照比较自己的判断。 22

实验3.2表达式考察 实验任务: 考察下列表达式的值。 Dateadd(“m”,1,#1/30/2000#) Datediff(“y”,#12/03/1999#,#1/03/2000#) ‘计算时间间隔多少日 123 + Mid(“123456”,3,2) 123 & Mid(“123456”,3,2) Ucase(Mid(“abcdefgh”,3,4)) 16 / 4 – 2 ^ 5 * 8 / 4 MOD 5 \ 2 实验步骤: 先自己分析表达式的功能和可能的结果,然后在立即窗口用Print方法求出相应表达式的值,对照比较自己的判断。 实验3.3简单打印图形 实验任务: 使用Print方法、Tab函数和String函数设计一个过程,显示如图3-1所示的图形,并将结果保存到文件中。 图3-1 实验3.2运行界面 参考代码如下: Private Sub Form_Load() Print Tab(15); String(1, "1") Print Tab(14); String(3, "2") Print Tab(13); String(5, "3") Print Tab(12); String(7, "4") End Sub 操作提示: 应先将Form窗体的AutoRedraw属性值设为True。

试验项目试验一最简单的C程序设计

试验项目试验一最简单的C程序设计

实验项目:实验一最简单的C程序设计 (所属课程:《C语言程序设计》学时:2) 一、实验目的 1.掌握:使用标准输入/输出函数进行常见数据类型的数据的输入/输出方法。 2.初步培养编制程序框图和源程序、准备测试数据以及实际调试程序的独立编程能力。 3.掌握顺序结构程序设计的基本思路。 二、实验条件 Pc和vc++编程环境 三、实验内容 1.熟悉编程环境。 2.输入并运行一个C程序 3.掌握各种格式转换符的正确使用方法。 4.编程序:设圆半径r=1.5,圆柱高h=3,求圆周长、圆面积、圆球表面积、圆球体积、圆柱体积。要求用scanf函数从键盘上输入数据(半径和高),输出各计算结果,输出时要求有文字说明,取小数点后2位数字。5.编程序:用getchar函数输入两个字符给C1、C2,然后分别用putchar函数和printf函数输出这两个字符。 四、实验步骤 编写程序,输入数据,观察结果。

五、实验结果 观察结果和预期是否一致。 实验项目:实验二选择结构程序设计 (所属课程:《C语言程序设计》学时:2) 一、实验目的 1.了解C语言表示逻辑量的方法(以0代表“假”,以非0代表“真”)。 2.学会正确使用逻辑运算符和逻辑表示式。 3.熟练掌握if语句和switch语句 二、实验条件 Pc和vc++编程环境 三、实验内容

1.有一函数: ?????≥-<≤-<=)10x (113x ) 10x 1(12x )1x (x y 用scanf 函数输入x 的值,求y 值。 运行程序,输入x 的值(分别为x<1、1≤x <10、x ≥10三种情况),检查输出的y 值是否正确。 2.给出一个百分制成绩,要求输出成绩等级A 、B 、C 、D 、E 。90分以上为A ,81~89分为B ,71~79分为C ,61~69分为D ,60分以下为E 。 3.输入4个整数,要求按由小到大顺序输出。 四、实验步骤 编写程序,输入数据,观察结果。 五、实验结果 观察结果和预期是否一致。 实验项目:实验三 循环结构程序设计

最简单的C程序设计—顺序程序设计实验报告

嘉应学院计算机学院 实验报告 课程名称程序设计基础实验名称实验地点 指导老师实验时间提交时间 班级姓名座号 一、实验目的和要求 (1)掌握C语言中使用最多的一种语句——赋值语句的使用方法。 (2)掌握各种类型数据的输入输出的方法,能正确使用各种格式装换符。 (3)进一步掌握编写程序的和调试程序的方法。 二、实验环境和方法 实验方法: (一)综合运用课本所学的知识,用不同的算法实现在不同的程序功能。 (二)结合指导老师的指导,解决程序中的问题,正确解决实际中存在的异常情况,逐步改善功能。 (三)根据实验内容,编译程序。 实验环境:Windows xp Visual C++6.0 三、实验内容及过程描述 实验步骤: ①进入Visual C++ 6.0集成环境。 ②输入自己编好的程序。 ③检查一遍已输入的程序是否有错(包括输入时输错的和编程中的错误),如发现有错,及时改正。 ④进行编译和连接。如果在编译和连接过程中发现错误,频幕上会出现“报错信息”,根据提示找到出错位置和原因,加以改正。再进行编译,如此反复直到不出错为止。 ⑤运行程序并分析运行结果是否合理。在运行是要注意当输入不同的数据时所得结果是否正确,应运行多次,分别检查在不同情况下结果是否正确。 实验内容:编译以下题目的程序并调试运行。 实验① (1)通过下面的程序掌握各种格式装换符的正确使用方法。 ①输入以下程序:

②运行程序并分析结果如图: ③在此基础上,将程序第10~14行改为 c1=a;c2=b; f=3157.;g=0.; d=f;e=g; P=a=m=50000;q=b=n=-60000; 运行程序,分析结果如: (二)设圆半径r=1.5,圆柱高h=3,求圆周长﹑圆面积﹑圆球表面积﹑圆球体积﹑圆柱体积。 用scanf 输入数据,输出计算结果,输出时要求有文字说明,取小数点后两位数字。 程序代码为: #include int main() {int a,b; float d,e; char c1,c2; double f,g; long m,n; unsigned int p,q; a=61,b=62; c1='a';c2='b'; d=3.56;e=-6.87; f=3157.;g=0.; m=50000;n=-60000; p=32768;q=40000; printf("a=%d,b=%d\nc1=%c,c2=%c\nd=%6.2f,e=%6.2f\n",a,b,c1,c2,d,e); printf("f=%15.6f,g=%15.12f\nm=%1d,n=%1d\np=%u,q=%u\n",f,g,q,m,n,p,q); } #include int main() {float h,r,l,s,sq,vq,vz; float pi=3.; printf("请输入圆半径r ,圆柱高h :"); scanf("%f,%f",&r,&h); l=2*pi*r; s=r*r*pi; sq=4*pi*r*r; vq=3.0/4.0*pi*r*r*r; vz=pi*r*r*h;

实验项目:实验一 最简单的C程序设计

实验项目:实验一最简单的C程序设计 (所属课程:《C语言程序设计》学时:2) 一、实验目的 1.掌握:使用标准输入/输出函数进行常见数据类型的数据的输入/输出方法。 2.初步培养编制程序框图和源程序、准备测试数据以及实际调试程序的独立编程能力。 3.掌握顺序结构程序设计的基本思路。 二、实验条件 Pc和vc++编程环境 三、实验内容 1.熟悉编程环境。 2.输入并运行一个C程序 3.掌握各种格式转换符的正确使用方法。 4.编程序:设圆半径r=1.5,圆柱高h=3,求圆周长、圆面积、圆球表面积、圆球体积、圆柱体积。要求用scanf函数从键盘上输入数据(半径和高),输出各计算结果,输出时要求有文字说明,取小数点后2位数字。5.编程序:用getchar函数输入两个字符给C1、C2,然后分别用putchar 函数和printf函数输出这两个字符。 四、实验步骤 编写程序,输入数据,观察结果。 五、实验结果 观察结果和预期是否一致。 实验项目:实验二选择结构程序设计

(所属课程:《C 语言程序设计》学时:2) 一、实验目的 1.了解C 语言表示逻辑量的方法(以0代表“假”,以非0代表“真”)。 2.学会正确使用逻辑运算符和逻辑表达式。 3.熟练掌握if 语句和switch 语句 二、实验条件 Pc 和vc++编程环境 三、实验内容 1.有一函数: ?? ???≥-<≤-<=)10x (113x )10x 1(12x )1x (x y 用scanf 函数输入x 的值,求y 值。 运行程序,输入x 的值(分别为x<1、1≤x <10、x ≥10三种情况),检查输出的y 值是否正确。 2.给出一个百分制成绩,要求输出成绩等级A 、B 、C 、D 、E 。90分以上为A ,81~89分为B ,71~79分为C ,61~69分为D ,60分以下为E 。 3.输入4个整数,要求按由小到大顺序输出。 四、实验步骤 编写程序,输入数据,观察结果。 五、实验结果 观察结果和预期是否一致。 实验项目:实验三 循环结构程序设计

C程序设计上机实验报告完整版

C语言程序设计上机实验报告 学院:机械工程学院 班级:机自161213 姓名:刘昊 学号: 实验时间:2017年3月6号 任课老师:张锐 C语言程序设计上机实验报告 实验一 一、实验名称: C程序的运行环境和运行C程序的方法 二、实验目的:了解在C编译系统上如何编辑、编译、连接和运行一个C程序 三、实验内容: (1). 输入并运行一个简单的C程序。 (2). 设计程序,对给定的两个数求和。 (3). 设计程序,对给定的两个数进行比较,然后输出其中较大的数。 四、源程序代码: ??代码1: 运行结果1: ??程序分析1: 该程序用来判断所输入的整数是否为一个素数,如果一个数能被除了1和它本身整除,还能被其它数整除,那么它就不是一个素数,因此,用for循环来进行整除过程的简写。 代码2: ? 运行结果2: ? 程序分析2: 简单的使用printf()和scanf()函数进行简单的数据运算。 代码3: 运行结果3: 程序分析3: 使用if语句进行判断。 五.实验总结 C语言程序设计上机实验报告 实验二 一、实验名称:顺序结构程序设计 二、实验目的:正确使用常用运算符(算术运算符、赋值运算符)的用法,熟练掌握算 术运算符及其表达式,逻辑运算符和逻辑表达式。

三、实验内容: (1). 编写程序,实现小写字母转大写。 (2). 编写程序,实现输入两个不同类型数据后,经过适当的运算(加、减、乘、除)后输出。 (3). 编写程序,计算三角形面积、立方体的体积和表面积、圆的面积和周长。 (4). 编写程序,实现单字符getchar和putchar输入输出。 (5). 编写程序,实现十进制、八进制、十六进制不同数制的输出。 四、源程序代码 代码1: 运行结果1: ??程序分析1: 所有的字符都有一个对应的数字与之对应,每一个小写字母对应的数与大写字母对应的数之间总是相差32,所以只需要对所输入的字符进行减法运算就可以转换为与之对应的大写字母。 代码2: ?运行结果2: ??程序分析2: 简单的数据运算和格式字符的输出。 代码3: 运行结果3: ??? ?程序分析3: 简单的数据运算实现相应的功能。 代码4: ??运行结果4: ?程序分析4: getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1,且将用户输入的字符回显到屏幕. ?代码5: ??运行结果5: 程序分析5: 重要的是格式字符的使用,%d(输出十进制整型数)%o(以八进制格式输出整形数)%x(以十六进制格式输出整型数)%f(以带小数点的形式输出浮点数)%c(输出一个字符)%s(输出字符串,直到遇到\0) 五、实验总结 C语言程序设计上机实验报告 实验三 一、实验名称:选择结构程序设计 二、实验目的:正确使用逻辑运算符和逻辑表达式,熟练掌握if 语句和switch 语句, 学习调试程序。 三、实验内容: (1). 编写程序,用if 语句编程实现一分段函数的求解。 (2).用if 语句编写程序,求一元二次方程的根。

安徽大学计算机实验平台答案:实验1——C简单程序设计

实验一:C简单程序设计 1、略。 2、编程:要求程序运行后在屏幕上输出如下信息: My Program Is Running! Good,and I'm very glad now! #include #include main() { printf("My Program Is Running!\nGood,and I'm very glad now!"); system("pause"); } 3、编程:输入圆的半径,计算圆的面积并输出。 #include #include #define PI 3.14 main() { float r,S=0; printf("请输入圆的半径r:"); scanf("%f",&r); S=PI*r*r; printf("输出圆的面积S:%.2f\n",S); system("pause"); } 4、编程:输出一个形如“▲”的三角图形(要求:第1行输出一个“*”,第2行输出三个“*”,......,最后一行输出9个“*”)。(此题为选做)。= #include #include main() { char star='*',space=' '; int i,j,num1,num2; for(i=1;i<=5;i++) { for(j=5;j>=i;j--) { printf(" "); } for(j=1;j<=2*i-1;j++) { printf("*"); }

printf("\n"); } system("pause"); } 5、编程:输出一个形如“◣”的三角图形(要求:共5行,第1行一个“*”,第2行二个“*”,......,最后一行5个“*”)。(此题为选做)。 #include #include main() { char star='*',space=' '; int i,j,num1,num2; for(i=1;i<=5;i++) { num1=5-i; num2=i; for(j=0;j

C语言 实验五 最简单的C语言程序设计

实验5:最简单的C语言程序设计 一、实验目的: (1)掌握C语言中使用最多的一种语句——赋值语句的使用方法。(2)掌握各种类型数据的输入输出的方法,能正确使用各种格式转换符。(3)进一步掌握编写程序和调试程序的方法。 二、实验内容和步骤: 1、通过下面的程序掌握各种格式转换符的正确使用方法。 (1)输入以下程序: /* Note:Your choice is C IDE */ # include int main( ) {int a,b; float d,e; char c1,c2; double f,g; long m,n; unsigned int p,q; a=61,b=62; c1='a';c2='b'; d=3.56;e=-6.87; f=3157.890121;g=0.123456789; m=50000;n=-60000; p=32768;q=40000; printf("a=%d,b=%d\nc1=%c,c2=%c\nd=%6.2f,e=%6.2f\n",a,b,c1,c2,d,e); printf("f=%15.6f,g=%15.12f\nm=%1d\np=%u,q=%u\n",f,g,m,n,p,q); 显示结果: (2)运行此程序并分析结果。 (3)在此基础上,将程序第10~14行改为c1=a,c2=b; f=3157.890121;g=0.123456789; d=f;e=g;运行程序,分析结果。 /* Note:Your choice is C IDE */ #include"stdio.h"

int main() {int a,b; float d,e; char c1,c2; double f,g; long m,n; unsigned int p,q; a=61;b=62; c1=a;c2=b; f=3157.890121;g=0.123456789; d=f;e=g; p=a=m=50000;q=b=n=-60000; printf("a=%d,b=%d\nc1=%c,c2=%c\nd=%6.2f,e=%6.2f\n",a,b,c1,c2,d,e); printf("f=%15.6f,g=%15.12f\nm=%1d,n=%1d\np=%u,q=%u\n",f,g,m,n,p,q); } 显示结果: (4)用sizeof运算符分别检测程序中各类型的数据占多少字节。例如,int型变量a的字节数为sizeof(a)或sizeof(int),用printf函数语句输出各类型变量的长度(字节数)。 程序如下: # include int main( ) {printf("int:%d\n",sizeof(int)); return 0; } 显示结果: #include int main( ) {printf("float:%d\n",sizeof(float)); return 0; }

程序设计基础实验报告

实验一 用C 语言编写简单程序 一、实验目的 1. 熟悉VC6.0的编程环境,掌握运行C 程序的基本步骤。 2. 了解C 程序的基本框架,模仿例题编写简单的C 语言程序。 3. 正确书写算术表达式、赋值表达式和关系表达式。 4. 掌握基本输入输出函数的使用,正确调用C 语言提供的数学库函数。 5. 掌握简单的单步调试方法。 二、实验环境 Windows XP ;Visual C++ 6.0。 三、实验内容 1.在屏幕上显示一个句子“What is a computer?”。 2.求华氏温度150F 对应的摄氏温度。计算公式如下: 其中,c 表示摄氏温度;f 表示华氏温度。 3.输入x ,计算下列分段函数的值(保留2位小数),请调用sqrt 函数求平方根,调用pow 函数求幂。 4. 输入一个正整数m (0100)m ≤≤,求100i m i =∑。 四、实验要求 1. 将上机验证正确的源代码写到实验报告上。 2.根据自己的真实感受,认真填写实验分析和实验心得以及问题和建议。 3.按时提交实验报告。 553299 c f =?- ?21(1)2 x<02()0x x f x ?+++?=≥

一、实验目的 1.熟练掌握关系表达式的使用。 2.熟练掌握使用else-if 语句实现多分支结构程序设计。 二、实验环境 Windows XP ;Visual C++ 6.0。 三、实验内容 1.输入x ,计算并输出下列分段函数sign(x)的值。 2.输入月薪salary ,输出应交的个人所得税tax (保留2位小数)。按照2011年开始实行的新的个人所得税法,计算公式为:tax = rate*(salary-3500)-deduction 。 当salary≤3500时,rate=0、deduction=0 当35000y sign x -??==???

c面向对象程序设计MFC简单计算器实验报告

计算机与信息工程学院 《程序设计基础》课程设计报告 题目名称:60.编写一个能实现简单功能的计算器学生姓名:刘沛东 学生学号:54 专业班级:电子信息工程(1)班 指导教师:高攀

1 课程设计的题目 编写一个能实现简单功能的计算器 2 题目要求 1. 有一个计算器图形。 2. 能实现加、减、乘、除及乘方的运算。 3. 当输入题目时,屏幕上要在指定位置上显示出相应的题目内容,且相应的数字键要改变颜色 例如:输入数字1 时,在计算器图形上的1键变为红色。 4. 屏幕、图形颜色、形状自定 3 总体设计 总体框架 图1 系统框架

系统功能说明 在VC++中绘制计算器界面,各控件的设置 对0~9控件设定相应的ID和其他属性: 图2 “1”控件设置 对“+、-、*、\”控件设定相应的ID和其他属性: 图2 “+”控件设置 对其它控件设定相应的ID和其他属性: 图3 其它控件设置

主要使用到Layout菜单中的Align功能对各个按钮进行对其,使界面更加整洁。拖出的控件有上面的一个Edit控件用于显示数字,Button控件用于处理鼠标的消息。 4 程序详细设计 系统主调模块 图5 程序流程图

各模块详细设计 建立的变量,控件的命名,对应的消息处理函数对应表 double poz; 按钮的处理函数 void CCalcDlg::OnPt() { point_flag=TRUE; && buffer[i+1]==0)之前那位 }

m_Dis=strtod(buffer,NULL);整个大的程序的实现分8个功能,每个功能都通过一个相应的函数来实现.在调试时分别进行调试,使得调试更方便些.在编写各个函数只是按着题目要求的去完成,后来经指导老师指导后,发现了很多自己欠缺的地方,又一次将程序整体进行调试.最后把程序完善了许多。 本次MFC计算器的制作,学习到了MFC基本的编程方法。对OOP编程的理解进一步加深。但是程序仍然存在一定的问题,比如除数不能为0的Exception handle,符号键多次点击结果混乱。通过这次课程设计,以后Windows 应用程序势必会轻车熟路。 参考书目: [1]谭浩强,《C++程序设计》,北京,清华大学出版社,2006年。[2]孙鑫《深入浅出MFC》,视频教程。 [3]《Visual_C++MFC入门教程》,电子版。

实验一 简单程序设计

实验一:简单程序设计 【实验目的】 1、学会使用Visual C++6.0版本编译系统完成C++语言源程序的编译。 2、掌握C++程序的基本格式与规范,学会编写简单的C++程序。 3、理解C++程序结构的特点。 4、熟悉C++程序基本的输入输出操作。 【实验内容】 题目一: 运行下列正确程序,练习一个文件程序编译、连接、运行的方法。 //This is a C++ program #include void main() { double x,y; cout<<”Enter two float numbers:”; cin>>x>>y; double z=x+y; cout<<”x+y=”<>x; int y,k=x+y; cout<< “x+y=”< int main() { int x,y; cin>>x>>y; int k; k=x+y; cout<<"x+y="<

题目三: 编程实现输入千米数,输出显示其英里数。已知:1英里=1.60934千米(用符号常量)。 测试数据:输入1,输出:0.621373 #include void main() { const double YL(1.60934); double x; cin>>x; double y=x/YL; cout<<"y="< void main() { int m,n; cin>>m>>n; int p; p=100*m+n; cout<<"p="< void main() { double x,y; cin>>x; y=-4*x*x*x+5.8*x*x-2*x+2.6; cout<<"y="<

可视化程序设计实验报告

XI`AN TECHNOLOGICAL UNIVERSITY 实验报告 成绩:

西安工业大学实验报告 一、实验目的 (1)熟悉Visual Basic 6.0集成开发环境。 (2)学习怎样启动和退出VB (3)掌握开发一个简单程序的基本步骤。 (4)掌握简单代码的编写。 二、实验内容 (1)启动和退出VB (2)开发一个简单程序。 (3)将Visual Basic工程编译生成可执行文件。 三、实验步骤、数据记录及处理 1、启动和退出VB <1>进入VB集成开发环境 要进入VB应用程序,首先要运行VB的集成开发环境。启动VB的方法如下: [方法1] ◇单击任务栏上的“开始”按钮; ◇选择“程序”文件夹,接着选取“Microsoft Visual Basic 6.0中文版”文件夹,再选取“Microsoft Visual Basic 6.0中文版”项,如图1-1所示; 图1-1 启动VB的方法1 ◇单击鼠标左键。 [方法2] ◇单击任务栏上的“开始”按钮; ◇选择“程序”文件夹; ◇使用“Windows 资源按理器”查找VB可执行文件 VB6.exe;

◇双击图标。 [方法3] ◇在桌面创建一个VB快捷键; ◇双击该快捷键。 VB启动后,出现VB6.0的“新建工程”对话框(图1-2)单击“打开”按钮,带有一个窗体的新工程将被创建,并可以看到VB集成开发环境的界面,如图1-3所示。有的系统启动后可直接进入图1-3所示的界面。 图1-2 VB6.0的“新建工程”对话框 图1-3 VB的集成开发环境窗口 <2>退出VB方法: (1)单击主窗口右上角的“关闭”按钮。 (2)执行“文件”菜单中的“退出”命令。 (3)按Alt+Q键。

实验七 Hello World 简单应用程序设计

实验七交叉编译及Linux简单程序设计 一、实验目的 1. 熟悉CVT-A8 教学系统中的Linux 开发环境; 2. 掌握简单的Linux 应用程序helloworld 的编译; 3. 掌握CVT-A8 教学系统中Linux 应用程序的调试。 二、实验环境 预装Fedora10的pc机一台,CVT-A8系列实验箱,以太网线一根,串口线一根。 三、实验步骤 1. 建立工作目录 注:本实验以及后续的所有实验中用“$”符号表示在主机的Linux 控制台上输入的命令行。用“#”符号表示在目标机的Linux 控制台上输入的命令行。 $cd /opt/cvtech/examples $cd helloworld 2. 编写程序源代码 在Linux 下的文本编辑器有许多,常用的是vim, Xwindow 界面下的gedit 等。实际的源代码较简单,如下: #include int main(){ printf("Hello, World!\n");

} 3. 在主机端编译并运行helloworld 程序 $gcc -o helloworld helloworld.c $./helloworld 使用file命令查看编译后的可执行文件信息,正确的结果将在主机的显示器上打印如下字符串: Hello, World! 4.编译在目标机运行的helloworld 程序 $arm-linux-gcc -o helloworld helloworld.c 由于编译器采用的是arm-linux-gcc 编译器,因此使用上述命令编译出来的程序只能在ARM 处理器上运行,不能在x86 平台下运行,如果在Fedora10 中运行该程序将出现如下错误结果。使用file命令查看交叉编译后的可执行文件信息。 $./helloworld bash: ./helloworld: cannot execute binary file 5. 下载helloworld 程序到CVT-A8 中调试 CVT-A8 通过将主机的/tftpboot/目录挂接到目标机的/mnt/ 目录中,因此,需要将第四步编译的程序helloworld 拷贝到主机的/tftpboot/目录或其子目录下。 $cp helloworld /tftpboot/ 在PC 电脑的超级终端输入如下命令将主机端/tftpboot/目录

上机实验三 简单c程序设计

上机实验三简单程序设计 一.目的要求 1.巩固scanf()函数、printf()函数的用法,包括整型int、单精度浮点型float、双 精度浮点型double的输入输出格式。 2.顺序和选择程序基本算法。 3.请每组在晚8:10之前将填写完毕的本文档发送到xsfang@https://www.360docs.net/doc/6411645900.html,,完成与否皆可。 请同组之间对内亲密合作,互相抄袭,又快又好的完成编程和word 文档的编写任务。 每组请只交一份word 文档,不要包含其他文件。 4.请同组同学尽量挨近就坐。请保持安静,不要大声交谈,请不要打游戏。十分答卷5 分1组,4.5分2组,4分3组,3.5分4组。未交卷或违规者给3分。 二.实验内容 1.请将下列源程序填写完整。 12 34 5.6789 -7.8237 12.4592 2.191768 请截图贴出运结果(包含签名): 如将标有/* line 9 */、/* line 10 */、/* line 11 */的三个语句分别改写为:printf("a=%5d, b=%5d\n", a,b); printf("x=%.2f, y=%.2f\n",x,y); printf("r=%8.f, s=%8.f\n", r,s); 重新编译、连接并运行,输入内容同上,则输出结果显示为(包含签名): 思考题——位于%与字母d或f之间加入的数字起什么作用?其中, %5d中的5: %.2f中的2: %8.f中的8:

请写出解决以上任务的源程序:并贴图给出运行结果(含签名): 请写出解决以上任务的源程序:并贴图给出运行结果(含签名): 请写出解决以上任务的源程序:并贴图给出运行结果(含签名):

C语言程序设计实验报告 实验一 简单程序设计

C语言程序设计实验报告 实验一简单程序设计 班级 2017231 学号 201723128 姓名郭鹏博 一、实验目的 1、熟悉VC6.0编程环境。 2、掌握使用VC6.0输入源程序、编译连接和运行程序的基本过程。 3、练习C调试程序的方法。 4、熟练使用赋值运算、算术运算等表达式。 5、熟练使用标准输入、输出函数及各种数据类型的格式说明符。 二、实验内容及步骤 1、分析程序,根据要求写出结论: 课本P83页第4题、第5题; 第4题 (1)c1=a,c2=b c1=97,c2=98 原因:字符变量实质上是一个字节的整形变量,由于它常用来存储字符,所以称为字符变量。可以把0到127之间的整数赋给一个字符变量。在输出字符变量的值时,可以选择以十进制整数形式或字符形式输出。 (2)c1=?c2=? c1=-59,c2=-58 原因:Visual C++系是把char默认为signed char类型的,而signed char类型的变量允许存储的值为—128~127,但字符的代码不可能为负值,所以在存储字符时实际上只用到0~127这一部分,其第1位都是0。给字符型变量赋值197和198时,所赋之值超出了字符变量的取值范围,导致无论是以以十进制整数形式输出还是以字符形式输出,输出结果均错误。 (3)c1=a,c2=b c1=97,c2=98 原因同(1) 第5题 2、程序改错 下面程序的功能是计算球体的表面积和球体的体积,程序中有多处错误,并且指定必须按下面的形式输入、输出数据。请对程序做相应的修改。 #include main { double r,s,v; int pi=3.14;

c程序的设计实验报告

C++程序设计 实 验 报 告 姓名: 班级: 学号: 指导教师:

实验一 VC6.0环境入门与简单程序设计 一、实验目的: 1、熟悉VC++6.0开了环境并编写简单的C++程序。 3、使用C++语言编写简单的输入输出程序。 4、使用VC++6.0的DEBUG调试功能:单步执行、设置断点、观察变量值。 二、实验内容: 1、使用VC++建立一个标准C++程序,编译、运行如下程序: #include Int main() { Cout<<”Hello World!\n”; Cout<<”Welcome to C++!\n”; } 2、编程计算图形的面积。程序可计算圆形、长方形、正方形等的面积,运行时首先提示用户选择图形类型,然后根据不同图形类型,输入相关参数计算其面积,并将其显示出来。 #include using namespace std; void circle_area(); void rectangle_area(); void square_area(); int main() { int i; while(1)

{ cout<<"请输入图形类型(1:圆形;2:长方形;3:正方形;4:退出。):"; cin>>i; switch(i) { case 1:circle_area();break; case 2:rectangle_area();break; case 3:square_area();break; case 4:return 0; default:cout<<"输入错误!\n"; } } } void circle_area() { float r,s; cout<<"输入圆的半径:"; cin>>r; s=3.14*r*r; cout<<"该圆的面积是:"<>a>>b; s=a*b; cout<<"该长方形的面积是:"<>a; s=a*a; cout<<"该正方形的面积是:"<

实验1 C程序的运行环境和最简单的C程序设计

实验1 C程序的运行环境和最简单的C程序设计 1.实验目的和要求 (1)熟悉C语言程序开发环境(Visual C++),了解开发环境中的编辑、编译、链接和运行命令。 (2)掌握在C语言开发环境中如何编辑、编译、链接和运行一个标准C语言程序。(3)通过运行简单的程序,熟悉C语言的基本格式规范,并初步了解它的结构特点。(4)了解下列命令及函数:include、main、printf、scanf。 (5)掌握C语言数据类型的概念,熟悉如何定义一个整型、字符型、实型变量,以及如何对它们进行赋值。 (6)掌握整型、字符型、实型变量数据输出时所用的格式转换符。 (7)学会使用C的有关算术运算符,正确使用由这些运算符构成的表达式。 (8)进一步熟悉如何编辑、编译、链接和运行一个C程序。 2.实验内容:实验指导书中的实验一和实验二 3.实验步骤 3.1启动VC; 3.2请附图说明在VC环境下开发一个C程序的完整步骤

3.3编译、链接、运行程序 输入源程序后,如何编译该程序? 单击“Compile”按钮,根据编译窗口的错误提示进行修改,再进行编译,直至无错误为止。 如何链接程序生成可执行文件? 单击“Build”,根据窗口的错误提示进行修改,再进行编译,直至编译链接无错为止。 如何运行程序查看结果?单击“BuildExecute” 3.4实验一实验内容部分 1_1(1_1表示实验一的实验内容部分的第1题对应的程序,下同): 1_2请在下面插入该程序运行结果画面:

1_3_1(表示实验一实验内容部分第3题的第1个程序,下同)请在下面分别插入改正错误后程序运行成功时的画面: 1_3_2程序运行画面:

实验一 简单程序设计实验

实验一简单程序设计实验 一、实验目的 掌握单片机程序设计的基本方法,熟悉程序交叉编译和调试执行的过程。 二、实验器材 1、PC机(安装Keil软件)1台 2、实验开发板 1块 3、U-EC5型仿真器1只 4、直流稳压电源 1台 三、实验要求 1、课前温习和回顾C语言程序设计方面的知识。 2、搭建集成开发环境 (参考所给实验附件中《Keil软件与仿真驱动程序安装.doc》)。 3、编写简单的C程序,熟悉Keil环境下的调试方法,包括跟踪、单步运行和断点设置等。 4、仔细观察实验板电路图,并结合《C8051F411数据手册》给出自己I/O 端口配置方案。 5、编写程序,实现LED的亮灭、蜂鸣器的鸣叫以及按键状态识别等控制功能。 四、扩充实验(思考题) 1、如何实现蜂鸣器的间歇鸣叫(1秒)? 2、如何实现依次点亮LED的控制功能? 五.实验程序 1.实现LED的亮灭 #include "c8051F410.h" #define OLD_BOARD #ifdef OLD_BOARD sbit LED8=P1^7; sbit LED7=P1^6; sbit LED6=P1^5; sbit LED5=P1^4; sbit LED4=P1^3; sbit LED3=P1^2; sbit LED2=P1^1; sbit LED1=P1^0;

sbit KEY5=P2^2; sbit KEY4=P2^1; sbit KEY3=P2^0; sbit KEY2=P0^2; sbit KEY1=P0^1; sbit SPKOUT=P0^0; #else sbit LED8=P2^1; sbit LED7=P2^0; sbit LED6=P0^7; sbit LED5=P0^6; sbit LED4=P0^3; sbit LED3=P0^2; sbit LED2=P0^1; sbit LED1=P0^0; sbit KEY5=P1^7; sbit KEY4=P1^6; sbit KEY3=P1^5; sbit KEY2=P1^4; sbit KEY1=P1^3; sbit SPKOUT=P2^2; #endif void init(void); //初始化子程序 void main() { init(); //初始化子程序 LED1 = 1; LED2 = 0; LED3 = 0; LED4 = 0; LED5 = 0; LED6 = 0; LED7 = 0; LED8 = 1; SPKOUT = 0; for(;;) { } } voidinit(void) { PCA0MD=0x00;//关闭WDT控制 OSCICN=0xc7;//设置内部振荡器工作(使用内部晶振24.5MHz)

相关文档
最新文档