收藏 分享(赏)

嵌入式Linux应用程序开发.ppt

上传人:j35w19 文档编号:5939841 上传时间:2019-03-21 格式:PPT 页数:103 大小:882KB
下载 相关 举报
嵌入式Linux应用程序开发.ppt_第1页
第1页 / 共103页
嵌入式Linux应用程序开发.ppt_第2页
第2页 / 共103页
嵌入式Linux应用程序开发.ppt_第3页
第3页 / 共103页
嵌入式Linux应用程序开发.ppt_第4页
第4页 / 共103页
嵌入式Linux应用程序开发.ppt_第5页
第5页 / 共103页
点击查看更多>>
资源描述

1、嵌入式Linux应用程序开发,主讲人:方攀 Email: Blog:,嵌入式Linux应用程序开发,嵌入式Linux应用程序开发及交叉编译:Hello World ! 嵌入式Linux内核模块开发:Hello Module! Linux环境下多进程及多线程编程,1.1 Linux应用程序介绍,在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是灵活运用C编译器.目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSIC标准的编译系统,能够编译用C、C+和Object C

2、等语言编写的程序.GCC不仅功能非常强大,结构也异常灵活.最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等. 开放自由和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程.在使用GCC编译程序时,编译过程可以被细分为四个阶段: 预处理(Pre-Processing) 编译(Compiling) 汇编(Assembling) 链接(Linking) Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制

3、文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备.和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码 GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性此外,GCC还对标准的C和C+语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量.,1.1 应用程序起步编写源文件,在/root/下建一个自己的目录 #mkdir project #cd project #vim helloworld.c #include int main(void) printf(“W

4、e love arm,we love sep4020!n“);return 1; ,代码注意问题,main函数的返回值应该是int类型; main函数在终止前没有调用“return 1;”语句来结尾。,1.2 编译helloworld.c,(1)在pc机上编译能在pc机上运行的应用程序的方法# gcc -o helloworld_pc helloworld.c(这个是用i386的gcc编译器编译的,所以只能在pc机的linux环境中运行) (2)在pc机上编译能在sep4020板子上运行的应用程序的方法# arm-linux-gcc -o helloworld_arm helloworld.c

5、(这个是用arm的交叉gcc编译器编译的,所以可以下载到板子上运行),1.3运行helloworld程序,(1)在pc机上运行i386的程序 rootlocalhost code# ./helloworld_pc We love arm,we love sep4020! (2)在开发板上运行应用程序 将应用程序helloworld_arm拷贝到/nfs/demo文件夹下 rootlocalhost code# cp helloworld_arm /nfs/demo / # cd demo /demo # ./helloworld_arm We love arm,we love sep4020!

6、,2.1 编写Hello Module源代码,前面我们介绍了一个简单的Linux程序Hello World, 它是运行于用户态的应用程序,现在我们再介绍一个运行于内核态的 Hello Module程序,它其实是一个最简单的驱动程序模块。我们将Hello Module的源代码放置于/root/project/module目录,名称为hellomodule.c,内容如下:,#include #include MODULE_LICENSE(“GPL“); static int _init sep4020_hello_module_init(void) printk(“Hello, sep4020 m

7、odule is installed !n“); return 0; static void _exit sep4020_hello_module_cleanup(void) printk(“Good-bye, sep4020 module was removed!n“); module_init(sep4020_hello_module_init); module_exit(sep4020_hello_module_cleanup);,2.2 编译Hello Module源代码,由于这个模块是加到嵌入式linux的内核中的,所以它肯定会用到许多嵌入式linux源码的头文件的,我们的嵌入式li

