Linux下的C编程实战-.pdf

上传人:索**** 文档编号:76192036 上传时间:2023-03-08 格式:PDF 页数:50 大小:69.19KB
返回 下载 相关 举报
Linux下的C编程实战-.pdf_第1页
第1页 / 共50页
Linux下的C编程实战-.pdf_第2页
第2页 / 共50页
点击查看更多>>
资源描述

《Linux下的C编程实战-.pdf》由会员分享,可在线阅读,更多相关《Linux下的C编程实战-.pdf(50页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、Linux下的 C编程实战Linux下的 C 编程实战(一)开发平台搭建1.引言Linux操作系统在服务器领域的应用和普及已经有较长的历史,这源于它的开源特点以及其超越Windows的安全性和稳定性。而近年来,Linux操作系统在嵌入式系统领域的延伸也可谓是如日中天,许多版本的嵌入式Linux系统被开发出来,如ucLinux、RTLinux、ARM-Linux等等。在嵌入式操作系统方面,Linux的地位是不容怀疑的,它开源、它包含TCP/IP 协议栈、它易集成 GUI。鉴于 Linux操作系统在服务器和嵌入式系统领域愈来愈广泛的应用,社会上越来越需要基于 Linux操作系统进行编程的开发人员。

2、浏览许多论坛,经常碰到这样的提问:“现在是不是很流行unix/linux下的 c 编程?所以想学习一下!但是不知道该从何学起,如何下手!有什么好的建议吗?各位高手!哪些书籍比较合适初学者?在深入浅出的过程中应该看哪些不同层次的书?比如好的网站、论坛请大家赐教!不慎感激!”鉴于读者的需求,在本文中,笔者将对Linux平台下 C 编程的几个方面进行实例讲解,并力求回答读者们关心的问题,以与读者朋友们进行交流,共同提高。在本文的连载过程中,有任何问题或建议,您可以给笔者发送email:,您也可以进入笔者的博客参与讨论:http:/ 内存足够大的情况下,不要直接安装Linux操作系统,最好把它安装在运

3、行VMWare虚拟机软件的Windows平台上,如下图:在 Linux平台下,可用任意一个文本编辑工具编辑源代码,但笔者建议使用emacs软件,它具备语法高亮、版本控制等附带功能,如下图:2.GCC编译器GCC 是 Linux平台下最重要的开发工具,它是GNU 的 C 和 C+编译器,其基本用法为:gcc options filenames options为编译选项,GCC 总共提供的编译选项超过100 个,但只有少数几个会被频繁使用,我们仅对几个常用选项进行介绍。假设我们编译一输出“Hello World”的程序:/*Filename:helloworld.c*/main()printf(H

4、ello Worldn);最简单的编译方法是不指定任何编译选项:gcc helloworld.c 它会为目标程序生成默认的文件名a.out,我们可用-o 编译选项来为将产生的可执行文件指定一个文件名来代替a.out。例如,将上述名为helloworld.c的 C 程序编译为名叫helloworld的可执行文件,需要输入如下命令:gcc o helloworld helloworld.c-c 选项告诉GCC 仅把源代码编译为目标代码而跳过汇编和连接的步骤;-S 编译选项告诉GCC 在为 C 代码产生了汇编语言文件后停止编译。GCC 产生的汇编语言文件的缺省扩展名是.s,上述程序运行如下命令:gc

5、c S helloworld.c 将生成 helloworld.c的汇编代码,使用的是AT&T 汇编。用 emacs打开汇编代码如下图:-E 选项指示编译器仅对输入文件进行预处理。当这个选项被使用时,预处理器的输出被送到标准输出(默认为屏幕)而不是储存在文件里。-O 选项告诉GCC 对源代码进行基本优化从而使得程序执行地更快;而-O2 选项告诉GCC产生尽可能小和尽可能快的代码。使用-O2 选项编译的速度比使用-O 时慢,但产生的代码执行速度会更快。-g 选项告诉GCC 产生能被 GNU 调试器使用的调试信息以便调试你的程序,可喜的是,在GCC 里,我们能联用-g 和-O(产生优化代码)。-p

6、g选项告诉GCC 在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示你的程序的耗时情况。3.GDB调试器GCC 用于编译程序,而Linux的另一个GNU 工具 gdb 则用于调试程序。gdb 是一个用来调试C 和 C+程序的强力调试器,我们能通过它进行一系列调试工作,包括设置断点、观查变量、单步等。其最常用的命令如下:file:装入想要调试的可执行文件。kill:终止正在调试的程序。list:列表显示源代码。next:执行一行源代码但不进入函数内部。step:执行一行源代码而且进入函数内部。run:执行当前被调试的程序quit:终止 gdb watch:监视一个变量的值bre

7、ak:在代码里设置断点,程序执行到这里时挂起make:不退出gdb而重新产生可执行文件shell:不离开gdb 而执行 shell 下面我们来演示怎样用GDB 来调试一个求0+1+2+3+,+99的程序:/*Filename:sum.c*/main()int i,sum;sum=0;for(i=0;i 100;i+)sum+=i;printf(the sum of 1+2+.+is%d,sum);执行如下命令编译sum.c(加-g 选项产生debug信息):gcc g o sum sum.c 在命令行上键入gdb sum并按回车键就可以开始调试sum 了,再运行 run 命令执行 sum,屏幕

8、上将看到如下内容:list 命令:list 命令用于列出源代码,对上述程序两次运行list,将出现如下画面(源代码被标行号):根据列出的源程序,如果我们将断点设置在第5 行,只需在 gdb 命令行提示符下键入如下命令设置断点:(gdb)break 5,执行情况如下图:这个时候我们再run,程序会停止在第5 行,如下图:设置断点的另一种语法是break,它在进入指定函数(function)时停住。相反的,clear 用于清除所有的已定义的断点,clear 清除设置在函数上的断点,clear 则清除设置在指定行上的断点。watch命令:watch命令用于观查变量或表达式的值,我们观查sum变量只需

9、要运行watch sum:watch 为表达式(变量)expr设置一个观察点,一量表达式值有变化时,程序会停止执行。要观查当前设置的watch,可以使用info watchpoints命令。next、step命令:next、step用于单步执行,在执行的过程中,被watch变量的变化情况将实时呈现(分别显示 Old value和 New value),如下图:next、step命令的区别在于step遇到函数调用,会跳转到到该函数定义的开始行去执行,而 next则不进入到函数内部,它把函数调用语句当作一条普通语句执行。4.Make make是所有想在Linux系统上编程的用户必须掌握的工具,对于

10、任何稍具规模的程序,我们都会使用到make,几乎可以说不使用make的程序不具备任何实用价值。在此,我们有必要解释编译和连接的区别。编译器使用源码文件来产生某种形式的目标文件(object files),在编译过程中,外部的符号参考并没有被解释或替换(即外部全局变量和函数并没有被找到)。因此,在编译阶段所报的错误一般都是语法错误。而连接器则用于连接目标文件和程序包,生成一个可执行程序。在连接阶段,一个目标文件中对别的文件中的符号的参考被解释,如果有符号不能找到,会报告连接错误。编译和连接的一般步骤是:第一阶段把源文件一个一个的编译成目标文件,第二阶段把所有的目标文件加上需要的程序包连接成一个可

11、执行文件。这样的过程很痛苦,我们需要使用大量的gcc 命令。而 make则使我们从大量源文件的编译和连接工作中解放出来,综合为一步完成。GNU Make的主要工作是读进一个文本文件,称为makefile。这个文件记录了哪些文件(目的文件,目的文件不一定是最后的可执行程序,它可以是任何一种文件)由哪些文件(依靠文件)产生,用什么命令来产生。Make 依靠此 makefile中的信息检查磁盘上的文件,如果目的文件的创建或修改时间比它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。假设我们写下如下的三个文件,add.h用于声明add 函数,add.c提供两个整数相加的函数体,而ma

12、in.c中调用 add 函数:/*filename:add.h*/extern int add(int i,int j);/*filename:add.c*/int add(int i,int j)return i+j;/*filename:main.c*/#include add.h main()int a,b;a=2;b=3;printf(the sum of a+b is%d,add(a+b);怎样为上述三个文件产生makefile呢?如下:-test:main.o add.o gcc main.o add.o-o test main.o:main.c add.h gcc-c main.

13、c-o main.o add.o:add.c add.h gcc-c add.c-o add.o -(注意分割符为TAB 键)上述 makefile利用 add.c和 add.h文件执行 gcc-c add.c-o add.o命令产生add.o目标代码,利用main.c和 add.h文件执行gcc-c main.c-o main.o命令产生main.o目标代码,最后利用main.o和 add.o文件(两个模块的目标代码)执行gcc main.o add.o-o test命令产生可执行文件test。我们可在makefile中加入变量,另外。环境变量在make过程中也被解释成make的变量。这些变

14、量是大小写敏感的,一般使用大写字母。Make 变量可以做很多事情,例如:i)存储一个文件名列表;ii)存储可执行文件名;iii)存储编译器选项。要定义一个变量,只需要在一行的开始写下这个变量的名字,后面跟一个=号,再跟变量的值。引用变量的方法是写一个$符号,后面跟(变量名)。我们把前面的makefile 利用变量重写一遍(并假设使用-Wall-O g 编译选项):OBJS=main.o add.o CC=gcc CFLAGS=-Wall-O-g test:$(OBJS)$(CC)$(OBJS)-o test main.o:main.c add.h$(CC)$(CFLAGS)-c main.c-

