1、Tar naUnix/Linux 核心编程课程内容Unix/Linux操作系统简介GNU编译工具GCCGNU C内存管理文件I/O进程管理信号进程间通信多线程网络通信UNIX/LINUX操作系统Unix操作系统 UNIX操作系统,是美国AT&T公司于1971年在PDP-11上运行的操作系统。具有多用户、多任务的特点,支持多种处理器架构,最早由肯汤普逊(Kenneth Lane Thompson)、丹尼斯里奇(Dennis MacAlistair Ritchie)和Douglas McIlroy于1969年在AT&T的贝尔实验室开发。Unix的三大派生版本 System V Berkley Hy
2、bridSystem V AIX Solaris HP-UX IRIXBerkleyFreeBSD 一种类UNIX操作系统,但不是真正意义上的UNIX操作系统,它是由经过BSD、386BSD和4.4BSD发展而来的Unix的一个重要分支NetBSD 是一份免费,安全的具有高度可定制性的类Unix操作系统,适于多种平台,从64位AMDAthlon服务器和桌面系统到手持设备和嵌入式设备OpenBSD 一个从NetBSD衍生出来的类Unix操作系统Mac OS X 是苹果公司开发的专属操作系统Mac OS的最新版本。它是一套Unix基础的操作系统,包含两个主要的部份:核心名为Darwin,是以Fre
3、eBSD源代码和Mach微核心为基础,由苹果公司和独立开发者社区协力开发;及一个由苹果电脑开发,名为Aqua之专有版权的图形用户界面。Hybrid Minix 名称取自英语Mini UNIX,是一个迷你版本的类Unix操作系统(约300MB)Linux 是一类Unix计算机操作系统的统称Unix族谱Linux简介 Linux是一种自由和开放源码的类Unix操作系统。目前存在着许多不同的Linux,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,从手机、平板电脑、路由器和视频游戏控制台,到台式计算机、大型机和超级计算机。Linux是一个领先的操作系统,世界上运算最快的10台
4、超级计算机运行的都是Linux操作系统。严格来讲,Linux这个词本身只表示Linux内核,但实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。Linux得名于计算机业余爱好者Linus Torvalds。Tux(一只企鹅,全称为tuxedo)是Linux的标志和Linux相关 MINIX 操作系统 MINIX 系统是由Andrew S.Tanenbaum(AST)开发的。AST 是在荷兰Amsterdam 的Vrije 大学数学与计算机科学系统工作,是ACM 和IEEE 的资深会员 GNU 计划 POSIX 标准 GPL通用公共许可
5、证GNU计划 GNU Project由Richard Stallman发起开始于1984年,由自由软件基金(FSF:Free Software Foundation)支持。GNU的基本原则是共享。GNU的主旨在于发展一个类似 Unix,并且为自由软件的完整操作系统:GNU 系统。目前已经有各种使用 Linux 作为内核的 GNU 操作系统正被广泛地使用著;这些系统通常被称作为“Linux”,但准确的说应该被称GNU/Linux”系统。POSIX 标准 POSIX(Portable Operating System Interface for Computing Systems)是由IEEE 和
6、ISO/IEC 开发的一簇标准。该标准是基于现有的UNIX 实践和经验,描述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移植运行。POSIX.1 仅规定了系统服务应用程序编程接口(API),仅概括了基本的系统服务标准 在90 年代初,POSIX 标准的制定正处在最后投票敲定的时候,Linux刚刚起步,这个UNIX 标准为Linux 提供了极为重要的信息,使得Linux 的能够在标准的指导下进行开发,能够与绝大多数UNIX 系统兼容。GPL通用公共许可证 GNU通用公共许可证(GPL)一个法定的版权声明,但附带(或,在技术上去除了某些限制),在条款中,允许对
7、某项成果以及由它派生的其余成果的重用,修改和复制对所有人都是自由的。非版权(copyleft)copyleft带有标准的Copyright声明,确认作者的所有权和标志。但它放弃了标准copyright中的某些限制。它声明:任何人不但可以自由分发该成果,还可以自由地修改它。但你不能声明你做了原始的工作,或声明是由他人做的。最终,所有派生的成果必须遵循这一条款(相当于继承关系)版本命名早期版本 第一个版本的内核是0.01。其次是0.02,0.03,0.10,0.11,0.12(第一GPL版本),0.95,0.96,0.97,0.98,0.99及1.0。旧计划(1.0和2.6版之间),版本的格式为A
8、.B.C,其中A,B,C代表:A大幅度转变的内核。这是很少发生变化,只有当发生重大变化的代码和核心发生才会发生。在历史上曾改变两次的内核:1994年的1.0及1996年的2.0。B是指一些重大修改的内核。内核使用了传统的奇数次要版本号码的软件号码系统(用偶数的次要版本号码来表示稳定版本)。C是指轻微修订的内核。这个数字当有安全补丁,bug修复,新的功能或驱动程序,内核便会有变化。第三次,自2.6.0(2003年12月)发布后,人们认识到,更短的发布周期将是有益的。自那时起,版本的格式为A.B.C.DLinux特点 遵循GNU/GPL 开放性 多用户 多任务 设备独立性 供了丰富的网络功能 可靠
9、的系统安全 良好的可移植性Linux发行版 大众的Ubuntu 优雅的Linux Mint 锐意的Fedora 华丽的openSUSE 自由的Debian 简洁的Slackware 老牌的RedHatGNU 编译工具GCC编译工具-GCC 编译,如C、C+、Object C、Java、Fortran、Pascal、Ada等语言。GCC是可以在多种硬件平台上编译出可执行 在使用GCC编译程序时,编译过程可以细分为4个阶段:a.预处理。b.编译。c.汇编。d.链接。程序员可以对编译过程进行控制,同时GCC提供了强大的代码优化功能。查看gcc的版本:gcc vC程序中的文件后缀名扩展名扩展名说明说明
10、.a静态对象库静态对象库.c需要预处理的需要预处理的C语言源代码语言源代码.hC语言源代码头文件语言源代码头文件.i不需要预处理的不需要预处理的C语言源代码语言源代码.o目标文件目标文件.s汇编语言代码汇编语言代码.so共享对象库共享对象库编译单源程序 语法:gcc 选项参数 c文件 通用选项参数说明如下:1、指定输出文件名-o 指定输出文件例子:gcc -o main ch01.c 2、警告与提示.-pedantic检测不符合ANSI/ISO C语言标准的源代码,使用扩展语法的地方将产生警告信息。-Wall 生成尽可能多的警告信息。-Werror 要求编译器将警告当做错误进行处理。3、指定编
11、译文件类型-x 指定编译代码类型,c、c+、assembler,none。None根据扩展名自动确认。例子:gcc x c -Wall o main ch01.c 4、生成调试信息与优化-g 生成调试信息-O优化 5、建议:在编译任何程序的时候都带上-Wall选项。编译多源程序 语法:gcc 选项 C源代码1 C源代码2 C源代码3 思考:头文件的作用是什么?预处理 语法:gcc -E C源代码文件 示例:gcc-E-o ch01.i ch01.c gcc-E-o ch01_1.i ch01_1.c 注意:预处理每次只能处理一个文件。不能处理多个文件,就是每个.c文件对应一个.i文件。不指定
12、o 选项,预处理的结果输出到标准输出设备。预处理指令介绍预编译指示符号预编译指示符号说明说明#define定义宏#elifelse if 多选分支#else与#if、#ifndef、#ifdef结合使用#error产生错误,挂起预处理程序#if判定#endif结束判定#ifdef判定宏是否定义#ifndef判定宏是否定义#include将指定的文件插入#include的位置#include_next与#include一样,但从当前目录之后的目录查找#line指定行号#pragma提供额外信息的标准方法,可用来指定平台#undef删除宏#warning创建一个警告#连接操作符号,用于宏内连接两个
13、字符串#error、#warning#include#define VERSION 3/*演示编译器gcc*/#if(VERSION 2)#error 版本低#else#warning 版本高#endif int main()printf(Hello gcc使用!n);return 0;#include、#include_next1.系统头文件使用#include 2.用户头文件使用#include“”规则:1.系统头文件会在I参数指定得目录中查找。2.用户头文件会在当前目录查找。3.Unix标准系统目录/usr/local/include/usr/lib/gcc-lib/版本/include
14、/usr/include4.编译C+优先查找/usr/include/g+5.#include 会在所有标准目录的子目录sys中查找time.h6.#include的文件名含扩展,*、?无意义。除非文件名中包含*。#line int re=0;printf(Hello gcc使用!n);for(int i=0;i200)re+=i;printf(out:%dn,re);/代码行数被修改#line 200printf(out:%dn,re,a);/人为错误printf(out:%dn,re);#pragma 所有GCC的pragma都定义两个词GCC+其他#pragma GCC dependen
15、cy 文件名 提示符号测试文件的时间戳,当指定文件比当前文件新的时候产生警告。#pragma GCC poison每次使用指定名字就会产生警告pragma pack(1)#pragma有一个等价的宏_Pragam#include#pragma GCC dependency ch02.c/#pragma GCC poison printf addint main()int re=0;printf(Hello gcc使用!n);int i;_Pragma(GCC poison printf add)for(i=0;i200;i+)re+=i;printf(out:%dn,re);return 0;
16、预定义宏介绍宏宏说明说明_BASE_FILE_源代码的完整路径源代码的完整路径_cplusplusC+有效,程序不符合标准为有效,程序不符合标准为1,否则是标准的年月否则是标准的年月_DATE_日期日期_FILE_源代码文件名源代码文件名_func_当前函数名当前函数名_FUNCTION_同上同上_INCLUDE_LEVEL_包含层数包含层数,基本的为基本的为0_LINE_行数行数_TIME_时间时间编译环境变量 C_INCLUDE_PATH:查找头文件的目录。C。CPATH:查找头文件,相当于-l选项。CPLUS_INCLUDE_PATH:查找头文件的目录。C+。LD_LIBRARY_PAT
17、H:编译没有影响,主要影响运行。指定目录便于定位共享库。LIBRARY_PATH:查找连接文件,相当于-l选项生成汇编 编译成汇编 gcc-S ch01.c ch01_1.c 编译汇编 gcc ch01.s ch01_1.s-o main创建静态库编译静态库 gcc-c-static ch01_1.c 其中-static可选,可阻止gcc使用共享库 不使用共享库会使可执行文件变大,但会减少运行时间开销ar指令 ar-r libmy.a ch01_1.o 语法:ar 选项 归档文件名 目标文件列表 指令ar的常用选项选项选项说明说明-d从归档文件删除指定目标文件列表。从归档文件删除指定目标文件列
18、表。-q将指定目标文件快速附加到归档文件末尾。将指定目标文件快速附加到归档文件末尾。-r将指定目标文件插入文档,如果存在则更新。将指定目标文件插入文档,如果存在则更新。-t显示目标文件列表显示目标文件列表-x把归档文件展开为目标文件把归档文件展开为目标文件使用静态库 gcc-o main ch01.c libmy.a 如果libmy.a在LIBRARY_PATH的指定目录中,还可以采用如下方式编译。gcc ch01.c -o main lmy创建共享库 编译共享库 编译共享库分成两个部分:编译成位置独立代码的目标文件,选项-fpic 编译成共享库,选项-shared gcc-c-fpic ch
19、01_1.c gcc-shared ch01_1.o-o libmy.so 使用一条指令的效果一样 gcc-fpic-shared ch01_1.c-o libmy.so定位共享库 共享库编译的时候与静态库一样依赖LIBRARAY_PATH,运行的时候依赖LD_LIBRARY_PATH。规则:查找LD_LIBRARY_PATH,目录使用冒号分隔。/ect/ld.so.cache中找到的列表。工具ldconfig维护。目录/lib 目录/usr/lib使用共享库 gcc ch01.c libmy.so-o main 在代码中动态加载共享库:共享库代码int add(int a,int b)int
20、 c=a+b;c=c/2;return c;共享库的四个函数#include void*dlopen(const char*filename,int flag);char*dlerror(void);void*dlsym(void*handle,const char*symbol);int dlclose(void*handle);其中dlopen的参数flag的含义如下:RTLD_LAZY:符号查找时候才加载。RTLD_NOW:马上加载。其他工具简介库工具程序介绍 ldconfig ldconfig是一个动态链接库管理命令,为了让动态链接库为系统所共享,还需运行动态链接库的管理命令-ldco
21、nfig.ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表 ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令.ldconfig选项(1)-v或-verbose:用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名
22、字.(2)-n:用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录.(3)-N:此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接.(4)-X:此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新.(5)-f CONF:此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf.(6)-C CACHE:此选项指定生成的缓存文件为CACHE,系统默认的是/etc/ld
23、.so.cache,此文件存放已排好序的可共享的动态链接库的列表.(7)-r ROOT:此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为 ROOT/etc/ld.so.conf.如用-r/usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性.(8)-l:通常情况下,ldconfig搜索动态链接库时将自动建立动态链接库的连接.选择此项时,将进入专家模式,需要手工设置连接.一般用户不用
24、此项.(9)-p或-print-cache:此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字.(10)-c FORMAT 或-format=FORMAT:此选项用于指定缓存文件所使用的格式,共有三种:old(老格式),new(新格式)和compat(兼容格式,此为默认格式).(11)-V:此选项打印出ldconfig的版本信息,而后退出.(12)-?或-help 或-usage:这三个选项作用相同,都是让ldconfig打印出其帮助信息,而后退出.ldd 列出共享库的依赖关系:ldd libmy.soobjdump显示二进制文件信息以一种可阅读的格式让你更多地了解二进制文件
25、可能带有的附加信息-source-S 尽可能反汇编出源代码,尤其当编译的时候指定了-g这种调试参数时,效果比较明显。隐含了-d参数。-show-raw-insn 反汇编的时候,显示每条汇编指令对应的机器码,除非指定了 -prefix-addresses,这将是缺省选项。-no-show-raw-insn 反汇编时,不显示汇编指令的机器码,这是指定-prefix-addresses 选项时的缺省设置。错误处理异常处理方式 根据函数返回值判断异常 返回一般用户数据-1:表示异常,其他就是用户数据 返回指针用户数据 NULL指针,OXFFFFFFFF指针表示错误 其他就是指针用户数据 返回值不是用户
26、数据,只是用来指明函数调用状态。0:成功-1:失败 返回void。一般不会发生错误异常处理方式 使用外部全局变量errno获取异常原因 根据errno得到异常编号 errno在函数调用正确不会被修改 绝对不要通过errno判定错误 把errno转换为字符串 strerror函数 perror函数 printf函数 printf()的格式输出%m格式环境变量 环境表 每个程序都会接收到一张环境表,是一个字符指针数组。数组以null做为结束。全局变量environ保存了该数组的首地址。环境变量操作函数函数函数描述描述getenv返回指向name关联的value的指针putenv将形式为name=v
27、alue的环境变量放入环境表setenv将name设置为value,每三个参数决定是否替代已有变量unsetenv删除定义clearenv删除环境表中所有项内存管理在语言结构上的变化 从malloc/free到new/delete C+是强类型语言,new/delete的主要成果也就是加强了类型观念,减少了强制类型转换的需求。但是从内存管理角度看,这个变革并没有多少的突破性 从new/delete到内存配置器(allocator)allocator的引入也是C+内存管理一个突破。留意一下你就可以发现,整个STL所有组件的内存均从allocator分配。也就是说,STL并不推荐使用new/del
28、ete进行内存管理,而是推荐使用allocator.Unix/Linux内存管理 Unix/Linux低层采用三层结构,实际使用中可以方便映射到两层或者三层结构,以适用不同的硬件结构。最下层的申请内存函数get_free_page。之上有三种类型的内存分配函数 1.kmalloc类型。内核进程使用,基于切片(slab)技术,用于管理小于内存页的内存申请。思想出发点和应用层面的内存缓冲池同出一辙。但它针对内核结构,特别处理,应用场景固定,不考虑释放。2.vmalloc类型。内核进程使用。用于申请不连续内存。3.brk/mmap类型。用户进程使用。malloc/free实现的基础。进程与内存 所有
29、进程(执行的程序)都必须占用一定数量的内存 对任何一个普通进程来讲,它都会涉及到5种不同的数据段 代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作它是不可写的。数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。BSS段:BSS段包含了程序中未初始化全局变量,在内存中 bss段全部置零。Block started by symbol堆和栈堆(heap):堆是用于存放进程运行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程
30、调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“”中定义的变量(但不包括static声明的变量,static意味这在数据段中存放变量)。除此以外在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也回被存放回栈中。由于栈的后进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上将我们可以把堆栈看成一个临时数据寄存、交换的内存区。进程如何组织这些区域 上述几种内存区域中数据段、BSS和堆通常是被连续
31、存储的内存位置上是连续的,而代码段和栈往往会被独立存放。有趣的是堆和栈两个区域关系很“暧昧”,他们一个向下“长”(i386体系结构中栈向下、堆向上),一个向上“长”,相对而生。但你不必担心他们会碰头,因为他们之间间隔很大查看内存结构/proc/进程ID目录 cat maps size 文件名 报告正文段、数据段和bss段的长度虚拟内存管理技术Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该空间是块大小为4G的线性虚拟空间,用户所看到和接触的都是该虚拟地址,无法看到实际的物理内存地址。利用这种虚拟地址不但能起到保护操作系统的效果(用户不能直接访问物理内存),
32、而且更重要的是用户程序可使用比实际物理内存更大的地址空间4G的进程地址空间被人为的分为两个部分用户空间与内核空间。用户空间从0到3G(0 xC0000000),内核空间占据3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。例外情况只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。虚拟内存管理技术 用户空间对应进程,所以每当进程切换,用户空间就会跟着变化;而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表(init_mm.pgd),用户进程各自有不同的页表 每个进程的用户空间都是完全独立、互不相干
33、的。你可以把程序同时运行10次(当然为了同时运行,让它们在返回前一同睡眠100秒吧),你会看到10个进程占用的线性地址一模一样。进程内存管理 进程内存管理的对象是进程线性地址空间上的内存镜像,这些内存镜像其实就是进程使用的虚拟内存区域(memory region)。进程虚拟空间是个32或64位的“平坦”(独立的连续区间)地址空间(空间的具体大小取决于体系结构)。要统一管理这么大的平坦空间可绝非易事,为了方便管理,虚拟空间被化分为许多大小可变的(但必须是4096的倍数)内存区域,这些区域在进程线性地址中像停车位一样有序排列。这些区域的划分原则是“将访问属性一致的地址空间存放在一起”,所谓访问属性
34、在这里无非指的是“可读、可写、可执行等”。物理内存管理(页管理)Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数4k(在i386体系结构中)大小页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存,系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低刷新率(频繁刷新会很大增加访问速度)。getpagesize();brk/sbrk的虚拟内存管理 void*sbrk(int size)Size=0 返回
35、sbrk/brk上次的末尾地址 size0 分配内存空间,返回上一次末尾地址 size0 释放空间 int brk(void*ptr)直接修改访问的有效范围的末尾地址 释放空间形成一个完整的page,则该页影射被解除 返回:0:分配成功-1:失败系统底层的内存映射(mmap/munmap)#include void*mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);int munmap(void*start,size_t length);参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让
36、系统自动选定地址,映射成功后返回该地址。参数length:代表将文件中多大的部分映射到内存。映射空间大小。建议4k倍数,不是4K倍数,自动对齐 参数prot:映射区域的保护方式。可以为以下几种方式的组合:1.PROT_EXEC 映射区域可被执行2.PROT_READ 映射区域可被读取3.PROT_WRITE 映射区域可被写入4.PROT_NONE 映射区域不能存取mmap参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。1.MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓
37、励用此标志。2.MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。3.MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。4.MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。5.MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。6.MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。mmap 参数fd 要映射到内存中的文件
38、描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(1),错误原因存于errno 中。errno 错误代码:1.EBADF 参数fd 不是有效的文件描述词2.EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用
39、MAP_SHARED则要有PROT_WRITE以及该文件要能写入。3.EINVAL 参数start、length 或offset有一个不合法。4.EAGAIN 文件被锁住,或是有太多内存被锁住。5.ENOMEM 内存不足。系统调用系统调用Linux 大部分的系统功能是通过系统调用(System Call)来实现的.如open,send之类.这些函数在C程序调用起来跟标准C库函数(printf)非常类似.但是实现机制完全不同.库函数仍然是运行在Linux 用户空间程序.很多时候内部会调用系统调用.但系统调用是内核实现的.在C库封装成函数.但通过系统软中断进行调用.用time命令测试时间,系统时间
40、实际就是系统调用时间累积 time./demo1 用strace 可以跟踪一种程序系统调用使用情况 strace./demo1#不需要调试信息库函数与系统调用的关系 以是C库函数malloc与系统调用sbrk的关系文件控制Linux文件结构 Linux环境中的文件具有特别重要的意义,因为它们为操作系统服务和设备提供了一个简单而统一的接口.在Linux中,一切(或几乎一切)都是文件。通常程序完全可以像使用文件那样使用磁盘文件、串行口、打印机和其他设备。大多数情况下,你只需要使用五个基本的函数open、close、read、write和ioctl Linux中的任何事物都可以用一个文件代表,或者可
41、以通过特殊的文件进行操作。Linux文件结构(2)一些特殊文件 目录 设备文件 /dev/console /dev/tty/dev/null 底层库函数Linux 在底层实现一整套处理文件函数.这一些函数能处理普通文件,网络socket文件,设备文件等 全部是系统调用实现的函数文件处理函数 open 打开或创建一个文件 creat 建立一个空文件 close 关闭一个文件 read 从文件读入数据 write 向文件写入一个数据 lseek 在文件中移动读写位置 unlink 删除一个文件 remove 删除一个文件本身 fcntl 控制一个文件属性文件描述符值为一个非负整数用于表示一个打开文
42、件在内核空间被引用,并且由系统调用(open)所创建read,write使用文件描述符内核缺省打开三个文件描述符 1-标准输出 2-错误输出 0-标准输入unistd.h中,0,1,2应当替换成STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO.文件描述符的变化范围是0OPEN_MAX。OPEN_MAX有可能是63,linux允许更大的值。Open函数#include int open(const char*pathname,int flags);int open(const char*pathname,int flags,mode_t mode);int cre
43、at(const char*pathname,mode_t mode);第三个参数(.)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)flags 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。1.O_RDONLY 只读模式 2.O_WRONLY 只写模式 3.O_RDWR 读写模式 打开/创建文件时,至少得使用上述三个常量中的一个Open函数以下常量是选用的:1.O_APPEND 每次写操作都写入文件的末尾2.O_CREAT 如果指定文件不存在,则创建这个文件3.O_EXCL 如果要创建的文件已存在
44、,则返回-1,并且修改 errno 的值4.O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容5.O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。6.O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)open函数以下三个常量同样是选用的,它们用于同步输入输出1.O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。2.O_RSYNC read 等待所有写入同一区域的写操作完成后再进行3.O_SYNC
45、 等待物理 I/O 结束后再 write,包括更新文件属性的 I/Oopen 返回的文件描述符一定是最小的未被使用的描述符一个进程同时打开文件的个数是有限的,这个限制通常由limits.h头文件中的常量OPEN_MAX决定POSIX要求最少16通常被设置成256函数说明:write#include ssize_t write(int filedes,const void*buf,size_t nbytes);返回值:写入文件的字节数(成功);-1(出错)write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf。返回值一般总是等于 nbytes,否则就是出错了。常见
46、的出错原因是磁盘空间满了或者超过了文件大小限制。函数说明:read#include ssize_t read(int filedes,void*buf,size_t nbytes);返回值:读取到的字节数;0(读到 EOF);-1(出错)read 函数从 filedes 指定的已打开文件中读取 nbytes 字节到 buf 中。以下几种情况会导致读取到的字节数小于 nbytes:A.读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30。此时再使用 read 函数作用于这个文件
47、会导致 read 返回 0。B.从终端设备(terminal device)读取时,一般情况下每次只能读取一行。函数说明:read C.从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。D.读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes。E.从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。F.在读取了部分数据时被信号中断。函数说明:close close调用终止一个文件描述符fildes与其对应文件之间的关联。文件描述符被释放并能够重新使用。close调用成功就返回0
48、,出错就返回-1。有时检查close调用的返回结果十分重要。有的文件系统,特别是网络文件系统,可能不会在关闭文件之前报告文件写操作中出现的错误,因为执行写操作时,数据可能未被确认写入。关闭一个文件时会释放该进程加在文件上的所有记录锁 当一个进程终止,内核自动关闭它所有打开的文件。lseek函数每个打开的文件都有一个与其相关的“当前文件偏移量”偏移量通常是一个非负整数,用以度量从文件开始处计算的字节数。读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。当打开一个文件时,除非指定O_CREAT,否则偏移量被设置为0该函数仅将当前的文件偏移量记录在内核中,它并不会引起任何I/O操作。
49、文件偏移量可以大于文件的当前长度。对该文件的下一次读写会加长该文件,并形成文件空洞,这个是允许的文件空洞并不要求在磁盘上占用存储区,但对于新写的数据需要分配磁盘块打开文件的内核数据结构dup,dup2函数 复制一个现在的文件描述符 Dup返回的返回的一定是当前可用描述符的最小值 dup2可由第二个参数指定描述符值,如果指定的文件已经打开,那么先关闭文件 一定程度上和fcntl功能相同sync,fsync,fdatasync 大多数磁盘IO都通过缓冲进行,写入文件时先写入缓冲区,如果缓冲未满,则不将其排入输出队列,这种方式叫做延迟写。延迟写减少了磁盘写次数,但降低了文件内容更新速度 这三个函数可
50、以保证缓冲区和实际文件系统的数据一致。sync将所有修改过的缓冲区排入写队列,然后就返回,并不等实际写磁盘 fsync只对一个文件,并且等实际写磁盘完成才返回 fdatasync只更新数据,不更新文件属性fcntl函数#include int fcntl(int fd,int cmd);int fcntl(int fd,int cmd,long arg);int fcntl(int fd,int cmd,struct flock*lock);函数说明:fcntl()用来操作文件描述词的一些特性。参数fd代表欲设置的文件描述词.参数cmd代表欲操作的指令。fcntl函数 cmd有以下几种情况:F