8、nux的内核源码位置在/linux-3.2/下面,这中间的链接过程非常复杂,为了不让我们手动输入编译指令,一般编译2.6 版本的驱动模块需要把驱动代码加入内核代码树,并做相应的配置,如下步骤 Step1:把刚才的源码文件拷贝到/linux-3.2/drivers/char/sep4020_char目录下 Step 2:编辑配置 /linux-3.2/drivers/char/sep4020_char 的Kconfig文件,加入驱动选项( 注意tristate 前面有tab键) config SEP4020_HELLOMODULEtristate “sep4020 hello module dr

9、iver“ 使之在 make menuconfig的时候出现 ;,Step3: 修改Makefile文件,在其中加入: obj-$(CONFIG_SEP4020_HELLOMODULE) += hellomodule.o Step4:这时回到 /linux-3.2 源代码根目录位置,执行 make modules,2.3 把 HelloModule 下载到开发板并安装使用,3 Linux进程编程,3.1 Linux进程的概念 3.2 Linux下的进程启动 3.3 进程控制编程 3.4 守护进程编程 3.5 进程间通信,3.1 Linux进程的概念,(1)进程是一个独立的可调度的活动; (2)

10、进程是一个抽象实体,当它执行某个任务时,将要分配和释放各种资源(P. Denning) ; (3)进程是可以并行执行的计算部分。以上进程的概念都不相同,但其本质是一样的。它指出了进程是一个程序的一次执行的过程。它和程序是有本质区别的,程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整个过程。它是程序执行和资源管理的最小单位。,进程控制块 (1),进程是 Linux 系统的基本调度单位,那么从系统的角度看如何描述并表示它的变化呢? 进程标识符:当一个进程产生时,系统都会为它分配一个标识符;进程所占的内

11、存区域:每个进程执行时都需要占用一定的内存区域,此区域用于保存该进程所运行的程序代码和使用的程序变量。每一个进程所占用的内存是相互独立的,因此改变一个进程所占内存中数据的任何改动,都只对该进程产生影响,不会影响到其它进程的顺利执行; 文件描述符:当一个进程在执行时,它需要使用一些相关的文件描述符。文件描述符描述了被打开文件的信息,不同的进程打开同一个文件时,所使用的文件描述符是不同的。一个进程文件描述符的改变并不会对其它的进程打开同一个文件的描述符产生任何影响;,进程控制块 (2),安全信息:一个进程的安全信息包括用户识别号和组识别号; 进程环境:一个进程的运行环境包括环境变量和启动该进程的程

12、序调用的命令行; 信号处理:一个进程有时需要用信号同其它进程进行通信。进程可以发送和接收信号,并对其作出相应处理; 资源安排:进程是调度系统资源的基本单位。当多个进程同时运行时,linux系统内核安排不同进程轮流使用系统的各种资源;,进程控制块 (3),同步处理:多个程序之间同步运行的实现,也是通过进程来完成的。这将会使用到诸如共享内存、文件锁定等方法。 进程状态:在一个进程存在期间,每一时刻进程都处在一定的状态,包括运行、等待被调度或睡眠状态。,进程结构体描述,进程的标识,进程的标识 :进程号、父进程号、父进程组号、会话号、会话首领号都是用于从不同的侧面来描述进程的身份 测试进程号的代码:/

13、*process.c*/ #include #include #include int main() /*获得当前进程的进程ID和其父进程ID*/ printf(“The PID of this process is %dn“,getpid(); printf(“The PPID of this process is %dn“,getppid(); return 1; ,进程状态,一个进程在其生存期内,可处于一组不同的状态下,称为进程状态 运行状态(TASK_RUNNING)当进程正在被CPU 执行,或已经准备就绪随时可由调度程序执行,则称该进程为处于运行状态(running)。进程可以在内核

14、态运行,也可以在用户态运行; 可中断睡眠状态(TASK_INTERRUPTIBLE)当进程处于可中断等待状态时,系统不会调度该进行执行。当系统产生一个中断或者释放了进程正在等待的资源,或者进程收到一个信号,都可以唤醒进程转换到就绪状态(运行状态);,不可中断睡眠状态(TASK_UNINTERRUPTIBLE)与可中断睡眠状态类似。但处于该状态的进程只有被使用wake_up()函数明确唤醒时才能转换到可运行的就绪状态。 暂停状态(TASK_STOPPED)当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN 或SIGTTOU 时就会进入暂停状态。可向其发送SIGCONT 信号让进程转换到