15、o main.o add.o:add.c add.h$(CC)$(CFLAGS)-c add.c-o add.o makefile 中还可定义清除(clean)目标,可用来清除编译过程中产生的中间文件,例如在上述 makefile文件中添加下列代码:clean:rm-f*.o 运行 make clean时,将执行rm-f*.o命令,删除所有编译过程中产生的中间文件。不管怎么说,自己动手编写makefile仍然是很复杂和烦琐的,而且很容易出错。因此,GNU也为我们提供了Automake和 Autoconf来辅助快速自动产生 makefile,读者可以参阅相关资料。5.小结本章主要阐述了Linux

16、程序的编写、编译、调试方法及make,实际上就是引导读者学习怎样在 Linux下编程,为后续章节做好准备。Linux下的 C 编程实战(二)文件系统编程1.Linux文件系统Linux支持多种文件系统,如ext、ext2、minix、iso9660、msdos、fat、vfat、nfs 等。在这些具体文件系统的上层,Linux提供了虚拟文件系统(VFS)来统一它们的行为,虚拟文件系统为不同的文件系统与内核的通信提供了一致的接口。下图给出了Linux中文件系统的关系:在 Linux平台下对文件编程可以使用两类函数:(1)Linux操作系统文件API;(2)C 语言 I/O 库函数。前者依赖于Li

