欢迎来到淘文阁 - 分享文档赚钱的网站! | 帮助中心 好文档才是您的得力助手!
淘文阁 - 分享文档赚钱的网站
全部分类
  • 研究报告>
  • 管理文献>
  • 标准材料>
  • 技术资料>
  • 教育专区>
  • 应用文书>
  • 生活休闲>
  • 考试试题>
  • pptx模板>
  • 工商注册>
  • 期刊短文>
  • 图片设计>
  • ImageVerifierCode 换一换

    Linux下的多进程编程初步(转载).pdf

    • 资源ID:70008555       资源大小:260.81KB        全文页数:9页
    • 资源格式: PDF        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    微信登录下载
    三方登录下载: 微信开放平台登录   QQ登录  
    二维码
    微信扫一扫登录
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。
    如填写123,账号就是123,密码也是123。
    支付方式: 支付宝    微信支付   
    验证码:   换一换

     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    Linux下的多进程编程初步(转载).pdf

    1引言对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值。fork函数是Unix系统最杰出的成就之一,它是七十代UNIX早期的开发者经过长期在理论和实践上的艰苦探索后取得的成果,一方面,它使操作系统在进程管理上付出最小的代价,另一方面,又为程序员提供一个简洁明的多进程方法。与DOS和早期的Windows同,Unix/Linux系统是真正实现多任务操作的系统,可以说,使用多进程编程,就能算是真正的Linux环境下编程。多线程程序设计的概念早在六十代就被提出,但直到八十代中期,Unix系统中才引入多线程机制,如今,由于自身的许多优点,多线程编程已经得到广泛的应用。下面,我们将介绍在Linux下编写多进程和多线程程序的一些初步知识。2多进程编程什么是一个进程?进程这个概念是针对系统而是针对用户的,对用户来说,他面对的概念是程序。当用户敲入命令执行一个程序的时候,对系统而言,它将启动一个进程。但和程序同的是,在这个进程中,系统可能需要再启动一个或多个进程来完成独立的多个任务。多进程编程的主要内容包括进程控制和进程间通信,在解这些之前,我们先要简单知道进程的结构。2.1Linux下进程的结构Linux下一个进程在内存里有三部分的数据,就是代码段、堆栈段和数据段。其实学过汇编语言的人一定知道,一般的CPU都有上述三种段寄存器,以方便操作系统的运行。这三个部分也是构成一个完整的执行序列的必要的部分。代码段,顾名思义,就是存放程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变。而数据段则存放程序的全局变,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就多介绍。系统如果同时运行数个相同的程序,它们之间就能使用同一个堆栈段和数据段。2.2Linux下的进程控制在传统的Unix环境下,有两个基本的操作用于创建和修改进程:函数fork()用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec()用来启动另外的进程以取代当前运行的进程。Linux的进程控制和传统的Unix进程控制基本一致,只在一些细节的地方有些区别,例如在Linux系统中调用vfork和fork完全相同,而在有些版本的Unix系统中,vfork调用有同的功能。由于这些差别几乎影响我们大多数的编程,在这里我们予考虑。2.2.1fork()fork在英文中是分叉的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用fork,就产生另一个进程,于是进程就分叉,所以这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示使用fork的基本框架:cpp0 1.v o i d m a i n()0 2.i n t i 0 3.i f (f o r k()=0 )0 4./*子进程程序*/0 5.f o r (i =1 i 1 0 0 0 i +)p r i n t f(T h i s i s c h i l d p r o c e s s/n )0 6.0 7.e l s e 0 8./*父进程程序*/0 9.f o r (i =1 i )0 8.f g e t s(c o m m a n d,2 5 6,s t d i n )0 9.c o m m a n d s t r l e n(c o m m a n d)1 =0 1 0.i f (f o r k()=0 )1 1./*子进程执行此命令*/1 2.e x e c l p(c o m m a n d,c o m m a n d )1 3./*如果e x e c 函数返回,表明没有正常执行命令,打印错误信息*/1 4.p e r r o r(c o m m a n d )1 5.e x i t(e r r o r n o )1 6.1 7.e l s e 1 8./*父进程,等待子进程结束,并打印子进程的返回值*/1 9.w a i t (&r t n )2 0.p r i n t f(c h i l d p r o c e s s r e t u r n%d/n ,.r t n )2 1.2 2.2 3.此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友一定知道DOS/WINDOWS也有exec类函数,其使用方法是类似的,但DOS/WINDOWS还有spawn类函数,因为DOS是单任务的系统,它只能将父进程驻留在机器内再执行子进程,这就是spawn类的函数。WIN32已经是多任务的系统,但还保留spawn类函数,WIN32中实现spawn函数的方法同前述UNIX中的方法差多,开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统,所以从核心角度上讲需要spawn类函数。在这一节里,我们还要讲讲system()和popen()函数。system()函数先调用fork(),然后再调用exec()来执行用户的登录shell,通过它来查找可执行文件的命令并分析参数,最后它么使用wait()函数族无法加载插件。无法加载插件。无法加载插件。无法加载插件。之一来等待子进程的结束。函数popen()和函数system()相似,同的是它调用pipe()函数创建一个管道,通过它来完成程序的标准输入和标准输出。这两个函数是为那些太勤快的程序员设计的,在效率和安全方面都有相当的缺陷,在可能的情况下,应该尽避免。2.3Linux下的进程间通信详细的讲述进程间通信在这里绝对是可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到什么样的地步,所以在这一节的开头首先向大家推荐著名作者RichardStevens的著名作品:AdvancedProgrammingintheUNIXEnvironment,它的中文译本UNIX环境高级编程已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最最简单的一些知识和概念。首先,进程间通信至少可以通过传送打开文件来实现,同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用这种方法。但一般说来,进程间通信(IPC:InterProcessCommunication)包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且幸的是,极少方法能在所有的Unix系统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信方法:管道、消息队列、共享内存、信号、套接口等等。下面我们将逐一介绍。2.3.1管道管道是进程间通信中最古的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。无名管道由pipe()函数创建:#includeintpipe(intfiledis2);参数filedis返回两个文件描述符:filedes0为读而打开,filedes1为写而打开。filedes1的输出是filedes0的输入。下面的例子示范如何在父进程和子进程间实现通信。cpp0 1.#d e f i n e I N P U T 0 0 2.#d e f i n e O U T P U T 1 0 3.0 4.v o i d m a i n()0 5.i n t f i l e _ d e s c r i p t o r s 2 0 6./*定义子进程号*/0 7.p i d _ t p i d 0 8.c h a r b u f 2 5 6 0 9.i n t r e t u r n e d _ c o u n t 1 0./*创建无名管道*/1 1.p i p e(f i l e _ d e s c r i p t o r s)1 2./*创建子进程*/1 3.i f(p i d =f o r k()=1)1 4.p r i n t f(E r r o r i n f o r k/n )1 5.e x i t(1)1 6.1 7./*执行子进程*/1 8.i f(p i d =0)1 9.p r i n t f(i n t h e s p a w n e d (c h i l d)p r o c e s s./n )2 0./*子进程向父进程写数据,关闭管道的读端*/2 1.c l o s e(f i l e _ d e s c r i p t o r s I N P U T )2 2.w r i t e(f i l e _ d e s c r i p t o r s O U T P U T ,t e s t d a t a ,s t r l e n(t e s t d a t a )2 3.e x i t(0)2 4.e l s e 2 5./*执行父进程*/2 6.p r i n t f(i n t h e s p a w n i n g (p a r e n t)p r o c e s s./n )2 7./*父进程从管道读取子进程写的数据,关闭管道的写端*/2 8.c l o s e(f i l e _ d e s c r i p t o r s O U T P U T )2 9.r e t u r n e d _ c o u n t =r e a d(f i l e _ d e s c r i p t o r s I N P U T ,b u f,s i z e o f(b u f)3 0.p r i n t f(%d b y t e s o f d a t a r e c e i v e d f r o m s p a w n e d p r o c e s s:%s/n ,3 1.r e t u r n e d _ c o u n t,b u f)3 2.3 3.在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成一个名为myfifo的有名管道:方式一:mkfifo(myfifo,rw)方式二:mknodmyfifop生成有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建一个名为myfifo的有名管道。cpp0 1./*进程一:读有名管道*/0 2.#i n c l u d e 0 3.#i n c l u d e 0 4.v o i d m a i n()0 5.F I L E *i n _ f i l e 0 6.i n t c o u n t =1 0 7.c h a r b u f 8 0 0 8.i n _ f i l e =f o p e n(m y p i p e ,r )0 9.i f (i n _ f i l e =N U L L)1 0.p r i n t f(E r r o r i n f d o p e n./n )1 1.e x i t(1)1 2.1 3.w h i l e (c o u n t =f r e a d(b u f,1,8 0,i n _ f i l e)0)1 4.p r i n t f(r e c e i v e d f r o m p i p e:%s/n ,b u f)1 5.f c l o s e(i n _ f i l e)1 6.1 7./*进程二:写有名管道*/1 8.#i n c l u d e 1 9.#i n c l u d e 2 0.v o i d m a i n()2 1.F I L E *o u t _ f i l e 2 2.i n t c o u n t =1 2 3.c h a r b u f 8 0 2 4.o u t _ f i l e =f o p e n(m y p i p e ,w )2 5.i f (o u t _ f i l e =N U L L)2 6.p r i n t f(E r r o r o p e n i n g p i p e.)无法加载插件。无法加载插件。2 7.e x i t(1)2 8.2 9.s p r i n t f(b u f,t h i s i s t e s t d a t a f o r t h e n a m e d p i p e e x a m p l e/n )3 0.f w r i t e(b u f,1,8 0,o u t _ f i l e)3 1.f c l o s e(o u t _ f i l e)3 2.2.3.2消息队列消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也再解释,也建议读者忽略这种方式。2.3.3共享内存共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据需要在同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式给系统带来额外的开销,但在现实中并常用,因为它控制存取的将是实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然太实际。常用的方式是通过shmXXX函数族来实现利用共享内存进行存储的。首先要用的函数是shmget,它获得一个共享存储标识符。#include#include#includeintshmget(key_tkey,intsize,intflag)这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数的标识符,这样对一个消息队列发送消息时只要引用标识符就可以。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。void*shmat(intshmid,void*addr,intflag)shmid为shmget函数返回的共享存储标识符,addr和flag参数决定以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好。通常,信号被要来实现对共享存储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。2.3.4信号信号又称为信号灯,它是用来协调同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为获得共享资源,进程需要执行下列操作:(1)测试控制该资源的信号。无法加载插件。无法加载插件。(2)若此信号的值为正,则允许进行使用该资源。进程将进号减1。(3)若此信号为0,则该资源目前可用,进程进入睡眠状态,直至信号值大于0,进程被唤醒,转入步骤(1)。(4)当进程再使用一个信号控制的资源时,信号值加1。如果此时有进程正在睡眠等待此信号,则唤醒此进程。维护信号状态的是Linux内核操作系统而是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h中看到内核用来维护信号状态的各个结构的定义。信号是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获得一个信号ID。cpp0 1.#i n c l u d e 0 2.#i n c l u d e 0 3.#i n c l u d e 0 4.i n t s e m g e t(k e y _ t k e y,i n t n s e m s,i n t f l a g)key是前面讲过的IPC结构的关键字,它将来决定是创建新的信号集合,还是引用一个现有的信号集合。nsems是该集合中的信号数。如果是创建新集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号集合(一般在客户机中)则将nsems指定为0。semctl函数用来对信号进行操作。intsemctl(intsemid,intsemnum,intcmd,unionsemunarg)同的操作是通过cmd参数来实现的,在头文件sem.h中定义7种同的操作,实际编程时可以参照使用。semop函数自动执行信号集合上的操作数组。intsemop(intsemid,structsembufsemoparray,size_tnops)semoparray是一个指针,它指向一个信号操作数组。nops规定该数组中操作的数。下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号,建立此信号的索引,修改索引指向的信号的值,最后我们清除信号。在下面的代码中,函数ftok生成我们上文所说的唯一的IPC关键字。cpp0 1.#i n c l u d e 0 2.#i n c l u d e 0 3.#i n c l u d e 0 4.#i n c l u d e 0 5.v o i d m a i n()0 6.k e y _ t u n i q u e _ k e y /*定义一个I P C 关键字*/0 7.i n t i d 0 8.s t r u c t s e m b u f l o c k _ i t 0 9.u n i o n s e m u n o p t i o n s 1 0.i n t i 1 1.1 2.u n i q u e _ k e y =f t o k(.,a )/*生成关键字,字符 a 是一个随机种子*/1 3./*创建一个新的信号集合*/无法加载插件。无法加载插件。1 4.i d =s e m g e t(u n i q u e _ k e y,1,I P C _ C R E A T|I P C _ E X C L|0 6 6 6)1 5.p r i n t f(s e m a p h o r e i d=%d/n ,i d)1 6.o p t i o n s.v a l =1 /*设置变值*/1 7.s e m c t l(i d,0,S E T V A L,o p t i o n s)/*设置索引0 的信号*/1 8.1 9./*打印出信号的值*/2 0.i =s e m c t l(i d,0,G E T V A L,0)2 1.p r i n t f(v a l u e o f s e m a p h o r e a t i n d e x 0 i s%d/n ,i)2 2.2 3./*下面重新设置信号*/2 4.l o c k _ i t.s e m _ n u m =0 /*设置哪个信号*/2 5.l o c k _ i t.s e m _ o p =1 /*定义操作*/2 6.l o c k _ i t.s e m _ f l g =I P C _ N O W A I T /*操作方式*/2 7.i f (s e m o p(i d,&l o c k _ i t,1)=1)2 8.p r i n t f(c a n n o t l o c k s e m a p h o r e./n )2 9.e x i t(1)3 0.3 1.3 2.i =s e m c t l(i d,0,G E T V A L,0)3 3.p r i n t f(v a l u e o f s e m a p h o r e a t i n d e x 0 i s%d/n ,i)3 4.3 5./*清除信号*/3 6.s e m c t l(i d,0,I P C _ R M I D,0)3 7.2.3.5套接口套接口(socket)编程是实现Linux系统和其他大多数操作系统中进程间通信的主要方式之一。我们熟知的WWW服务、FTP服务、TELNET服务等都是基于套接口编程来实现的。除在异地的计算机进程间以外,套接口同样适用于本地同一台计算机内部的进程间通信。关于套接口的经典教材同样是RichardStevens编著的Unix网络编程:联网的API和套接字,清华大学出版社出版该书的影印版。它同样是Linux程序员的必备书籍之一。关于这一部分的内容,可以参照本文作者的另一篇文章设计自己的网络蚂蚁,那里由常用的几个套接口函数的介绍和示例程序。这一部分或许是Linux进程间通信编程中最须关注和最吸引人的一部分,毕竟,Internet正在我们身边以可思议的速度发展着,如果一个程序员在设计编写他下一个程序的时候,根本没有考虑到网络,考虑到Internet,那么,可以说,他的设计很难成功。3Linux的进程和Win32的进程/线程比较熟悉WIN32编程的人一定知道,WIN32的进程管理方式与Linux上有着很大区别,在UNIX里,只有进程的概念,但在WIN32里却还有一个线程的概念,那么Linux和WIN32在这里究竟有着什么区别呢?WIN32里的进程/线程是继承自OS/2的。在WIN32里,进程是指一个程序,而线程是一个进程里的一个执行线索。从核心上讲,WIN32的多进程与Linux并无多大的区别,在WIN32里的线程才相当于Linux的进程,是一个实际正在执行的代码。但是,WIN32里同一个进程里各个线程之间是共享数据段的。这才是与Linux的进程最大的同。下面这段程序显示WIN32下一个进程如何启动一个线程。无法加载插件。无法加载插件。cpp0 1.i n t g 0 2.D W O R D W I N A P I C h i l d P r o c e s s(L P V O I D l p P a r a m e t e r )0 3.i n t i 0 4.f o r (i =1 i 1 0 0 0 i +)0 5.g +0 6.p r i n t f(T h i s i s C h i l d T h r e a d:%d/n ,g )0 7.0 8.E x i t T h r e a d(0 )0 9.1 0.1 1.v o i d m a i n()1 2.1 3.i n t t h r e a d I D 1 4.i n t i 1 5.g =0 1 6.C r e a t e T h r e a d(N U L L,0,C h i l d P r o c e s s,N U L L,0,&t h r e a d I D )1 7.f o r (i =1 i 1 0 0 0 i +)1 8.g +1 9.p r i n t f(T h i s i s P a r e n t T h r e a d:%d/n ,g )2 0.2 1.在WIN32下,使用CreateThread函数创建线程,与Linux下创建进程同,WIN32线程是从创建处开始运行的,而是由CreateThread指定一个函数,线程就从那个函数处开始运行。此程序同前面的UNIX程序一样,由两个线程各打印1000条信息。threadID是子线程的线程号,另外,全局变g是子线程与父线程共享的,这就是与Linux最大的同之处。大家可以看出,WIN32的进程/线程要比Linux复杂,在Linux要实现类似WIN32的线程并难,只要fork以后,让子进程调用ThreadProc函数,并且为全局变开设共享数据区就行,但在WIN32下就无法实现类似fork的功能。所以现在WIN32下的C语言编译器所提供的库函数虽然已经能兼容大多数Linux/UNIX的库函数,但却仍无法实现fork。对于多任务系统,共享数据区是必要的,但也是一个容易引起混乱的问题,在WIN32下,一个程序员很容易忘记线程之间的数据是共享的这一情况,一个线程修改过一个变后,另一个线程却又修改它,结果引起程序出问题。但在Linux下,由于变本来并共享,而由程序员来显式地指定要共享的数据,使程序变得清晰与安全。至于WIN32的进程概念,其含义则是应用程序,也就是相当于UNIX下的exec。Linux也有自己的多线程函数pthread,它既同于Linux的进程,也同于WIN32下的进程,关于pthread的介绍和如何在Linux环境下编写多线程程序我们将在另一篇文章Linux下的多线程编程中讲述。无法加载插件。无法加载插件。

    注意事项

    本文(Linux下的多进程编程初步(转载).pdf)为本站会员(qwe****56)主动上传,淘文阁 - 分享文档赚钱的网站仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知淘文阁 - 分享文档赚钱的网站(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    关于淘文阁 - 版权申诉 - 用户使用规则 - 积分规则 - 联系我们

    本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

    工信部备案号:黑ICP备15003705号 © 2020-2023 www.taowenge.com 淘文阁 

    收起
    展开