15、可运行状态。 僵死状态(TASK_ZOMBIE)当进程已停止运行,但其父进程还没有询问其状态时,则称该进程处于僵死状态。,Linux下进程的模式和类型,在 Linux 系统中,进程的执行模式划分为用户模式和内核模式。如果当前运行的是用户程序、应用程序或者内核之外的系统程序,那么对应进程就在用户模式下运行;如果在用户程序执行过程中出现系统调用或者发生中断事件,那么就要运行操作系统(即核心)程序,进程模式就变成内核模式。在内核模式下运行的进程可以执行机器的特权指令,而且此时该进程的运行不受用户的干扰, 即使是 root 用户也不能干扰内核模式下进程的运行。 用户进程既可以在用户模式下运行,也可以在

16、内核模式下运行,,3.2 Linux下的进程启动,Linux 下启动一个进程有两种主要途径:手工启动和调度启动。手动启动是由用户输入命令直接启动进程,而调度启动是指系统根据用户的设置自行启动进程。 (1)手工启动 ,又可分为前台启动和后台启动。 前台启动,如“ls -l”;后台启动往往是在该进程非常耗时,且用户也不急着需要结果的时候启动的,如“tftp gr tcpip.rar 192.168.0.2 &” (2)调度启动 ,系统需要进行一些比较费时而且占用资源的维护工作,如at和cron,3.3 进程控制编程,(1)fork() (2)exec函数族 (3)exit和_exit (4)wai

17、t和waitpid,(1)fork(),fork 函数用于从已存在进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。这两个分别带回它们各自的返回值,其中父进程的返回值是子进程的进程号,而子进程则返回0。因此,可以通过返回值来判定该进程是父进程还是子进程。 fork()函数头文件:#include格式: pid_t fork();返回值:0:子进程子进程ID(大于0的整数):父进程1:出错,子进程是父进程的拷贝,它具有和父进程相同的代码段,但是它具有自己的数据段和堆栈段。 子进程将从父进程获得绝大部分属性,但也会更改部分属性的值,要更改的属性为: 进程ID 进程组ID(更改为父进程ID

18、) SESSION ID(为子进程的运行时间记录) 所打开文件及文件的偏移量(父进程对文件的锁定),/*fork.c*/ #include #include #include #include int main(void) pid_t result; /*调用fork函数,其返回值为result*/ result = fork(); /*通过result的值来判断fork函数的返回情况,首先进行出错处理*/ if(result = -1) perror(“fork“); exit; /*返回值为0代表子进程*/ else if(result = 0) printf(“The return va

19、lue is %dnIn child process!nMy PID is%dn“,result,getpid(); /*返回值大于0代表父进程*/ else printf(“The return value is %dnIn father process!nMy PID is%dn“,result,getpid(); return 1; ,编译源文件fork.c并在板子上运行,先在上位机编译源文件: 进入/nfs/project目录下 rootlocalhost project# arm-linux-gcc -o fork fork.c 将可执行程序下载到目标板上,运行结果如下所示:,(2)

20、exec函数族,exec 函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是 Linux 下任何可执行的脚本文件。 在Linux中使用exec函数族主要有两种情况: (1)当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何 exec 函数族让自己重生; (2) 如果一个进程想执行另一个程序,那么它就可以调用 fork 函数新建一个进程,然后调用任何一个 exec,这样看

21、起来就好像通过执行应用程序而产生了一个新进程。 (这种情况非常普遍),exec函数族有下列6个函数:,#includeint execl(const char* path,const char* arg,.);int execlp(const char* file,const char* arg,.);int execle(const char* path,const char* arg,char * const envp);int execv(constchar* path,char* const argv);int execvp(const char* file,char* const a

22、rgv);int execve(const char* path, char* const argv,char * const envp);,Execlp实例,/*execlp.c*/ #include #include #include int main() if(fork()=0) /*调用execlp函数,这里相当于调用了“ps ”命令*/ if(execlp(“ps“,“ps“,NULL)0) perror(“execlp error!“); 将源码文件execlp.c保存到/nfs/project目录下并交叉编译: rootlocalhost project# arm-linux-g