17、nux系统调用,后者实际上与操作系统是独立的,因为在任何操作系统下,使用C 语言 I/O库函数操作文件的方法都是相同的。本章将对这两种方法进行实例讲解。2.Linux文件 API Linux的文件操作API 涉及到创建、打开、读写和关闭文件。创建int creat(const char*filename,mode_t mode);参数 mode指定新建文件的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:int umask(int newmask);该调用将umask设

18、置为 newmask,然后返回旧的umask,它只影响读、写和执行权限。打开int open(const char*pathname,int flags);int open(const char*pathname,int flags,mode_t mode);open函数有两个形式,其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面),flags可以去下面的一个值或者是几个值的组合:标志含义O_RDONLY 以只读的方式打开文件O_WRONLY 以只写的方式打开文件O_RDWR 以读写的方式打开文件O_APPEND 以追加的方式打开文件O_CREAT 创建一个文件

19、O_EXEC 如果使用了O_CREAT而且文件已经存在,就会发生一个错误O_NOBLOCK 以非阻塞的方式打开一个文件O_TRUNC 如果文件已经存在,则删除文件的内容O_RDONLY、O_WRONLY、O_RDWR三个标志只能使用任意的一个。如果使用了O_CREATE标志,则使用的函数是int open(const char*pathname,int flags,mode_t mode);这个时候我们还要指定mode标志,用来表示文件的访问权限。mode可以是以下情况的组合:标志含义S_IRUSR 用户可以读S_IWUSR 用户可以写S_IXUSR 用户可以执行S_IRWXU 用户可以读、写

