




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第一章:生成一个Process(进程)进程是什么?简单地说,进程就是在执行状态下的一个程序(包括CPU状态,所占内存的状态,等等)A进程生成了B进程,也就是说,A程序在执行的时候,又生成了另一个进程B。这个时候,我们可以把A进程叫做父进程,把B进程叫做子进程。例程序://Usage:./a.out20#includeintmain(intargc,char*argv[]){intdep_time;dep_time=atoi(argv[1])*60;/将/参数中给出的20(分钟)转换成整型的秒数if(fork()==0)//生成子进程,然后父进程中止{sleep(dep_time);fprintf(stderr,!!!!!!!!\n);}return0;}上面的程序是一个闹钟程序。当你执行之后。程序不会显示什么,而是一下就回到UNIX的提示符下。但是你在命令行中指定了20分钟后你有事,那么在你执行这个程序之后20分钟,他会提醒你到时间了。本程序只是做示例用,没有检查参数是否正确,等等。生成一个新的进程,可以使用fork()函数。以下说说fork()函数。头文件:#include形式pid_tfork();参数无返回值成功时:父进程中:子进程的PID(ProcessID)子进程中:0失败时:父进程中:-1由于失败,没有生成子进程;fork()刚执行完的时候,子进程和父进程是完全一模一样的两份进程(当然,PID是不一样的)。他们的各个变量的值都是一样的,而且都认为自己已经执行完fork()了。fork()后,区分父进程和子进程,只要看fork()的返回值就行了。if(fork()==0)printf这是子进程);elseprintf(这是父进程);同理:if(fork()==0){//接下来要子进程做的工作}else{//接下来要父进程做的工作}一般,我们会把fork()返回给父进程的值保存下来(其实就是子进程的PID),等到需要结束子进程的时候,我们关掉他,如下:pid_tchild_pid;child_pid=fork();if(child_pid==0){}else{}// 需要结束子进程的时候kill(child_pid,SIGKILL)//kil1(函数是用来发给另一个进程一个消息的。以后再讲。先写这些,试试手。喜欢就顶。要是没人爱看我就不写了。呵呵。省得大家说我乱贴垃圾。以后计划贴的东西:在程序中执行UNIX命令或者另一个程序取得环境变量并利用UNIX文件系统(在程序中取得分区信息,等等)使用管道操作达到在各进程互相交流数据信号(signal)进程间共享内存用message实现进程间共享信息第二章:在程序中执行UNIX命令或者其它程序在UNIX下,像DOS的/r/那样的程序,我们称之为外壳(shell)。外壳就是一个命令解释器,你在外壳的提示符下输入命令(如同DOS的提示符一样),系统便会执行。DOS的提示符一般是C:\>,当然,你想改成什么样就能改成什么样,又当然,像BBS一样贴张图上去是不太现实的。UNIX的提示符根据外壳的不同是不同的。为了更好地说明本章想讲解的内容,我们先做一个外壳试试(玩具级别的)。我们给他起名叫SSH(SohuShell)吧。想取名叫CSH,可惜CSH在没生我之前就有了。呵呵。/*简单的外壳程序*/#includeintmain(){staticcharprompt[64]=>;charcommand[256];intst;fprintf(stderr,%s,prompt);//在屏幕上输出提示符while(gets(command)!=NULL)//取得命令{if(fork()==0)//生成子进程{//这里是子进程接下来要做的事if(execl(command,command,(char*)0)==(-1))//上一句是执行命令exit(1);//当出错时子进程异常中止}else{//父进程wait(&st);//等待子进程结束fprintf(stderr,%s,prompt);//输出提示符,等待命令}}return0;}执行方法:%./ssh>/bin/ls当前目录下文件名一览>Ctrl+D%普通的外壳在执行exit命令后会关闭。也就是说,退出一层外壳。咱们这个程序现在还做不到。愿意的话加上这个功能试试好了。所以要关闭这个外壳就得来点狠的oCtrl+D,Ctrl+C什么的。再不你就再开一个外壳然后ps-ef再kilO再狠一些……拆硬盘,拨电源我们这里有了一个新的函数:execl()o其实他是一组函数中的一个。这组函数如下:intexecl(path,arg0,arg1,...,argn,(char*)0);intexecv(path,argv);intexecle(path,arg0,arg1,...,argn,(char*)0,envp);intexecve(path,argv,envp);intexeclp(file,arg0,arg1,...,argn,(char*)0);intexecvp(file,argv);其中的参数定义如下:char*path;char*file;char*arg0,*arg1,...,*argn;char*argv[];char*envp[];返回值:成功时:所执行的命令将会覆盖现有的进程,所以无返回值失败时:-1用过TC的朋友应该知道,TC的Process.h里有一个system。函数。这组函数其实和system。的功能差不多。比方说:execl(/bin/ls,/bin/ls,-al,/home,(char*)0);或者char*argv[];strcpy(argv[0],/bin/ls);strcpy(argv[1],-al);strcop(argv[2],/home);execv(/bin/ls,argv);都相当于在命令行下敲入了/bin/ls-al/home并且回车。(引号不是命令里的。是我解释时加上去的。别看混了)。execle()和execve(),函数名最后一个字母都是e。就是说,这两个函数在调用其它程序的同时,还可以把环境变量一起传给被调程序execlp()和execvp(),函数名最后一个字母都是p,就是说,这两个函数在使用的时候,就算你不指定命令文件所在的路径,它也会根据环境变量PATH去挨个地方找。找着就执行。找不着拉倒。比方说:setenv$path=(/bin$path)这句话将环境变量PATH的第一个路径设为/bin。这是在SHELL下执行的。C里没这东西吧。在程序中这样用这个函数execlp(ls,ls,-al,/home,(char*)0);与上面的效果一样。当然。如果你PATH变量没设好的话。它就不一定找到哪儿去了。还有一个函数是wait(),说明如下:#includepid_twait(int*stat_loc);返回值就是一个PID了。忘了PID是什么意思的朋友光顾一下我上一篇贴子。它的参数有些意思。其实它与你的子进程用什么方式结束有关系。当你的子进程以exit()方式结束的话,stat_loc所指向的地址的前8位将会是exit()的参数的后8位,而stat_loc所指向的地址的后8位是0。比方说:你的子进程是exit(l)那stat_loc所指向的地址的内容应该是0000000100000000exit():#includevoidexit(intstatus);就算你在程序中没写exit()函数,编译器也是认为你是在最后加入了这个函数。下一篇贴子,咱们再把这个Shell完善一下第三章:增强ssh的功能(使用环境变量)还记得上次做的那个ssh吧?这回咱们把它再改一改。大家知道。C语言的main函数是这样的:intmain(intargc,char*argv[],char*envp);前两个不用说了吧。其实知道前两个的话应该也知道第三个:取得系统环境变量。UNIX和DOS—样。有着各种各样的环境变量(明确地说,应该是比DOS用得更广泛)。比方说常用的$PATH,$HOME,$USER等等。如果用的是csh,可以改〜/.cshrc或者干脆直接在命令行下设就行了。这些是UNIX的东西,不属于我们在C/C++讨论的东西了。有兴趣的朋友可以到UNIX版去看一看。下面是一个取得系统环境变量并输出的小程序/*getenv.c取得系统环境变量并输出*/#includeintmain(intargc,char*argv[],char*envp[]){inti;for(i=0;envp[i]!=NULL;i++){printf(%s\n,envp[i]);}return0;}编译执行后应该是这样:%./getenvDISPLAY=:0.0HOME=/home/syuuiUSER=syuui以及你的系统中其它的环境变量。想一想它是怎么运行的:当你在命令行下敲入getenv并回车,shell就fork出一个新的进程,然后让这个进程去执行getenv,并把现在系统中的各个环境变量存入到envp里去。这个时候,原来shell的进程就是getenv进程的父进程。envp的参数是从父进程中取得的。现在你知道上一节中为什么有那两个带p的函数了?上一回做的ssh这个外壳的命令是带不了参数的。因为咱们的程序不知道要去读参数。这回不妨做一个能读参数的试试#include#defineSP0#defineNOSP1voidgetarg(char*argv[],char*p);/取/得各个参数intmain(){staticcharprompt[64]=>;charcommand[256],*argv[256],*p;intst;fprintf(stderr,%s,prompt);while((p=gets(command))!=NULL){getarg(argv,p);if(fork()==0){if(execv(argv[0],argv)==(-1))exit(1);}else{wait(&st);fprintf(stderr,%s,prompt);}}return0;}voidgetarg(char*argv[],char*p){inti,sp_flag;sp_flag=SP;〃SP代表空格,NOSP代表非空格的意思for(i=0;*p!='\0';p++){if(sp_flag==SP&&*p!='')//如果现在状态是读过空格,但现在这个字母是非空格//那很显然,读到一个新的参数{argv[i]=p;i++;sp_flag=NOSP;}if(*p==''){*p='\0';sp_flag=SP;}}argv[i]=(char*)0;}这篇文章东西说得比较少。给大家出个问题吧:看到了吧。C能做的事情很多。谁说C不支持多进程?那是因为DOS。呵呵上回做的ssh,必须输入绝对路径才能执行。现在要求,咱们做一个只输入命令以及参数,程序自动4$path里定义的各路径查找这个命令。找到就执行,找不到就报错的外壳出来试试?有做出来的,请贴程序。2003-7-27关于进程状态的一些补足:众所周知,UNIX是一个多用户多任务的操作系统。所谓多任务,就是指在同一个时间内,看上去有许多任务在同时执行。这一点是与DOS不同的。所以TurboC下找不到fork()函数。当然。大家知道,实际上CPU同一个时间只能处理一件事。它是在轮流执行这些进程,由于速度很快。所以看起来像是所有进程在一起跑。同样,一个进程,它有使用CPU的时候,也有等待使用CPU的时候。这就决定了进程的几种状态。进程大致可以分为“执行中”,“准备执行”,“睡眠中”三种状态。执行中:进程在占用CPU。准备执行:想要使用CPU,但是CPU被别的进程占用中,所以等待CPU空闲。睡眠中:等待事件发生的进程。比方说,等待输入输出结束的进程。我们用fork()生成一个新的进程,或者用exec函数组覆盖当前进程后。当这个子进程结束的时候要给父进程送去一个信号(ignal,后述),然后转为zombie状态。zombie状态是指一个进程已经结束,所占内存空间等等已经返还给系统,但是在系统的进程列表中仍然存在的这么一个状态。当父进程执行wait()后,子进程才真正完全结束。如果父进程先一步结束了,那么由init代替父进程。所以,wait()不只是等待子进程结束。它还有上面所说的这个任务。我们写个程序来看一下:/*zombie*/#includeintmain(){intst;if(fork()==0){exit(1);//子进程生成后直接结束}else{sleep(300);//休眠300秒wait(&st);printf(Returncode=%d\n,i);}return0;}编译后执行%./zombie&这个时候,程序在后台运行%ps-ef|grepzombie看一下,是不是有一个进程处在zombie状态?第四章:文件系统UNIX所管理的机器一般是大型机而不是PC。所管理的硬盘一般也非常大。所以一般分成几个区,每个区都有其单独的文件系统。比方说你能大概能找到这样的一些文件/dev/sd/c0t0d0s0/dev/sd/c0t0d0s1当UNIX启动的时候,分区被挂報mount并统一成一个树状的文件系统分区的物理构造咱们暂且放在一边,先写个程序,读一下分区信息试试。/*ndf.c计算参数所指定的分区的剩余空间比率*/#include#include#includeintmain(intargc,char*argv[]){structstatvfsbuf[1];sync();if(statvfs(argv[1],buf)!=0){fprintf(stderr,Cannotreadsuperblock!\n);exit(1);}fprintf(stderr,%4.1f%%free\n,(float)buf[0].f_bfree/buf[0].f_blocks*100);return0;}编译执行:%./ndf/49.8%free这里用了一个statvfs函数。其具体如下:#include#includeintstatvfs(char*path,structstatvfs*buf);返回值:成功时:0失败时:-1还有一个sync()函数。用来更新分区的superblock;voidsync();UNIX系统为了加快处理速度,将分区的superblock信息读到内存中保存。sync()函数就是把在内存中保存的superblock信息再写回到硬盘上去。UNIX系统使用好几种文件系统。有S5,ufs,VxFS等等。虽然这些文件系统的构造非常不同,但一通百通,咱们在这几篇贴子里只讨论一下比较容易理解,而且经典的”S5文件系统。(别的我也不会。呵呵)S5:文件名最长14字节,构造简单ufs:文件名最长255字节,BSD所用的文件系统。VxFS:VeritasSoftwave公司开发的文件系统。出现错误时可以快速恢复。由于这种文件系统保证文件在硬盘上连续存放,所以处理速度很快。现在说一下S5分区的构造一个分区包含如下四部分(按顺序):[bootblock][superblock][inodeblock][datablock]bootblock:这个部分在分区的最开始处,用来存放引导程序。就算是不能引导的分区一样有bootblock,这个时候这部分就没有用了。不过一般这部分也不大。大多数只有512或者1024字节。superblock:superblock在bootblock之后,用来存放这个分区全体的管理信息。上面那个ndf.c就是读的这部分所存储的信息。里边存放了inodeblock的大小,freeblock数组等等。根据这些信息可以得知datablock的开始位置。inodeblock:inode是indexnode的缩写。inodeblock就是存放inode的部分UNIX把一切都看成是个文件。包括目录以及设备等等的所有的文件都有一个lnode号,作为这个文件的管理信息。文件本身存在于数据区,但是node号存在inodeblock里。主要包含文件的模式,链接数,文件所有者,文件大小,在硬盘上的位置,最后读写时间,最后更新时间等信息。为了加快存储速度,系统会把一定数量的inode存至内存。UNIX系统不一样,存多少也就不一样。datablock:这部分就是存放数据本身的了。这部分被分成一定大小的块,如同DOS的扇区一样。一般大小是1024字节,分到4096的也有。解说到这里,我们再来写个程序。打开一个目录,然后把这个目录下所有的文件的inode号及文件名输出来。/*nls.c*/#include#defineDIRSIZ14intmain(intargc,char*argv[]){structdir{inti_no;charf_name;};structdirdir_data[1];FILE*fp;fp=fopen(argv[1],r);while(fscanf(fp,%i%s,&(dir_data[0].i_no),dir_data[0].f_name)!=EOF){printf(%i%s\n,dir_data[0].i_no,dir_data[0].f_name);}fclose(fp);return0;}%./nls/2048usr2049home别忘了,在UNIX下,目录也当成文件。最近,为了使目录的格式变得通用而不再依赖于操作系统,程序中大多使用统一的格式这种情况下,我们最好就不直接用fopen()打开目录,而使用opendir()readdir()等函数比较好。重写一下上面的程序。/*nls2.c*/#include#includeintmain(intargc,char*argv[]){DIR*fp;structdirent*p;fp=opendir(argv[1]);while((p=readdir(fp))!=NULL){printf(%i%s\n,p->d_ino,p->d_name);}closedir(fp);return0;}执行结果和上面一样。函数说明如下:#includeDIR*opendir(char*dirname);打开一个目录,成功时返回DIR结构体的地址,失败返回NULLstructdirent*readdir(DIR*dirp);取得打开目录中下一个文件的信息,成功时返回下一个文件信息的地址,失败时,或者读至末尾时返回NULLintcloseidr(DIR*dirp);关闭目录,成功时返回0,失败时返回-1注意:readdir在成功地读出一项之后,会自动指向下一项。假设你有如下程序:structdirent*p;p=readdir(fp);p++;//千万不要像这行这样写。你无法保证这目录里的文件信息是连续存放的。你只要一遍一遍地用readirQ这个函数就行了。它帮你全搞定第五章:目录及文件操作上一章我们说了一些UNIX的文件系统的物理构造。下面我们来看看具体怎么对文件进行操作。当然这里所说的文件及目录操作不是fopen()。呵呵。我们要做一些fopen办不到的事。/*newer.c比较参数所指定的两个文件将其中新的文件的文件名输出来*/#include#include#includeintmain(intargc,char*argvp]){structstatbuf[2],*p;if(argc!=3){fprintf(stderr,Usage:%sfile1file2\n,argv[0]);exit(1);}p=buf;if(stat(argv[1],p)!=0)//取得第一个文件的信息{fprintf(stderr,%snotfound!\n,argv[1]);exit(1);}p++;if(stat(argv[2],p)!=0)//取得第二个文件的信息{fprintf(stderr,%snotfound!\n,argv[2]);exit(1);}if(buf[0].st_mtime>buf[1].st_mtime)/比/较最终更新时间printf(%s\n,argv[1]);elseprintf(%s\n,argv[2]);return0;}执行结果:%newerafilebfilebfile使用stat()函数,可以得到文件的信息。这些信息是在inode中保存的这个文件信息的一部分。得到的信息将会保存在stat.h中定义的stat型的结构体中。stat()函数解释如下:#include#includeintstat(char*path,structstat*buf);返回值:成功时:0失败时:-1我们再来写一个玩玩#include#include#include#defineMASK0555//这个数字的意思等一下解释,他代表“可读”和“可执行”intmain(intargc,char*argv[]){structstatbuf[1];mode_tmode;if(argc!=2){fprintf(stderr,Usage:%sfile\n,argv[0]);exit(1);}if(stat(argv[1],buf)!=0){fprintf(stderr,Cannotreadi-node\n);exit(1);}mode=(buf[0].st_mode&MASK);/计/算文件新的权限标志if(chmod(argv[1],mode)!=0)//改变文件的权限标志{fprintf(stderr,Cannotchangemode\n);exit(1);}return0;}现在来解释一下0555这个数字的意思。众所周知,UNIX是一个多用户多任务的操作系统。每个用户有自己的权限,这个权限限制了用户可以做哪些事,不可以做哪些事。对于文件来说,用户可以分成四类:root(根用户,超级用户)这个用户是系统的管理呐,权限最大,可以随意读写任何文件。owner(文件拥有者)实际拥有文件的用户。文件属主。group(组成员)用户所在的用户组的成员other以上三类之外的其它用户UNIX中,每个文件信息中包括一组9位的权限标志。分别给文件属主,用户组和其他用户指定对该文件的读、写和执行权。请看下面的例子:%ls-l/bin/ls-rwxr-xr-x1rootbin27281Aug152002/bin/ls*重要是看-rwxr-xr-x,第一个-表示这是一个普通文件,这个位置也可以出现些别的字符,比方说目录的话这里会是d。而1表示一个链接。余下的9位可以分成三段,每段三位。本例中从左至右rwx表示文件的属主拥有文件的读,写,执行权r-x表示同组的用户拥有文件的读,执行权(注意,“写”权限的位置是一个-)r-x表示其它的用户拥有文件的读,执行权文件的访问权限还可以用三位8进制来表示。如上例rwxr-xr-x可以换成111101101(有该权限,则该位为1,否则为0)换成8进制,二进制的111是八进制的7,二进制的101是八进制的5。现在看看0555是什么意思?就是说,可以读,可以写的意思。把0555和原来文件的权限标志做与运算,得到的新的权限标志就是把原来的文件权限标志中所有的写权限全取消了。其余权限变。然后在程序中用chmod()把这个新的权限标志赋给文件即可。chomd()函数用法如下:#include#includeintchmod(char*path,mode_tmode);返回值:成功时:0失败时:-1关于目录,还有另一个比较有用的函数,即chdir()。用这个函数可以在程序中转换当前目录。#include#includeintchdir(char*path);返回值:成功时:0失败时:-1以上两章,简单地叙述了一下UNIX的文件系统以及在UNIXC中对文件的操作方法。并列举了常用的一些函数。下一章,我们将简单地叙述一下UNIXC的输入输出,以及用管道pipe)实现两个进程互换数据。第六章:标准输入输出以及管道操作标准输入输出大概所有的操作系统都差不多吧。从键盘输入。从屏幕输出。除非你用的还是打纸带的老家伙。呵呵。主要说一下管道操作。注意:此处所说的管道操作不是%cat-ntest.c|more这是在提示符状态下使用管道,把第一个程序(cat)的输出作为输入传给第二个程序(more)。我们现在要在程序中使用的管道原理与此相同。将某一个程序的输出变为另一个程序的输入。做一个石头剪子布的程序。其中包括一个父程序和一个子程序。注意是两个程序,不是两个函数。也不是两个进程。不过因为父程序运行的时候要通过exec()函数来执行子程序。所以我们也可以把它看成是两个进程。父进程取得用户的输入(石头S,剪子C,布P中的某一个,P=0,S=l,C=2)并通过管道传给子进程子进程取得父进程传来的数字,加上自己的PID后做为种子数,生成一个随机数然后被3除,得出来一个余数是0、1或者2。再通过管道传回给父进程。父进程根据两个数字(用户输入的,以及子进程传回来的)判定胜负,输出/*parent.*/#include#includeintmain(){inti,j,st,fd[2];pid_tpid;staticintresult[3][3]={1,0,2,2,1,0,0,1,2};charargv1[3],argv2[3],ch;ch=getchar();switch(ch){case'P':i=0;break;case'S':i=1;break;case'C':i=2;break;default:fprintf(stderr,EnterP,SorC,Please!\n);exit(1);}if(pipe(fd)!=0)//建立管道{fprintf(stderr,PIPEError!\n);exit(1);}sprintf(argv1,%d,fd[0]);sprintf(argv1,%d,fd[1]);switch(pid=fork())//fork出一个新的进程,执行子程序{case0:if(execl(child,child,argv1,argv2,(char*)0)==(-1))exit(1);//执行了子程序break;case-1:fprintf(stderr,forkError!\n);exit(1)}write(fd[1],&i,sizeof(i));//向管道写数据wait(&st);//等待子程序结束read(fd[0],&j,sizeof(j));//从管道读数据switch(result[i][j]){case0:printf(Youwon\n);break;case1:printf(Same\n);break;case2:printf(Youlost\n);}close(fd[0]);close(fd[1]);return0;}/*child.c*/#includeintmain(intargc,char*argv[]){inti,j,read_fd,write_fd;read_fd=atoi(argv[1]);//设定输入用管道write_fd=atoi(argv[2]);//设定输出用管道read(read_fd,&i,sizeof(i));/从/管道中取得数据srand(i+getpid());//设定随机数的种子数j=rand()%3;//生成随机数write(write_fd,&j,sizeof(j));/写/向管道close(read_fd);close(write_fd);return0;}编译执行父程序即可。这种管道是用来在父子进程间传递数据的。如同大家在程序中所见:父进程开辟管道然后生成子进程执行子程序,并将管道参数作为main()函数的参数传给子程序。通过一个相同的管道实现读写。开辟管道时,我们用到了这个函数:intpipe(intfd[2]);开辟一个管道参数fd⑵是一个有两个元素的数组。可以看成是两个管道的记述子。fd[O]用来读,fd[1]用来写。返回值:成功时:0失败时:-1读取/写入管道时,我们用到了下面函数读取管道中的数据intread(intfd,char*buf,unsignedintnbyte);向管道中写入数据intwrite(intfd,char*buf,unsignedintnbyte);其中,fd是管道记述子,也就是我们前面说的fd[0]或者fd[l],buf装数据,nbyte指定读/写数据的数量,单位是字节。成功时返回0,失败时返回-1。由于准备考研。这篇文章耽误了一些时日。最近还有些事,也许下一篇也得几天后才能再贴。另外,在此向诸位致歉。我的程序是在学校的UNIX下写的,一般是用软盘带回来,写上一篇贴子程序的时候没有带软盘,只好打印出来回来再敲。在输入的时候有一个错误(现已改正)intmain(intargc,char*argvp]);应为intmain(intargc,char*argv[]);下一章准备说说UNIX的进程(Process)和信号(signal、另外,感谢版主将我前几篇贴子选进了精华区。愚作不堪,如有错误及不到之处请诸位高人指正为盼。对本贴内容如有不明,请给我留言。我会尽快回答(如果我明白的话)。谢谢远方的洁白的哈达,雾中雪,初学无罪,hellosamxiaoyu等朋友。有你们的支持,我才有信心继续写下去。愿拙文能对诸位学习C语言有所帮助。下章见。第七章:UNIX进程(Process)及信号(signal)在进程执行过程中,如果出现什么事件(event),系统将会给进程一个信号。进程可以在得到这个信号后做些适当的处理。还是与以前一样,咱们先来个小程序吧。/*slot.c*/#include#includeintx[3];longkin;intkekka();intmain(intargc,char*argv[]){srand(getpid());/*设定随机数的种子数*/signal(SIGKILL,kekka);/*当Ctrl+C按下时,执行kekka函数*/kin=atoi(argv[l]);/*取得赌注数目*/printf(pressCtrl+Ctostopmaching!\n);while(l){x[0]=rand()%10;x[1]=rand()%10;x[2]=rand()%10;printf(%d%d%d\n,x[0],x[1],x[2]);fflush(stdout);}return0;}intkekka(){if(x[0]==x[1]&&x[1]==x[2])printf(Great,youwon%d\n,kin*3000);elseprintf(Youlost%d\n,kin);exit(0);/*游戏结束。退出程序*/}执行方法:%./slot100执行后。画面上会出现一排排的数字。当你按下Ctrl+C的时候,程序得到信号,跳转至kekka()函数入,判断随机数。然后退出程序。这里有这样一个新函数。是我们在UNIX程序设计中时常用得到的。#includeint(*signal(intsig,void(*func())))();返回值:成功时:进程中断地址错误时:SIG_ERR这个函数挺不好懂呵。解释解释。它的意思是,当收到SIG这个信号时,跳转至func()这个函数处继续执行。intsig这个参数其实好办。下面我们会给出一个表。将其所有的参数一并列出。这是在头文件中#define的,代表各种不同的信号。void(*func())这个参数是一个函数指针。意思是在SIG信号被传给进程后,所要执行的部分。此外,func()这个参数还可以为如下值:SIG_DEL系统的默认动作SIG_IGN忽略这个信号。signal作用只有一次,当一个信号发生,触发signal动作的之后,signal就不起作用了。比方说,在上例中,如果你把所有的exit(O)全去掉(不退出程序),程序会进入无限循环中。你按一次Ctrl+C,signal执行一次,显示出结果。然后继续循环,生成随机数,输出。你再按Ctrl+C,signal就不好使了。如果需要重复使用signal()函数,可以考虑在signa1调用的动作函数中再加一个signal比方说在上例的程序中再加一个signal(SIGKILL,kekka);加了signal这个函数后,整个程序如果没收到信号,则正常执行。否则跳转至igna1中指定的func()函数处继续。当然,我们不可能,也没有必要去为每一个信号指定一个动作。当未指定动作的信号发生的时候,系统会执行默认的动作。比方说我们知道:当Ctrl+C按下的时候,正常默认的动作是结束程序。但是,Ctrl+C按下时,发生的信号是SIGKILL,如果你为SIGKILL这个信号指定了一个动作的话,系统将去执行你指定的这个动作,而不管原来默认的了。打个比方让大家更容易懂一些:比方说:你在睡午觉。突然来了一个美女(帅哥)(Ctrl+C)让你陪她/他去逛街购物共进晚餐……(此处删去若干字),你正常的默认的动作是马上起来陪她出去玩(程序中止)。但是你妈说不许去,你妈说你得在家擦窗户,你只好留在家擦窗户signal()指定的动作)。擦完窗户之后你可以选择陪美女去逛街(默认动作,Ctrl+C的埸合是退出程序,即上例程序中的exit(O)),可以选择继续睡觉(回到原来中断的地方继续执行,上例程序中如果没有exit(O),便会回到中断处继续执行)。(大哥。打个比方嘛。干嘛拿臭鸡蛋砸我••…)signal种类非常多,在此不一一列出。使用这个函数,你可以防止用户按下Ctrl+C结束程序。还可以做很多其它的事——只有你想不到的,没有C做不到的。注意:根据UNIX系统的不同,signal的定义是不一样的。比方说,有的老式UNIX工作站上SIGINT是按下del键后发生的信号,而有些机型上刚是按下Ctrl+Z发生的。在使用时要注意。再一次感谢SOHUC/C++论坛的朋友们的支持。我考上研究生了。下一次,准备说说UNIX下C程序中,如何向别的进程发送信号。第八章:向其它进程传递信号上一章,我们简单地说了一下信号。信号不仅仅可以在本进程内使用,还可以向其它的进程传递。确切地说,一般情况下,一个进程收到的信号绝大部分是从系统的NIT传来的。而INIT也是一个进程。用过UNIX或者LINUX的朋友大概都知道,UNIX系的操作系统里,有一个kill命令。如果你的一个进程无法中止,可以使用kill命令强行干掉它。%killmypro我们自己也可以做个简单的kill命令。/*nkill.c*/#include#include#includeintmain(intargc,char*argv[]){pid_tpid;if(argc!=2)//检查命令行参数个数{fprintf(stderr,Usage:%sPID\n,argv[0]);exit(1);}pid=atol(argv[1]);if(kill(pid,SIGKILL)!=0)//向pid这个进程传送一个SIGKILL信号{fprintf(stderr,killfailed\n);}return0;}执行例:%sleep300&(后台睡眠300秒)[1]520%nkill520[1]+Killedsleep300你用自己写的程序杀死了一个sleep进程。没事。杀了就杀了吧。留着也是垃圾••…说明一下,众所周知,UNIX是非常注意用户的权限的。所以,如果你向你自己生成的进程发送SIGKILL的话,这个进程会中止。如果你是向别的(你没有写权限的)进程发送SIGKILL的话,除非那个进程允许你动它,否则你发了也是白发。打个不恰当的比较下流一些的比方:如果你要求亲一下你自己老婆的话(你有“亲”的权限),你会如愿以偿。但是如果你想亲一下别人的老婆的话(你没有“亲”的权限),除非他及她同意,否则……呵呵。你死定了。所以,执行上面的程序的时候,你必须保证你有权限关掉那个进程。可不是说侮s-ef然后随便挑一个进程就能关的。要不那黑客也太好当了。几乎是咱们的老规矩了:先写个程序,然后解释这个程序,说说其中新出现的函数。这回还照旧吧。大家看到了。这里新出来一个kill()函数。解释如下:#include#includeintkill(pid_tpid,intsig);返回值:成功时:0失败时:-1这个函数是向进程ID(PID)为pid的进程传送一个sig信号。这个函数有什么用呢?用处可大了。在上一章中,我们用到了SIGKILL信号。这一节我们用的还是这个信号。实际上,信号(signal)有许多。而且根据UNIX系统的不同,这些信号也不太一样。我们简单列举一些信号例子。其用途请恕小生才疏,解释不清。有兴趣的朋友可以自己查阅一下资料。SIGHUP,SIGINT,SIGQUIT,SIGILL,SIGTRAP,SIGABRT,SIGEMT,SEGFPE(注意,这里是SEG),SIGKILL,SIGBUS,SIGSEGV,SIGSYS,SIGPIPE,SIGALRM,SIGTERM,SIGUSR1,SIGUSR2,SIGCLD,SIGPWR,SIGWINCH,SIGSTOP,SIGTSTP,SIGCONT,SIGTTIN,SIGTTOU使用kill()函数,可以将这些信号中的一个或者多个送至另一个进程。刚才的程序中,我们就是将一个SIGKILL信号送至sleep那个进程,该进程收到信号,以为是用户按下了Ctrl+C,就中止了。知道了这些,我们再来做一个比较有意思的程序。我们做一个cat程序(类似于DOS下的type,查看文本文件的内容)。不过这个cat程序与UNIX的cat不太一样,我们把由参数指定的文件的内容输出到屏幕上。如果你没指定文件,那么这个程序会等你五秒,在这五秒钟里,你可以从标准输入输入一个文件。然后显示。万一这五秒钟里你也没输入什么文件名。程序会把当前目录下的所有文件名打印出来(ls),以督促你输入一个文件名。/*show.c*/#include#include#includejmp_bufenv;intfunc();intmain(intargc,char*argv[]){charfilename[256];signal(SIGALRM,func);//如果收到一个SIGALRM信号,则转至func处执行if(argc==1){fprintf(stderr,&);alarm(5);//5秒钟后向自己发送一个SIGALRM信号setjpm(env);//设置记号scanf(%s,filename);execl(/usr/bin/more,more,filename,(char*)0);}else{execl(/usr/bin/more,more,argv[1],(char*)0);}return0;}intfunc(){intst;fprintf(stderr,Whichfiledoyouwanttoread?\n\n);if(fork()==0){execl(/bin/ls,ls,-FCa,(char*)0);}wait(&st);longjmp(env,0);//跳转至先前的setjmp()处}这个程序看明白了吗?可能有些不太好懂。我们从头跑一遍试试。首先是装入头文件等等。没关系了。然后signal(SIGALRM,func),即,当收到一个SIGALRM信号的时候,中断现在所做的工作。转而执行func()函数。不过可惜得很,现在暂时还没人给咱们发送SIGALRM信号,我们继续往下看。下面是判断命令行参数了。如果只有这个程序的可执行文件一个参数的话(也就是说,没有指明要显示哪个文件的内容),那就打印出一个&号来。然后设置alarm(5),也就是说,在5秒后给自己发一个SIGALRM信号。下一句setjpm(env)是设置一个记号,以后当执行至longjmp()函数的时候,就会跳转到这里来。然后等待你输入文件名。如果在这五秒种里,你输入了一个文件名的话,那么就向下执行一一通过execl()函数调用系统的more命令,显示该文件的内容。然后退出。这只需要要很少的一段时间。绝对用不上一秒(看你当前目录下有多少文件了。没人会在一个目录下装上1G吧?)。最后结束程序。alarm()在5秒后发出的信号乐意咋地咋地去吧。咱不用管了。问题是:如果在这五秒钟里,你没有输入文件名的话,事情就变得非常微妙了。这种情况下,程序将停留在scanf(%s,filename);这一行。等待你输入文件名。然后五秒种的期限到了°alarm()函数向这个进程发了一个SIGALRM信号。signal。函数检测到这个信号,转向func()函数处执行。func()函数先是输出了一个提示信息Whichfiledoyouwanttoread?。然后fork()出一个子进程。这个子进程通过execl()函数调用/bin/ls命令,显示出当前目录下的所有文
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025届宿州市重点中学高考英语三模试卷含解析
- 2025届山东省临沂市费县高三最后一模英语试题含答案
- 山西省运城市景胜中学2025届高三下学期联合考试英语试题含解析
- 江西省南昌五校2025年高三第三次测评英语试卷含解析
- 植树护岸施工方案
- 电动大门施工方案
- 基于SERVQUAL模型的智慧社区实施效果研究
- 乙烯键连接的共价有机框架材料的制备及其在光催化水解产氢方面的研究
- 城市活力空间评价及提升策略研究-以郑州市主城区为例
- 脂肪因子RBP4及SFRP5在不同分型心衰患者中的变化及临床意义
- 小学三年级音乐《马兰谣》课件
- “当代文化参与”学习任务群相关单元的设计思路与教学建议课件(共51张PPT)
- 提高卧床患者踝泵运动的执行率品管圈汇报书模板课件
- 同理心的应用教学教材课件
- DB4102-T 025-2021海绵城市建设施工与质量验收规范-(高清现行)
- 城市轨道交通安全管理隐患清单
- 锡膏使用记录表
- 儿童保健学课件:绪论
- 中小学校园安全稳定工作岗位责任清单
- 校园安全存在问题及对策
- NY∕T 309-1996 全国耕地类型区、耕地地力等级划分
评论
0/150
提交评论