23、cc -o execlp execlp.c,Execlp运行,在该程序中,首先使用fork函数新建一个子进程,然后在子进程里使用 execlp函数。读者可以看到,这里的参数列表就是在 shell中使用的命令名和选项。并且当使用文件名的方式进行查找时,系统会在默认的环境变量 PATH 中寻找该可执行文件。读者可将编译后的结果下载到目标板上,运行结果如下所示:,Execle实例,/*execle.c*/ #include #include #include int main() /*命令参数列表,必须以NULL结尾*/ char *envp=“PATH:=/tmp:$PATH“,NULL; if(

24、fork()=0) /*调用execle函数,注意这里也要指出env的完整路径*/ if(execle(“/usr/bin/env“,“env“,NULL,envp)0) perror(“execle error!“); return 1; 将源码文件execlp.c保存到/nfs/project目录下并交叉编译: rootlocalhost project# arm-linux-gcc -o execle execle.c,Execle运行,exec函数族使用注意点,在使用 exec 函数族时,一定要加上错误判断语句。因为 exec 很容易执行失败,其中最常见的原因有: 找不到文件或路径,此

25、时errno被设置为 ENOENT; 数组argv和envp忘记用NULL结束,此时 errno被设置为 EFAULT; 没有对应可执行文件的运行权限,此时errno被设置为 EACCES。,(3)exit和_exit,终止进程函数,形式一: #include void exit(int status); 说明:exit()函数是标准C中提供的函数。它用来终止正在运行的程序,将关闭所有被该文件打开的文件描述符 形式二: #include void _exit(intstatus) 说明:_exit()函数也可用于结束一个进程,与exit函数不同的是,调用_exit()是为了关闭一些linux特

26、有的退出句柄。,终止进程函数,形式三: #include int atexit(void(*function)(void); 说明:atexit函数在结束一个程序后,调用一个不带参数同时也没有返回值的函数。该函数名由function指针指向 形式四: #include void abort(void); 说明:abort()函数用来发送一个SIGABRT信号,这个信号将使当前进程终止。 形式五: #include void assert(intexpression);说明: assert是一个宏。调用assert函数时,首先要计算表达式expression的值,如果为0,则调用abort()函

27、数结束进程。,Exit实例,/*exit.c*/ #include #include #include int main(void) pid_t pid;if(pid=fork()0)exit(0);else if(pid=0) printf(“go into child process!n“);printf(“child process PID:%d“,getpid();exit(0);elseprintf(“go into parent process!n“);printf(“Parent process PID:%4d“,getpid();_exit(0);return 0; ,Exit

28、运行,将源码文件execlp.c保存到/nfs/project目录下并交叉编译: rootlocalhost project# arm-linux-gcc -o execle execle.c 将板子启动Linux,并挂载网络文件系统,然后进入/project目录下面,运行exit实例:,(4)wait和waitpid,wait函数是用于使父进程(也就是调用wait的进程)阻塞,直到一个子进程结束或者该进程接到了一个指定的信号为止。 如果该父进程没有子进程或者他的子进程已经结束,则wait就会立即返回。 waitpid的作用和wait一样, 但它并不一定要等待第一个终止的子进程, 它还有若干选