20、、执行S_IRGRP 组可以读S_IWGRP 组可以写S_IXGRP 组可以执行S_IRWXG 组可以读写执行S_IROTH 其他人可以读S_IWOTH 其他人可以写S_IXOTH 其他人可以执行S_IRWXO 其他人可以读、写、执行S_ISUID 设置用户执行ID S_ISGID 设置组的执行ID 除了可以通过上述宏进行“或”逻辑产生标志以外,我们也可以自己用数字来表示,Linux总共用 5 个数字来表示文件的各种权限:第一位表示设置用户ID;第二位表示设置组ID;第三位表示用户自己的权限位;第四位表示组的权限;最后一位表示其他人的权限。每个数字可以取1(执行权限)、2(写权限)、4(读权限

21、)、0(无)或者是这些值的和。例如,要创建一个用户可读、可写、可执行,但是组没有权限,其他人可以读、可以执行的文件,并设置用户ID 位。那么,我们应该使用的模式是1(设置用户ID)、0(不设置组 ID)、7(1+2+4,读、写、执行)、0(没有权限)、5(1+4,读、执行)即 10705:open(test,O_CREAT,10705);上述语句等价于:open(test,O_CREAT,S_IRWXU|S_IROTH|S_IXOTH|S_ISUID);如果文件打开成功,open函数会返回一个文件描述符,以后对该文件的所有操作就可以通过对这个文件描述符进行操作来实现。读写在文件打开以后,我们才

22、可对文件进行读写了,Linux中提供文件读写的系统调用是read、write函数:int read(int fd,const void*buf,size_t length);int write(int fd,const void*buf,size_t length);其中参数buf为指向缓冲区的指针,length为缓冲区的大小(以字节为单位)。函数 read()实现从文件描述符fd 所指定的文件中读取length个字节到 buf 所指向的缓冲区中,返回值为实际读取的字节数。函数 write实现将把length个字节从 buf指向的缓冲区中写到文件描述符fd 所指向的文件中,返回值为实际写入的字

23、节数。以 O_CREAT为标志的 open 实际上实现了文件创建的功能,因此,下面的函数等同creat()函数:int open(pathname,O_CREAT|O_WRONLY|O_TRUNC,mode);定位对于随机文件,我们可以随机的指定位置读写,使用如下函数进行定位:int lseek(int fd,offset_t offset,int whence);lseek()将文件读写指针相对whence移动 offset个字节。操作成功时,返回文件指针相对于文件头的位置。参数whence可使用下述值:SEEK_SET:相对文件开头SEEK_CUR:相对文件读写指针的当前位置SEEK_EN

24、D:相对文件末尾offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5 个字节:lseek(fd,-5,SEEK_CUR);由于 lseek 函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文件的长度:lseek(fd,0,SEEK_END);关闭当我们操作完成以后,我们要关闭文件了,只要调用close 就可以了,其中fd 是我们要关闭的文件描述符:int close(int fd);例程:编写一个程序,在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello,software weekly”,关闭该文件。再次打开该文件,读取其中的内容并输出

25、在屏幕上。#include#include#include#include#define LENGTH 100 main()int fd,len;char strLENGTH;fd=open(hello.txt,O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);/*创建并打开文件*/if(fd)write(fd,Hello,Software Weekly,strlen(Hello,software weekly);/*写入Hello,software weekly字符串*/close(fd);fd=open(hello.txt,O_RDWR);len=read(fd,str,LE

26、NGTH);/*读取文件内容*/strlen=0;printf(%sn,str);close(fd);编译并运行,执行3.C 语言库函数C 库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOS、Windows、Linux还是在 VxWorks中都是这些函数:创建和打开FILE*fopen(const char*path,const char*mode);fopen()实现打开指定文件filename,其中的mode为打开模式,C 语言中支持的打开模式如下表:标志含义r,rb 以只读方式打开w,wb 以只写方式打开。如果文件不存在,则创建该文件,否则文件被截断a,ab 以追加方式打

