【实验目的】
1、 学会在Linux下创建进程,并观察父子进程并发执行的情况,加深进程的理解。 2、 学会使用信号量解决资源共享问题,并观察各个进程互斥进入临界区的情况。
【实验类型】
设计型
【预习内容】
1、预习Linux的几个常用命令、Vi编辑器及gcc编译器
1)常用基本命令
cd abc 将工作目录改变到abc cd 改变当前目录到主目录 ls 列出当前目录的内容
ls -l 输出当前目录内容的长列表,每个目录或文件占一行 cat mx.c 显示mx.c文件内容 clear 清除终端屏幕
2)熟悉vi编辑器
a) 启动vi vi filename b) 输入模式命令
a(append) 在光标之后加入内容 A 在该行之末加入内容 i(insert) 在光标之前加入内容 I 在该行之首加入内容 ESC 离开输入模式 c) 命令模式
:q! 离开vi,并放弃刚在缓冲区内编辑的内容 :wq 将缓冲区内的资料写入当前文件中,并离开vi :w 将缓冲区内的资料写入当前文件中,但并不离开vi
:q 离开vi,若文件被修改过,则要被要求确认是否放弃修改的内容,此
指令可与:w配合使用
3)gcc 编译器
编译器把源程序编译生成目标代码的任务分为以下4步: a. 预处理,把预处理命令扫描处理完毕;
b. 编译,把预处理后的结果编译成汇编或者目标模块;
c. 汇编,把编译出来的结果汇编成具体CPU上的目标代码模块; d. 连接,把多个目标代码模块连接生成一个大的目标模块;
常用 gcc –o outfilename sourcefile
例子: (1)vi test.c
(2) 在vi编辑器里编写以下程序 # include printf(“My first test program!\\n”); } (3)编译源程序 gcc -o test test.c ↙ 2、预习几个相关的函数 1)、fork( ) 创建一个新的子进程。其子进程会复制父进程的数据与堆栈空间,并继承父进程的用户代码、组代码、环境变量、已打开的文件代码、工作目录和资源限制。 系统调用格式: int fork() 如果Fork成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回0。如果fork失败则直接返回-1。 2)、wait( ) 等待子进程运行结束。如果子进程没有完成,父进程一直等待。wait( )将调用进程挂起,直至其子进程因暂停或终止而发来软中断信号为止。 系统调用格式: int wait(status) int *status; wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一快返回。如果不在意结束状态值,则参数status 可以设成NULL。子进程的结束状态值请参考waitpid()。返回值如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。 核心对wait( )作以下处理: (1) 首先查找调用进程是否有子进程,若无,则返回出错码; (2) 若找到一处于“僵死状态”的子进程,则将子进程的执行时间加到父进程的执行时 间上,并释放子进程的进程表项; (3) 若未找到处于“僵死状态”的子进程,则调用进程便在可被中断的优先级上睡眠, 等待其子进程发来软中断信号时被唤醒。 3)、exit( ) 终止进程的执行。 系统调用格式: void exit(status) int status; 其中,status是返回给父进程的一个整数,以备查考。为了及时回收进程所占用的资源并减少父进程的干预,UNIX/LINUX利用exit( )来实现进程的自我终止,通常父进程在创建子进程时,应在进程的末尾安排一条exit( ),使子进程自我终止。exit(0)表示进程正常终止,exit(1)表示进程运行有错,异常终止。 如果调用进程在执行exit( )时,其父进程正在等待它的终止,则父进程可立即得到其返回的整数。核心须为exit( )完成以下操作: (1) 关闭软中断 (2) 回收资源 (3) 写记帐信息 (4) 置进程为“僵死状态” (5) 4)、semget() 可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集: 系统调用:semget(); 原型:int semget(key_t key,int nsems,int semflg); 返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1: errno= EACCESS(没有权限) EEXIST(信号量集已经存在,无法创建) EIDRM(信号量集已经删除) ENOENT(信号量集不存在,同时没有使用IPC_CREAT) ENOMEM(没有足够的内存创建新的信号量集) ENOSPC(超出限制) 系统调用semget()的第一个参数为IPC_PRIVATE,系统将创建新的共享存储区的关键字。 参数nsems指出了一个新的信号量集中应该创建的信号量的个数。 打开和存取操作与参数semflg中的内容相关。 IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。 IPC_EXCL当和IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。 如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统 中已经存在的同样的关键字值的信号量的标识符。 如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。 在实验中,可以使用0666|IPC_CREAT,表示任意进程可读可写。 5)、semop() 系统调用:semop(); 调用原型:int semop(int semid, struct sembuf* sops, unsign ednsops); 返回值:0,如果成功。-1,如果失败: errno= E2BIG(nsops大于最大的ops数目) EACCESS(权限不够) EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行) EFAULT(sops指向的地址无效) EIDRM(信号量集已经删除) EINTR(当睡眠时接收到其他信号) EINVAL(信号量集不存在,或者semid无效) ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构) ERANGE(信号量值超出范围) 第一个参数是关键字值。 第二个参数是指向将要操作的数组的指针。 第三个参数是数组中的操作的个数。 参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的: struct sembuf{ ushort sem_num;/*将要处理的信号量的个数*/ short sem_op;/*要执行的操作*/ short sem_flg;/*操作标志*/ } 如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。如 果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。 sem_flg:信号操作标志,可能的选择有两种 IPC_NOWAIT //对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息。 SEM_UNDO //程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。 nsops:信号操作结构的数量,恒大于或等于1。 6)、 semctl() 系统调用:semctl(); 原型:int semctl(int semid, int semnum, int cmd, union semun arg); 返回值:如果成功,则为一个正数。 如果失败,则为-1:errno= EACCESS(权限不够) EFAULT(arg指向的地址无效) EIDRM(信号量集已经删除) EINVAL(信号量集不存在,或者semid无效) EPERM(EUID没有cmd的权利) ERANGE(信号量值超出范围) 系统调用semctl()的第一个参数是关键字值。 第二个参数是操作信号在信号集中的编号,第一个信号的编号是0。 第三个参数cmd中可以使用的命令如下: ·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。 ·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。 ·IPC_RMID将信号量集从内存中删除。 ·GETALL用于读取信号量集中的所有信号量的值。 ·GETNCNT返回正在等待资源的进程数目。 ·GETPID返回最后一个执行semop操作的进程的PID。 ·GETVAL返回信号量集中的一个单个的信号量的值。 ·GETZCNT返回这在等待完全空闲的资源的进程数目。 ·SETALL设置信号量集中的所有的信号量的值。 ·SETVAL设置信号量集中的一个单独的信号量的值。 第四个参数arg代表一个semun的实例。 semun是在linux/sem.h中定义的: union semun{ int val;/*value for SETVAL*/ struct semid_ds *buf;/*buffer for IPC_STAT&IPC_SET*/ ushort *array;/*array for GETALL&SETALL*/ struct seminfo *__buf;/*buffer for IPC_INFO*/ void *__pad; } val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array在使用GETALL/SETALL命令时使用的指针。 【实验内容及要求】 一、 要求读懂以下程序,并分析实验结果 #include main() { int pid; int i; while((pid = fork())==-1); } for(i=0;i<5;i++) { sleep(5); if(pid==0) { printf(\"child process ID:%d\%d\\n\ exit(0); } else { wait(NULL); printf(\"parent process ID:%d\%d\\n\ } } 二、 要求读懂以下程序,并分析实验结果 #include int pid,semid; struct sembuf P,V; union semun arg; int main(void) { semid=semget(IPC_PRIVATE,1,0666|IPC_CREAT); arg.val=1; if (semctl(semid,0,SETVAL,arg)==-1) perror(\"semctl SETVAL ERROR\"); P.sem_num=0; P.sem_op=-1; P.sem_flg= SEM_UNDO; V.sem_num=0; V.sem_op=1; V.sem_flg= SEM_UNDO; while((pid=fork())==-1); } if(pid==0) { semop(semid,&P,1); sleep(1); printf(\"I am child process\\n\"); semop(semid,&V,1); } else { semop(semid,&P,1); printf(\"I am parent process\\n\"); semop(semid,&V,1); } semctl(semid,IPC_RMID,0); exit(0); 三、 在充分理解以上例子程序的基础上,独立编写生产者和消费者程序(选做) 【实验报告要求】 1、 详细填写实验目的和实验要求。 2、 详细总结本次试验中学到的重要函数。 3、 针对每个例子程序的运行,详细地分析实验结果。 4、 记录编写的生产者和消费者程序,并在重要部分的语句上添加注释。(选做) 5、 总结上机调试过程中所遇到的问题和收获。 因篇幅问题不能全部显示,请点此查看更多更全内容