29、项,如可提供一个非阻塞版本的wait功能,也能支持作业控制。实际上 wait函数只是 waitpid函数的一个特例,在Linux内部实现wait函数时直接调用的就是 waitpid函数。,wait函数调用形式: #include #include pid_t wait(int *status) 这里的status是一个整型指针,是该子进程退出时的状态 status若为空,则代表任意状态结束的子进程 status若不为空,则代表指定状态结束的子进程 另外,子进程的结束状态可由Linux中一些特定的宏来测定 成功的话返回子进程的进程号 ,失败则返回-1,waitpid函数调用形式: #includ

30、e #include pid_t waitpid(pid_tpid,int* stat_loc,int options);作用:waitpid等待指定的子进程直到子进程返回,如果pid为正值则等待指定的进程(pid);如果为0则等待任何一个组ID和调用者的组ID相同的进程;为-1时等同于wait调用;小于-1时等待任何一个组ID等于pid绝对值的进程。 参数含义:stat_loc和wait的意义一样; options可以决定父进程的状态,可以取下面两个值: WNOHANG:当没有子进程存在时,父进程立即返回; WUNTACHED:当子进程结束时waitpid返回,但是子进程的退出状态不可得到.

31、,/*waitpid.c*/ #include #include #include #include #include int main() pid_t pc,pr; pc=fork(); if(pc0) printf(“Error fork.n“); /*子进程*/ else if(pc=0) /*子进程暂停5s*/ sleep(5); /*子进程正常退出*/ exit(0); /*父进程*/ else /*循环测试子进程是否退出*/ do /*调用waitpid,且父进程不阻塞*/ pr=waitpid(pc,NULL,WNOHANG); /pr=wait(NULL); /*若子进程还未退

32、出,则父进程暂停1s*/ if(pr=0) printf(“The child process has not exitedn“); sleep(1); while(pr=0); /*若发现子进程退出,打印出相应情况*/ if(pr=pc) printf(“Get child %dn“,pr); else printf(“some error occured.n“); ,Waitpid运行,将源码文件execlp.c保存到/nfs/project目录下并交叉编译: rootlocalhost project# arm-linux-gcc -o execle execle.c 将板子启动Linu

33、x,并挂载网络文件系统,然后进入/project目录下面,运行exit实例:,3.4 守护进程编程,守护进程,也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的。,创建守护进程的步骤,创建子进程, 父进程退出,创建新会话,改变当前目录 为根目录,重设文件 权限掩码,关闭文件描述符,这是编写守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在 She

34、ll 终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离。 /*父进程退出*/ pid=fork(); if(pid0) exit(0); ,(1)创建子进程,父进程退出,(2)在子进程中创建新会话 (setsid函数),这个步骤是创建守护进程中最重要的一步进程组 :进程组是一个或多个进程的集合。进程组由进程组 ID 来惟一标识。除了进程号(PID)之外,进程组ID 也一个进程的必备属性。 每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID。且该进程 ID 不会因组长进程的退出而受到影

35、响。 会话期 :会话组是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。 setsid函数作用 setsid函数用于创建一个新的会话,并担任该会话组的组长。调用setsid 有下面的 3 个 作用: 让进程摆脱原会话的控制。 让进程摆脱原进程组的控制。 让进程摆脱原控制终端的控制。,(3)改变当前目录为根目录,这一步也是必要的步骤。使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统(比如“/mnt/usb”等)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入

36、单用户模式) 。因此,通常的做法是让“/”作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是 chdir。,文件权限掩码是指屏蔽掉文件权限中的对应位。由于使用 fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为 0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask。在这里,通常的使用方法为umask(0)。,(4)重设文件权限掩码,(5)关闭文件描述符,同文件权限掩码一样,用fork函数新建的子进程会从父进程那里

37、继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。for(i=0;iMAXFILE;i+) close(i);,/*dameon.c创建守护进程实例*/ #include #include #include #include #include #include #include #define MAXFILE 65535 int main() pid_t pc; int i,fd,len; char *buf=“This is a Dameonn“; len =strlen(buf); pc=fork(); /第