27、开。如果文件不存在,则创建该文件r+,r+b,rb+以读写方式打开w+,w+b,wh+以读写方式打开。如果文件不存在时,创建新文件,否则文件被截断a+,a+b,ab+以读和追加方式打开。如果文件不存在,创建新文件其中 b 用于区分二进制文件和文本文件,这一点在DOS、Windows系统中是有区分的,但 Linux不区分二进制文件和文本文件。读写C 库函数支持以字符、字符串等为单位,支持按照某中格式进行文件的读写,这一组函数为:int fgetc(FILE*stream);int fputc(int c,FILE*stream);char*fgets(char*s,int n,FILE*stre

28、am);int fputs(const char*s,FILE*stream);int fprintf(FILE*stream,const char*format,.);int fscanf(FILE*stream,const char*format,.);size_t fread(void*ptr,size_t size,size_t n,FILE*stream);size_t fwrite(const void*ptr,size_t size,size_t n,FILE*stream);fread()实现从流stream中读取加n 个字段,每个字段为size 字节,并将读取的字段放入 pt

29、r 所指的字符数组中,返回实际已读取的字段数。在读取的字段数小于num时,可能是在函数调用时出现错误,也可能是读到文件的结尾。所以要通过调用feof()和 ferror()来判断。write()实现从缓冲区ptr所指的数组中把n 个字段写到流stream中,每个字段长为size个字节,返回实际写入的字段数。另外,C 库函数还提供了读写过程中的定位能力,这些函数包括int fgetpos(FILE*stream,fpos_t*pos);int fsetpos(FILE*stream,const fpos_t*pos);int fseek(FILE*stream,long offset,int w

30、hence);等。关闭利用 C 库函数关闭文件依然是很简单的操作:int fclose(FILE*stream);例程:将第2 节中的例程用C 库函数来实现。#include#define LENGTH 100 main()FILE*fd;char strLENGTH;fd=fopen(hello.txt,w+);/*创建并打开文件*/if(fd)fputs(Hello,Software Weekly,fd);/*写入 Hello,software weekly字符串*/fclose(fd);fd=fopen(hello.txt,r);fgets(str,LENGTH,fd);/*读取文件内容

31、*/printf(%sn,str);fclose(fd);4.小结Linux提供的虚拟文件系统为多种文件系统提供了统一的接口,Linux的文件编程有两种途径:基于Linux系统调用;基于C 库函数。这两种编程所涉及到文件操作有新建、打开、读写和关闭,对随机文件还可以定位。本章对这两种编程方法都给出了具体的实例。Linux下的 C 编程实战(三)进程控制与进程通信编程1.Linux进程Linux进程在内存中包含三部分数据:代码段、堆栈段和数据段。代码段存放了程序的代码。代码段可以为机器中运行同一程序的数个进程共享。堆栈段存放的是子程序(函数)的返回地址、子程序的参数及程序的局部变量。而数据段则存

32、放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)。与代码段不同,如果系统中同时运行多个相同的程序,它们不能使用同一堆栈段和数据段。Linux进程主要有如下几种状态:用户状态(进程在用户状态下运行的状态)、内核状态(进程在内核状态下运行的状态)、内存中就绪(进程没有执行,但处于就绪状态,只要内核调度它,就可以执行)、内存中睡眠(进程正在睡眠并且处于内存中,没有被交换到SWAP 设备)、就绪且换出(进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行)、睡眠且换出(进程正在睡眠,且被换出内存)、被抢先(进程从内核状态返回用户状态时,内核抢先于它,做

33、了上下文切换,调度了另一个进程,原先这个进程就处于被抢先状态)、创建状态(进程刚被创建,该进程存在,但既不是就绪状态,也不是睡眠状态,这个状态是除了进程0以外的所有进程的最初状态)、僵死状态(进程调用exit结束,进程不再存在,但在进程表项中仍有记录,该记录可由父进程收集)。下面我们来以一个进程从创建到消亡的过程讲解Linux进程状态转换的“生死因果”。(1)进程被父进程通过系统调用fork创建而处于创建态;(2)fork调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程进入就绪态(或者在内存中就绪,或者因为内存不够而在SWAP 设备中就绪);(3)若进程在内存中就绪,进程可以被内核

