《Linux下的C编程实战.docx》由会员分享,可在线阅读,更多相关《Linux下的C编程实战.docx(49页珍藏版)》请在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: 21cnbao21 ,您也可以进入笔者的博客参与讨论:笔者建议在PC内存足够大的情况下,不要直接安装Linux操作系统
3、,最好把它安装在运行 VMWare虚拟机软件的Windows平台上,如下图:在Linux平台下,可用任意个文本编辑工具编辑源代码,但笔者建议使用emacs 软件,它具备语法高亮、版本控制等附带功能,如下图2 .GCC编译器GCC是Linux平台下最重要的开发工具,它是GNU的C和C+编译器,其基本用 法为:gcc options filenamesoptions为编译选项,GCC总共提供的编译选项超过100个,但只有少数几个会被频繁使 用,我们仅对几个常用选项进行介绍。假设我们编译一输岀“Hello World的程序:/* Filename:helloworld.c */main()(prin
4、tf(Hello Worldn);)最简单的编译方法是不指定任何编译选项:gcc helloworld.c它会为目标程序生成默认的文件名a.out,我们可用编译选项来为将产生的可执行文件 指定一个文件名来代替a.out。例如,将上述名为helloworld.c的C程序编译为名叫helloworld的可执行文件,需要输入如下命令:gcc - helloworld helloworld.c-c选项告诉GCC仅把源代码编译为目标代码而跳过汇编和连接的步骤:-S编译选项告诉GCC在为C代码产生了汇编语言文件后停止编译。GCC产生的汇编语 言文件的缺省扩展名是.s,上述程序运行如下命令:gcc -S h
5、elloworld.c将生成helloworld.c的汇编代码,使用的是AT&T汇编。用emacs打开汇编代码如下图:-E选项指示编译器仅对输入文件进行预处理。当这个选项被使用时,预处理器的输出被送 到标准输出(默认为屏幕)而不是储存在文件里。-0选项告诉GCC对源代码进行基本优化从而使得程序执行地更快;而2选项告诉GCC 产生尽可能小和尽可能快的代码。使用2选项编译的速度比使用。时慢,但产生的代码执行速度会更快。-g选项告诉GCC产生能被GNU调试器使用的调试信息以便调试你的程序,可喜的是,在 GCC里,我们能联用g和一(产生优化代码)。-pg选项告诉GCC在你的程序里加入额外的代码,执行时
6、,产生gprof用的剖析信息以显 示你的程序的耗时情况。3 .GDB调试器GCC用于编译程序,而Linux的另个GNU工具gdb则用于调试程序。gdb是一 个用来调试c和c+ +程序的强调试器,我们能通过它进行系列调试工作,包括设置断点、观查变量、单步等。其最常用的命令如下:file:装入想要调试的可执行文件。kill:终止正在调试的程序。list:列表显示源代码。next:执行一行源代码但不进入函数内部。step:执行一行源代码而且进入函数内部。run:执行当前被调试的程序quit:终止 gdbwatch:监视个变量的值break:在代码里设置断点,程序执行到这里时挂起make:不退出gdb
7、而重新产生可执行文件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(Hthe sum of 1 + 2+.+ is %dH, 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用于清除所有的已定义的断点,clearfunction清除设置在函数上的断 点,clear 则清除设置在指定行上的断点0watch命令:watch命令用于观查变量或表达式的值,我们观查sum变量只需要运行w
9、atch sum :watch为表达式(变量)expr设置个观察点,量表达式值有变化时,程序会 停止执行。要观查当前设置的watch,可以使用info watchpoints命令。next、step 命令:next, step用于单步执行,在执行的过程中,被watch变量的变化情况将实时呈现(分别 显示 Old value 和 New value),如下图:next、step命令的区别在于step遇到函数调用,会跳转到到该函数定义的开始行去执行, 而next则不进入到函数内部,它把函数调用语句当作一条普通语句执行。4 .Makemake是所有想在Linux系统上编程的用户必须掌握的工具,对于任
10、何稍具规模的程序, 我们都会使用到make,几乎可以说不使用make的程序不具备任何实用价值。在此,我们有必要解释编译和连接的区别。编译器使用源码文件来产生某种形式的目标文件 (object files),在编译过程中,外部的符号参考并没有被解释或替换(即外部全局变量和函数并没有被找到)。因此,在编译阶段所报的错 误一般都是语法错误。而连接器则用于连接目标文件和程序包,生成一个可执行程序。在连接阶段,一个目标文件中对别的文件中的符号的参 考被解释,如果有符号不能找到,会报告连接错误编译和连接的一般步骤是:第一阶段把源文件一个个的编译成目标文件,第二阶段把所有 的目标文件加上需要的程序包连接成一
11、个可执行文件。这样的过程很痛苦,我们需要使用大量的gcc命令。而make则使我们从大量源文件的编译和连接工作中解放出来,综合为步完成。GNU Make的主要工作是读进个文本文件,称为makefile。这个文件记录了哪些文件(目的文件,目的文件不一定是最后的可执行程序,它可以是任何 种文件)由哪些文件(依靠文件)产生,用什么命令来产生。Make依靠此makefile中的信息检查磁盘上的文件,如果目的文件的创建或修 改时间比它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。假设我们写下如下的三个文件,add.h用于声明add函数,add.c提供两个整数相加的函 数体,而main.
12、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.gcc main.o add. o testmain.o : main.c
13、add.hgcc -c main.c -o main.oadd.o : add.c add.hgcc -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 -omain.o命令产生main.o目标代码,最后利用main.o和add.o文件(两个模块的目标代 码)执行gcc main.o add.o -o test命令产生可执行文件test 我们可在makefile中加入变量,另外。环境变量在make过程中
14、也被解释成make的变 量。这些变量是大小写敏感的,一般使用大写字母。Make变量可以做很多事情,例如:i)存储个文件名列表:ii)存储可执行文件名;iii)存储编译器选项。要定义个变量,只需要在一行的开始写下这个变量的名字,后面跟个=号,再跟变量的 值。引用变量的方法是写个$符号,后面跟(变量名)。我们把前面的makefile利用变量重写一遍(并假设使用Wall -g编译选 项):OBJS = main.o add.CC = gccCFLAGS = -Wall -O -gtest : $(OBJS)$(CC) $(OBJS) -o testmain.o : main.c add.h$(CC)
15、 $(CFLAGS) -c main.c -o main.oadd.o : add.c add.h$(CC) $(CFLAGS) -c add.c -o add.omakefile中还可定义清除(clean)目标,可用来清除编译过程中产生的中间文件,例如 在上述makefile文件中添加下列代码:clean:rm -f * .0运行make clean时,将执行rmf *.命令,删除所有编译过程中产生的中间文件。不管怎么说,自己动手编写makefile仍然是很复杂和烦琐的,而且很容易出错。因此,GNU 也为我们提供了 Automake和Autoconf来辅助快速自动产生makefile,读者可
16、以参阅相关资料。本章主要阐述了 Linux程序的编写、编译、调试方法及make,实际上就是引导读者学习 怎样在Linux下编程,为后续章节做好准备。Linux下的C编程实战(二)文件系统编程1 .Linux文件系统Linux 支持多种文件系统,如 ext、ext2、minix、iso966、msdos、fat、vfat nfs等。在这些具体文件系统的上层,Linux提供了虚拟文件系统(VFS)来统它们的行为,虚拟文件系统为不同的文件系统与内核的通信提供了 一致的接口。下图给出了 Linux中文件系统的关系:在Linux平台下对文件编程可以使用两类函数:(1) Linux操作系统文件AR; (2
17、) C语言I/。库函数。 前者依赖于Linux系统调用,后者实际上与操作系统是独立的,因为在任何操作系统下,使用C语言I/O库函数操作文 件的方法都是相同的。本章将对这两种方法进行实例讲解。2 .Linux 文件 APILinux的文件操作API涉及到创建、打开、读写和关闭文件。创建int creat(const char * filename, mode_t mode);参数mode指定新建文件的存取权限,它同umask 一起决定文件的最终权限 (mode&umask),其中umask代表了文件在创建时需要去掉的些存取权限。umask可通过系统调用umask()来改变:int umask(i
18、nt newmask);该调用将umask设置为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_APPEN
19、D以追加的方式打开文件O_CREAT创建一个文件O_EXEC如果使用了CREAT而且文件已经存在,就会发生一个错误O_NOBLOCK以非阻塞的方式打开个文件O_TRUNC如果文件已经存在,则删除文件的内容RDONLY、O_WRONLYRDWR三个标志只能使用任意的个。如果使用了CREATE标志,则使用的函数是int open(const char * pathname,int flags,mode_t mode);这个时候我们还要指定mode标志,用来表示文件的访问权限。mode可以是以下情况的组合: 标志含义S_l RUSR用户可以读S_IWUSR用户可以写S_IXUSR用户可以执行S_l
20、RWXU用户可以读、写、执行S_l RGRP组可以读SWGRP组可以写S_IXGRP组可以执行S_l RWXG组可以读写执行S_l ROTH其他人可以读S_l WOTH其他人可以写S_l XOTH其他人可以执行S_l RWXO其他人可以读、写、执行S_ISUID设置用户执行IDS_l SGI D设置组的执行I D除了可以通过上述宏进行“或”逻辑产生标志以外,我们也可以自己用数字来表示,Linux 总共用5个数字来表示文件的各种权限:第一位表示设置用户ID;第二位表示设置组ID;第三位表示用户自己的权限位;第四位表示组的权限;最后一位表示其他人的权限。每个数字可以取1(执行权限)、2(写权限)、
21、4(读权限)、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_lRWXU | S_lROTH | S_lXOTH | 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所指定的文件中读取!ength个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。函数write实现将把length个 字节从buf指向的缓冲区中写到文
23、件描述符fd所指向的文件中,返回值为实际写入的字节数。以一CREAT为标志的。pen实际上实现了文件创建的功能,因此,下面的函数等同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:相对文件开头S
24、EEK_CUR:相对文件读写指针的当前位置SEEK_END:相对文件末尾offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5个字节:lseek(fd, -5, SEEK_CUR);由于Iseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文 件的长度: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_l RUSR | S_IWUSR); / 创建并 打开文件/if (fd)(write(fd, Hello, Software Weekly, strlen(Hello, software weekly); /* 写入Hello, software weekly 字符串
26、/close(fd);fd = open(hello.txt, O_RDWR);len = read(fd, str, LENGTH); /t 读取文件内容 /strlen = 0;printf(%sn, str);close(fd);;编译并运行,执行3.C语言库函数C库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOS、Windows, Linux还是在VxWorks中都是这些函数:创建和打开Fl LE * fopen(const char * path, const char * mode);fopen()实现打开指定文件filename,其中的mode为打开模式,C语言中
27、支持的打开模 式如下表:标志含义r, rb以只读方式打开w, wb以只写方式打开。如果文件不存在,则创建该文件,否则文件被截断a, ab以追加方式打开。如果文件不存在,则创建该文件r+ , r+ b, rb+以读写方式打开w+ , w+ b, wh+以读写方式打开。如果文件不存在时,创建新文件,否则文件被截断a+, a+ b, ab+以读和追加方式打开。如果文件不存在,创建新文件其中b用于区分二进制文件和文本文件,这一点在DOS、Windows系统中是有区分 的,但Linux不区分二进制文件和文本文件。读写C库函数支持以字符、字符串等为单位,支持按照某中格式进行文件的读写,这组函数为:int
28、fgetc(FI LE * stream);int fputc(int c, Fl LE * stream);char * fgets(char * s, int n, Fl LE * stream);int fputs(const char * s, Fl LE * stream);int fprintf(FI LE * stream, const char *format, .);int fscanf (Fl LE * stream, const char *format, .);size_t fread(void * ptr, size_t size, size_t n, Fl LE *
29、 stream); size_t fwrite (const void * ptr, size_t size, size_t n, Fl LE * stream);fread()实现从流stream中读取加n个字段,每个字段为size字节,并将读取的字段放 入ptr所指的字符数组中,返回实际已读取的字段数。在读取的字段数小于num时,可能是在函数调用时出现错误,也可能是读到文件的结尾。所以 要通过调用feof()和ferror()来判断。write。实现从缓冲区ptr所指的数组中把n个字段写到流stream中,每个字段长为size 个字节,返回实际写入的字段数。另外,C库函数还提供了读写过程中
30、的定位能力,这些函数包括int fgetpos( Fl LE * stream, fpos_t * pos);int fsetpos( Fl LE * stream, const fpos_t * pos);int fseek(Fl LE * stream, long offset, int whence);等。关闭利用C库函数关闭文件依然是很简单的操作:int fclose (Fl LE * stream);例程:将第2节中的例程用C库函数来实现。# include # define LENGTH 100main()Fl LE *fd;char strLENGTH;fd = fopen(he
31、llo.txt, w+); / 创建并打开文件 /if (fd)fputs(Hello, Software Weekly, fd); /* 写入 Hello, software weekly 字符串 ,/fclose(fd);fd = fopen(hello.txt, r);fgets(str, LENGTH, fd); / 读取文件内容 /printf(%sn, str);fclose(fd);)4.小结Linux提供的虚拟文件系统为多种文件系统提供了统一的接口,Linux的文件编程有 两种途径:基于Linux系统调用;基于C库函数。这两种编程所涉及到文件操作有新建、打开、读写和关闭,对随机
32、文件还可以定位。本章对这两 种编程方法都给出了具体的实例。Linux下的C编程实战(三)进程控制与进程通信编程1 .Linux 进程Linux进程在内存中包含三部分数据:代码段、堆栈段和数据段。代码段存放了程序 的代码。代码段可以为机器中运行同程序的数个进程共享。堆栈段存放的是子程序(函数)的返回地址、子程序的参数及程序的局部变量。 而数据段则存放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)。与代码段不同,如果系统中同时运 行多个相同的程序,它们不能使用同一堆栈段和数据段。Linux进程主要有如下几种状态:用户状态(进程在用户状态下运行的状态)、内核状态(
33、进 程在内核状态下运行的状态)、内存中就绪(进程没有执行,但处于就绪状态,只要内核调度它,就可以执行)、内存中睡眠(进程正在睡眠 并且处于内存中,没有被交换到SWAP设备)、就绪且换出(进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行)、睡 眠且换出(进程正在睡眠,且被换出内存)、被抢先(进程从内核状态返回用户状态时,内核抢先于它,做了上下文切换,调度了另个进程, 原先这个进程就处于被抢先状态)、创建状态(进程刚被创建,该进程存在,但既不是就绪状态,也不是睡眠状态,这个状态是除了进程 以外的所有进程的最初状态)、僵死状态(进程调用exit结束,进程不再存在,但在进程表项中仍有
34、记录,该记录可由父进程收集)。下面我们来以个进程从创建到消亡的过程讲解Linux进程状态转换的“生死因果”。(1)进程被父进程通过系统调用fork创建而处于创建态;(2) fork调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程进入就绪态 (或者在内存中就绪,或者因为内存不够而在SWAP设备中就绪);(3)若进程在内存中就绪,进程可以被内核调度程序调度到CPU运行:(4)内核调度该进程进入内核状态,再由内核状态返回用户状态执行。该进程在用户状态 运行定时间后,又会被调度程序所调度而进入内核状态,由此转入就绪态。有时进程在用户状态运行时,也会因为需要内核服务,使用系统调 用而进入内核
35、状态,服务完毕,会由内核状态转回用户状态。要注意的是,进程在从内核状态向用户状态返回时可能被抢占,这是由于有优先 级更高的进程急需使用CPU,不能等到下一次调度时机,从而造成抢占:(5)进程执行exit调用,进入僵死状态,最终结束。2 .进程控制进程控制中主要涉及到进程的创建、睡眠和退出等,在Linux中主要提供了 fork、exec、 clone的进程创建方法,sleep的进程睡眠和exit的进程退出调用,另外Linux还提供了父进程等待子进程结束的系统调用waitofork对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一,它执行 次却返回两个值,完全“不可思
36、议”。先看下面的程序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 processThis is child processThis is parent processThis is parent processfork在英文中是“分叉”的意思,这个名字取得很形象。个进程在运行中,如果使用了 fork,就产生
37、了另个进程,于是进程就“分叉” 了当前进程为父进程,通过fork()会产生一个子进程。对于父进程,fork函数返回子程序 的进程号而对于子程序,fork函数则返回零,这就是个函数返回两次的本质。可以说,fork函数是Unix系统最杰出的成就之一,它是七十年 代Unix早期的开发者经过理论和实践上的长期艰苦探索后取得的成果。如果我们把上述程序中的循环放的大一点:int main()(int i;if (fork() = 0)(for (i = 1; i 10000; i+ + )printf(HThis is child process);)else(for (i = 1; i 10000; i
38、+ +)printf(*This is parent process”););则可以明显地看到父进程和子进程的并发执行,交替地输出“This is child process和 “This is parent process 此时此刻,我们还没有完全理解fork。函数,再来看下面的一段程序,看看究竟会产生多 少个进程,程序的输出是什么?int main()(int i;for (i = 0; i execlp、execle、execv execve 和execvp),被用于启动个指定路径和文件名的进程exec函数族的特点体现在:某进程一旦调用了 exec类函数,正在执行的程序就被干掉了, 系统
39、把代码段替换成新的程序(由exec类函数执行)的代码,并且原有的数据段和堆栈段也被废弃,新的数据段与堆栈段被分配,但是进程号却 被保留。也就是说,exec执行的结果为:系统认为正在执行的还是原先的进程,但是进程对应的程序被替换了。fork函数可以创建一个子进程而当前进程不死,如果我们在fork的子进程中调用exec函 数族就可以实现既让父进程的代码执行又启动个新的指定进程,这实在是很妙的。fork和exec的搭配巧妙地解决了程序启动另程序的执行但 自己仍继续运行的问题,请看下面的例子:char commandMAX_CMD_LEN;void main()int rtn; /子进程的返回数值/w
40、hile (1)(/从终端读取要执行的命令/printf();fgets(command, MAX CMD LEN, stdin);commandstrlen(command) - 1 = 0;if (fork() = 0)/子进程执行此命令/execlp(command, command);/如果exec函数返回,表明没有正常执行命令,打印错误信息/perror(command);exit(errorno);else/父进程,等待子进程结束,并打印子进程的返回值wait(&rtn);printf(M child process return %dnH, rtn););这个函数基本上实现了一个
41、shell的功能,它读取用户输入的进程名和参数,并启动对应的 进程。cloneclone是Linux2.0以后具备的新功能,它较fork更强(可认为fork是clone要实现的 一部分),可以使得创建的子进程共享父进程的资源,并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。clone函数的原型为:int clone(int (* fn)(void *), void * child_stack, int flags, void * arg);此函数返回创建进程的PI D,函数中的flags标志用于设置创建子进程时的相关选项,具体 含义如下表:标志含义CLO
42、NE_PARENT创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了兄弟”而不是父 子,,CLONE_FS子进程与父进程共享相同的文件系统,包括root、当前目录、umaskCLONE_FI LES子进程与父进程共享相同的文件描述符(file descriptor)表CLONE_NEWNS在新的namespace启动子进程,namespace描述了进程的文件hierarchyCLONE_SIGHAND子进程与父进程共享相同的信号处理(signal handler)表CLONE_ PTRACE若父进程被trace,子进程也被traceCLONE_VFORK父进程被挂起,直至子进程释放
43、虚拟内存资源CLONE_VM子进程与父进程运行于相同的内存空间CLONE_PID子进程在创建时PID与父进程一致CLONE_THREADLinux 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 tempeh;variable = 9;fd = open(test.filew, O_RDONLY);child_
44、stack = (void * *) malloc(16384);printf(The variable was %dn, variable);clone(do_something, child_stack, CLONE_VM|CLONE_FILES, NULL):sleep(1); /延时以便子进程完成关闭文件操作、修改变量/printf(The variable is now %dn, variable);if (read(fd, &tempch, 1) 1) perror(MFile Read Error*);exit(1);)printf(We could read from the filen);return 0;运行输出:The variable is now 42File Read Error程序的输出结果告诉我们,子进程将文件关闭并将变量修改(调用clone时用到的CLONE_VM、CLONE_FI LES标志将使得变量和文件描述符表被共享),父进程随即就感觉到了,这就是clone的特点。sleep函数调用sleep可以用来使进程挂起指定的秒数,该函数的原型为:unsigned int sleep(unsigned int seconds);该函数调用使得进程挂起