38、一步 if(pc0) exit(0); /*第二步*/ setsid(); /*第三步*/ chdir(“/“);,/*第四步*/ umask(0); for(i=0;iMAXFILE;i+) /*第五步*/ close(i); /*这时创建完守护进程,以下开始正式进入守护进程工作*/ while(1) if(fd=open(“/tmp/dameon.log“,O_CREAT|O_WRONLY|O_APPEND,0600)0) perror(“open“); exit(1); write(fd, buf, len+1); close(fd); sleep(10); ,守护进程运行,3.5 进程

39、间通信,(1)管道(Pipe)及有名管道(named pipe) :管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。 (2)信号(Signal) :信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。 (3)消息队列:消息队列是消息的链接表,包括 Posix 消息队列 systemV 消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列

40、中读取消息。 (4)共享内存:可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。 (5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。 (6)套接字(Socket) :这是一种更为一般的进程间通信机制,它可用于不同机器之间的进程间通信,应用非常广泛。,(1)管道通信,ps,内核,管道,grep init,管道是 Linux 中进程间通信的一种方式。这里所说的管道主要指无名管道,它具有如下特点。 它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄

41、弟进程之间) 。 它是一个半双工的通信模式,具有固定的读端和写端。 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。,创建管道,调用形式: #include int pipe(int fd2) 该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信管道的读写规则 : 管道两端可分别用描述字fd0以及fd1来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描

42、述字fd0表示,称其为管道读端;另一端则只能用于写,由描述字fd1来表示,称其为管道写端。 如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。 一般文件的I/O函数都可以用于管道,如close、read、write等等。,管道读写说明,/*pipe_rw.c*/ #include #include #include #include #include int main() int pipe_fd2; pid_t pid; char buf_r100; char* p_wbuf; int r_num; memset(buf_r,0,sizeof(buf_r); /*创建管道*/

43、 if(pipe(pipe_fd)0) printf(“pipe create errorn“); return -1; /*创建一子进程*/,管道读写实例,if(pid=fork()=0) printf(“n“); /*关闭子进程写描述符,并通过使父进程暂停2秒确保父进程已关闭相应的读描述符*/ close(pipe_fd1); sleep(2); /*子进程读取管道内容*/ if(r_num=read(pipe_fd0,buf_r,100)0) printf(“%d numbers read from the pipe is %sn“,r_num,buf_r); /*关闭子进程读描述符*/

44、 close(pipe_fd0); exit(0); else if(pid0) /*/关闭父进程读描述符,并分两次向管道中写入Hello Pipe*/ close(pipe_fd0); if(write(pipe_fd1,“Hello“,5)!= -1) printf(“parent write1 success!n“); if(write(pipe_fd1,“ Pipe“,5)!= -1) printf(“parent write2 success!n“); /*关闭父进程写描述符*/ close(pipe_fd1); sleep(3); /*收集子进程退出信息*/ waitpid(pid

45、,NULL,0); exit(0); ,管道的局限性,管道的主要局限性正体现在它的特点上: 只支持单向数据流; 只能用于具有亲缘关系的进程之间; 没有名字; 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小); 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等,FIFO,管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(namedpipe或FIFO)提出后,该限制得到了克服。 FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形

46、式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是, FIFO 严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。,系统调用形式: #include #include int mkfifo(const char*pathname,mode_t mode) 该函数的第一个参数是一个普通的路径名,也就是创

47、建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。 一般文件的I/O函数都可以用于FIFO,如close、read、write等等。,O_RDONLY:读管道 O_WRONLY:写管道 O_RDWR:读写管道 O_NONBLOCK:非阻塞 O_CREAT: O_EXCL:,FIFO读规则,约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:A.当前FIFO内有数据,但有其它进程在读这些数据;B.另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。 如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。,

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 学术论文 > 大学论文

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报