34、调度程序调度到CPU 运行;(4)内核调度该进程进入内核状态,再由内核状态返回用户状态执行。该进程在用户状态运行一定时间后,又会被调度程序所调度而进入内核状态,由此转入就绪态。有时进程在用户状态运行时,也会因为需要内核服务,使用系统调用而进入内核状态,服务完毕,会由内核状态转回用户状态。要注意的是,进程在从内核状态向用户状态返回时可能被抢占,这是由于有优先级更高的进程急需使用CPU,不能等到下一次调度时机,从而造成抢占;(5)进程执行exit调用,进入僵死状态,最终结束。2.进程控制进程控制中主要涉及到进程的创建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的进程创建方

35、法,sleep的进程睡眠和exit的进程退出调用,另外Linux还提供了父进程等待子进程结束的系统调用wait。fork 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一,它执行一次却返回两个值,完全“不可思议”。先看下面的程序:int main()int i;if(fork()=0)for(i=1;i 3;i+)printf(This is child processn);else for(i=1;i 3;i+)printf(This is parent processn);执行结果为:This is child process This is child p

36、rocess This is parent process This is parent process fork在英文中是“分叉”的意思,这个名字取得很形象。一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了。当前进程为父进程,通过fork()会产生一个子进程。对于父进程,fork函数返回子程序的进程号而对于子程序,fork函数则返回零,这就是一个函数返回两次的本质。可以说,fork函数是 Unix系统最杰出的成就之一,它是七十年代 Unix早期的开发者经过理论和实践上的长期艰苦探索后取得的成果。如果我们把上述程序中的循环放的大一点:int main()int i

37、;if(fork()=0)for(i=1;i 10000;i+)printf(This is child processn);else for(i=1;i 10000;i+)printf(This is parent processn);则可以明显地看到父进程和子进程的并发执行,交替地输出“This is child process”和“This is parent process”。此时此刻,我们还没有完全理解fork()函数,再来看下面的一段程序,看看究竟会产生多少个进程,程序的输出是什么?int main()int i;for(i=0;i);fgets(command,MAX_CMD_L

38、EN,stdin);commandstrlen(command)-1=0;if(fork()=0)/*子进程执行此命令*/execlp(command,command);/*如果 exec 函数返回,表明没有正常执行命令,打印错误信息*/perror(command);exit(errorno);else /*父进程,等待子进程结束,并打印子进程的返回值*/wait(&rtn);printf(child process return%dn,rtn);这个函数基本上实现了一个shell的功能,它读取用户输入的进程名和参数,并启动对应的进程。clone clone是 Linux2.0以后才具备的新

39、功能,它较 fork更强(可认为fork 是 clone要实现的一部分),可以使得创建的子进程共享父进程的资源,并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。clone函数的原型为:int clone(int(*fn)(void*),void*child_stack,int flags,void*arg);此函数返回创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项,具体含义如下表:标志含义CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”CLONE_FS 子进程与父进程共

40、享相同的文件系统,包括root、当前目录、umask CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表CLONE_NEWNS 在新的 namespace启动子进程,namespace描述了进程的文件hierarchy CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表CLONE_PTRACE 若父进程被trace,子进程也被trace CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源CLONE_VM 子进程与父进程运行于相同的内存空间CLONE_PID 子进程在创建时PID 与父进程一致C

