您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 公司方案 > Linux操作系统分析实验指导书(2015)
1Linux操作系统分析实验指导书计算机科学与教育软件学院计算机科学系实验一进程控制与进程互斥:Linux系统下多进程与多线程编程-----------------------2实验二进程通信:Linux系统的进程间通信----管道通信----------------------------------9实验三内存分配与回收:Linux系统下利用链表实现动态内存分配(也可以做课本中的内存管理实例)-------------------------------------------------------------------------11实验四文件操作算法:Linux系统下文件管理模拟实验--------------------------------18实验五设备驱动:Linux系统下的字符设备驱动程序编程----------------------------222实验一进程控制与进程互斥:Linux系统下多进程与多线程编程一、实验目的1、理解Linux下进程的结构;2、理解Linux下产生新进程的方法(系统调用—fork函数);3、掌握如何启动另一程序的执行;4、理解Linux下线程的结构;5、理解Linux下产生新线程的方法;6、理解Linux系统下多进程与多线程的区别二、实验内容1、利用fork函数创建新进程,并根据fork函数的返回值,判断自己是处于父进程还是子进程中;2、在新创建的子进程中,使用exec类的函数启动另一程序的执行;分析多进程时系统的运行状态和输出结果;3、利用最常用的三个函数pthread_create,pthread_join和pthread_exit编写了一个最简单的多线程程序。理解多线程的运行和输出情况;4、利用信号量机制控制多线程的运行顺序,并实现多线程中数据的共享;5、分析Linux系统下多进程与多线程中的区别。三、实验指导(一)Linux系统下多进程编程:1、理解Linux下进程的结构Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,一般的CPU,如I386,都有上述三种段寄存器,以方便操作系统的运行。“代码段”,顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段。堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。2、如何使用fork函数在Linux下产生新进程的系统调用就是fork函数,这个函数名是英文中“分叉”的意思。一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了,所以这个名字取得很形象。那么调用这个fork函数时发生了什么呢?一个程序调用fork函数,系统就为一个新的进程准备了前述三个段,首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的,对于数据段和堆栈段,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。而如果两个进程要共享什么数据的话,就要使用另一套函数(shmget,shmat,shmdt等)来操作。现在,已经是两个进程了,对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零,这样,对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。3但是,如果一个大程序在运行中,它的数据段和堆栈都很大,调用一次fork就要复制一次,那么fork的系统开销不是很大吗?其实,一般CPU都是以“页”为单位分配空间的,像INTEL的CPU,其一页在通常情况下是4K字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,利用写时复制技术,该进程的数据被写入另一数据段,这时两个进程之间的数据才有了区别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。3、如何启动另一程序的执行在Linux中要使用exec类的函数来启动另一程序的执行,exec类的函数不止一个,但大致相同,在Linux中,它们分别是:execl,execlp,execle,execv,execve和execvp。一个进程一旦调用exec类函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息)。那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢?那就是结合fork与exec的使用。下面一段代码显示如何启动运行其它程序:#includestdio.h#includeunistd.hmain(){intpid;pid=fork();/*创建子进程*/switch(pid){case-1:/*创建失败*/printf(forkfail!\n);exit(1);case0:/*子进程*/execl(/bin/ls,ls,-1,-color,NULL);printf(execfail!\n);exit(1);default:/*父进程*/wait(NULL);/*同步*/printf(lscompleted!\n);exit(0);}}运行结果执行命令ls-l-color,(按倒序)列出当前目录下所有文件和子目录;lscompleted!程序在调用fork()建立一个子进程后,马上调用wait(),使父进程在子进程结束之前,一直处于睡眠状态。子进程用exec()装入命令ls,exec()后,子进程的代码被ls的代码取代,这时子进程的PC指向ls的第1条语句,开始执行ls的命令代码。4(二)Linux系统下多线程编程:1、为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。使用多线程的理由之一是和进程相比,它是一种非常节俭的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种昂贵的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。除了以上所说的优点外,多线程程序作为一种多任务、并发的工作方式,还有以下的优点:1)提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(timeconsuming)置于一个新的线程,可以避免这种尴尬的情况。2)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。2、简单的多线程编程Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。顺便说一下,Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。下面展示一个最简单的多线程程序example1.c。/*example.c*/#includestdio.h#includepthreadtypes.hvoidthread(void){inti;for(i=0;i3;i++)printf(Thisisapthread.n);}5intmain(void){pthread_tid;inti,ret;ret=pthread_create(&id,NULL,(void*)thread,NULL);if(ret!=0){printf(Createpthreaderror!n);exit(1);}for(i=0;i3;i++)printf(Thisisthemainprocess.n);pthread_join(id,NULL);return(0);}我们编译此程序:gccexample1.c-lpthread-oexample1运行example1,我们得到如下结果:Thisisthemainprocess.Thisisapthread.Thisisthemainprocess.Thisisthemainprocess.Thisisapthread.Thisisapthread.再次运行,我们可能得到如下结果:Thisisapthread.Thisisthemainprocess.Thisisapthread.Thisisthemainprocess.Thisisapthread.Thisisthemainprocess.前后两次结果不一样,这是两个线程争夺CPU资源的结果。上面的示例中,我们使用到了两个函数,pthread_create和pthread_join,并声明了一个pthread_t型的变量。pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:typedefunsignedlongintpthread_t;它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:externintpthread_create__P((pthread_t*__thread,__constpthread_attr_t*__attr,void*(*__start_routine)(void*),void*__arg));第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线
本文标题:Linux操作系统分析实验指导书(2015)
链接地址:https://www.777doc.com/doc-2884864 .html