41、LONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群来看下面的例子:int variable,fd;int do_something()variable=42;close(fd);_exit(0);int main(int argc,char*argv)void*child_stack;char tempch;variable=9;fd=open(test.file,O_RDONLY);child_stack=(void*)malloc(16384);printf(The variable was%dn,variable);clone(do_

42、something,child_stack,CLONE_VM|CLONE_FILES,NULL);sleep(1);/*延时以便子进程完成关闭文件操作、修改变量*/printf(The variable is now%dn,variable);if(read(fd,&tempch,1)0)printf(received from pipe:%sn,buf);fclose(in_file);/*进程二:写有名管道*/void main()FILE*out_file;int count=1;char bufBUFFER_LEN;out_file=fopen(pipeexample,w);if(ou

43、t_file=NULL)printf(Error opening pipe.);exit(1);sprintf(buf,this is test data for the named pipe examplen);fwrite(buf,1,BUFFER_LEN,out_file);fclose(out_file);消息队列用于运行于同一台机器上的进程间通信,与管道相似;共享内存通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过s

44、hmXXX函数族来实现共享内存:int shmget(key_t key,int size,int flag);/*获得一个共享存储标识符*/该函数使得系统分配size 大小的内存用作共享内存;void*shmat(int shmid,void*addr,int flag);/*将共享内存连接到自身地址空间中*/shmid为 shmget函数返回的共享存储标识符,addr 和 flag 参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址。此后,进程可以对此地址进行读写操作访问共享内存。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般

45、说来,为了获得共享资源,进程需要执行下列操作:(1)测试控制该资源的信号量;(2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减1;(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。下面是一个使用信号量的例子,该程序创建一个特定的IPC 结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后清除信号量:#include#include#include#include void main()key_t

46、 unique_key;/*定义一个IPC 关键字*/int id;struct sembuf lock_it;union semun options;int i;unique_key=ftok(.,a);/*生成关键字,字符a 是一个随机种子*/*创建一个新的信号量集合*/id=semget(unique_key,1,IPC_CREAT|IPC_EXCL|0666);printf(semaphore id=%dn,id);options.val=1;/*设置变量值*/semctl(id,0,SETVAL,options);/*设置索引0 的信号量*/*打印出信号量的值*/i=semctl(i

47、d,0,GETVAL,0);printf(value of semaphore at index 0 is%dn,i);/*下面重新设置信号量*/lock_it.sem_num=0;/*设置哪个信号量*/lock_it.sem_op=-1;/*定义操作*/lock_it.sem_flg=IPC_NOWAIT;/*操作方式*/if(semop(id,&lock_it,1)=-1)printf(can not lock semaphore.n);exit(1);i=semctl(id,0,GETVAL,0);printf(value of semaphore at index 0 is%dn,i)

48、;/*清除信号量*/semctl(id,0,IPC_RMID,0);套接字通信并不为Linux所专有,在所有提供了TCP/IP 协议栈的操作系统中几乎都提供了 socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的。4.小节本章讲述了Linux进程的概念,并以多个实例讲解了进程控制及进程间通信方法,理解这一章的内容可以说是理解Linux这个操作系统的关键。Linux下的 C 编程实战(四)“线程”控制与“线程”通信编程1.Linux“线程”笔者曾经在基于嵌入式操作系统VxWorks的多任务并发程序设计(软件报2006年第 512期)中详细叙述了进程和线程的区别,并曾经说明Linu

49、x是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。Linux中所谓的“线程”只是在被创建的时候“克隆”(clone)了父进程的资源,因此,clone出来的进程表现为“线程”,这一点一定要弄清楚。因此,Linux“线程”这个概念只有在打冒号的情况下才是最准确的,可惜的是几乎没有书籍留心去强调这一点。Linux内核只提供了轻量进程的支持,未实现线程模型,但Linux尽最大努力优化了进程的调度开销,这在一定程度上弥补无线程的缺陷。Linux用一

50、个核心进程(轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成。目前 Linux中最流行的线程机制为LinuxThreads,所采用的就是线程进程“一对一”模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。LinuxThreads由 Xavier Leroy(Xavier.Leroyinria.fr)负责开发完成,并已绑定在GLIBC中发行,它实现了一种BiCapitalized面向 Linux的 Posix 1003.1c“pthread”标准接口。Linuxthread可以支持 Intel、Alpha、MIPS 等平台上的多处理器系统。按照 POSIX 10

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 技术资料 > 实施方案

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

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