doc文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。 【C 语言库函数源代码】 【本程序在 Dev C++ 4.9.9.2 下编译通过】 #include /* 这个函数调用的是库函数中的
strtol()函数,关于这个函数的
源代码后面将会给出。 */ int my_atoi(char *str) { return (int) strtol(str, NULL, 10); } /* 下面的两个函数没有调用 strtol()函数, 而是直接给出了该函数的实现。 */ int my_atoi01(const char *str) { long int v=0; int sign = 0; while ( *str == ' ') str++;
if(*str == '-'||*str == '+') sign = *str++;
while (isdigit(*str)) { v = v*10 + *str - '0'; str++; } return sign == '-' ? -v:v; } int my_atoi02(char *str) { int sign; int n; unsigned char *p;
p=str; while (isspace(*p) ) p++; sign = (*p == '-' ) ? -1 : 1; if (*p=='+' || *p=='-' ) p++;
for (n=0; isdigit(*p); p++) n = 10*n + (*p - '0'); return sign*n; }
int main() { char * str = \"21474837\"; printf(\"%d\\n\ str = \"-21474838\"; printf(\"%d\\n\ str = \"21474837\"; printf(\"%d\\n\ str = \"-21474838\"; printf(\"%d\\n\ str = \"21474837\"; printf(\"%d\\n\ str = \"-21474838\"; printf(\"%d\\n\ system(\"pause\"); return 0; } atoi(), itoa()是整型数和字符串表示的整型数字之间的转换,是函数调用实现的 对内建基本类型之间的强制类型转换是在编译时实现的,对数值可能会截断、重新解释等。 对自定义类型,也可以定义了类型转换函数(如 X::operator int())、带参数的构造函数(如 complex(double)) 来进行类型转换 区别大了,强制类型转换能将 int 转换成字符串吗? int a=123456; char pch[20]; itoa(a,pch,10);//按十进制转换成字符串,所以转换后 pch 数组为字符串\"123456\"; 同样,atoi()将字符串转换成相应的数字 int c; char b[]=\"123456\"; c=atoi(b);//转换后 c 就为十进制数 123456 强制类型转换根本就不能完成这些工作,比如 static_cast(b);//这样肯定不对啦 Unix Vi 编辑器完全使用手册- (一)、进入 vi 在系统提示字符(如$、#)下敲入 vi <档案名称>,vi 可以自动帮你载入所要 编辑的文件或是开启一个新 文件 (如果该文件不存在或缺少文件名) 进入 vi 后萤幕左方会出现波浪符号, 。 凡是列首有该符号就代 表此列目前是空的。 (二)、两种模式 如上所述,vi 存在两种模式:指令模式和输入模式。在指令模式下输入的按键 将做为指令来处理:如输入 a,vi 即认为是在当前位置插入字符。而在输入模式下,vi 则把输入的按键当作 插入的字符来处理。指令 模式切换到输入模式只需键入相应的输入命令即可(如 a,A),而要从输入模式 切换到指令模式,则需在 输入模式下键入 ESC 键, 如果不晓得现在是处於什麽模式, 可以多按几次 [ESC], 系统如发出哔哔声就表 示已处于指令模式下了。 付:有指令模式进入输入模式的指令: 新增 (append) a :从光标所在位置後面开始新增资料,光标後的资料随新增资料向後移动。 A: 从光标所
在列最後面的地方开始新增资料。 插入 (insert) i: 从光标所在位置前面开始插入资料,光标後的资料随新增资料向後移动。 I :从光标所在列的第一个非空白字元前面开始插入资料。 开始 (open)
o :在光标所在列下新增一列并进入输入模式。 O: 在光标所在列上方新增一列并进入输入模式。 (三)、退出 vi 在指令模式下键入:q,:q!,:wq 或:x(注意:号),就会退出 vi。其中:wq 和:x 是 存盘退出,而:q 是直接退出, 如果文件已有新的变化,vi 会提示你保存文件而:q 命令也会失效,这时你可以 用:w 命令保存文件后再用:q 退出,或用:wq 或:x 命令退出,如果你不想保存改变后的文件,你就需要用:q! 命令,这个命令将不保存文件 而直接退出 vi。 (四)、基本编辑 配合一般键盘上的功能键,像是方向键、[Insert] 、[Delete] 等等,现在你应 该已经可以利用 vi 来编辑文件了。 当然 vi 还提供其他许许多多功能让文字的 处理更为方便。 何谓编辑?一般认为是文字的新增、修改以及删除,甚至包括文字区块的搬移、 复制等等。先这里介绍 vi 的如何做删除与修改。(注意:在 vi 的原始观念里,输入跟编辑是两码子事。 编辑是在指令模式下操作 的,先利用指令移动光标来定位要进行编辑的地方,然後才下指令做编辑。) 删除与修改文件的命令: x: 删除光标所在字符。 dd :删除光标所在的列。 r :修改光标所在字元,r 後接著要修正的字符。 R: 进入取替换状态,新增文字会覆盖原先文字,直到按 [ESC] 回到指令模式 下为止。 s: 删除光标所在字元,并进入输入模式。 S: 删除光标所在的列,并进入输入模式。 其实呢,在 PC 上根本没有这麽麻烦!输入跟编辑都可以在输入模式下完成。例 如要删除字元,直接按
[Delete] 不就得了。而插入状态与取代状态可以直接用 [Insert] 切换,犯不 著用什麽指令模式的编 辑指令。不过就如前面所提到的,这些指令几乎是每台终端机都能用,而不是仅 仅在 PC 上。 在指令模式下移动光标的基本指令是 h, j, k, l 。想来各位现在也应该能猜到 只要直接用 PC 的方向 键就可以了,而且无论在指令模式或输入模式下都可以。多容易不是。 当然 PC 键盘也有不足之处。 有个很好用的指令 u 可以恢复被删除的文字, U 而 指令则可以恢复光标所 在列的所有改变。这与某些电脑上的 [Undo] 按键功能相同。 三、附件:vi 详细指令表 (一)、基本编辑指令: 新增 (append) a :从光标所在位置後面开始新增资料,光标後的资料随新增资料向後移动。 A: 从光标所在列最後面的地方开始新增资料。 插入 (insert) i: 从光标所在位置前面开始插入资料,光标後的资料随新增资料向後移动。 I :从光标所在列的第一个非空白字元前面开始插入资料。 开始 (open) o :在光标所在列下新增一列并进入输入模式。 O: 在光标所在列上方新增一列并进入输入模式。 x: 删除光标所在字符。 dd :删除光标所在的列。 r :修改光标所在字元,r 後接著要修正的字符。 R: 进入取替换状态,新增文字会覆盖原先文字,直到按 [ESC] 回到指令模式 下为止。 s: 删除光标所在字元,并进入输入模式。 S: 删除光标所在的列,并进入输入模式。 (二)、光标移动指令: 由於许多编辑工作是藉由光标来定位,所以 vi 提供许多移动光标的方式,这个 我们列
几张简表来说明(这些当然是指令模式下的指令): ┌—————┬—————————————┬—————┐ │指令 │说明 │功能键 │ ├—————┼—————————————┼—————┤ │0 │移动到光标所在列的最前面 │[Home] │ ├—————┼—————————————┼—————┤ │$ │移动到光标所在列的最後面 │[End] │ ├—————┼—————————————┼—————┤ │[CTRL][d] │向下半页 │ │ ├—————┼—————————————┼—————┤ │[CTRL][f] │向下一页 │[PageDown]│ ├—————┼—————————————┼—————┤ │[CTRL][u] │向上半页 │ │ ├—————┼—————————————┼—————┤ │[CTRL][b] │向上一页 │ [PageUp] │ └—————┴—————————————┴—————┘ ┌——┬—————————————————┐ │指令│说明 │
├——┼—————————————————┤ │H │移动到视窗的第一列 │ ├——┼—————————————————┤ │M │移动到视窗的中间列 │ ├——┼—————————————————┤ │L │移动到视窗的最後列 │ ├——┼—————————————————┤ │b │移动到下个字的第一个字母 │ ├——┼—————————————————┤ │w │移动到上个字的第一个字母 │ ├——┼—————————————————┤ │e │移动到下个字的最後一个字母 │ ├——┼—————————————————┤ │^ │移动到光标所在列的第一个非空白字元│ └——┴—————————————————┘ ┌——┬———————————————————┐ │指令│说 明 │ ├——┼———————————————————┤ │n│减号移动到上一列的第一个非空白字元 │ │ │前面加上数字可以指定移动到以上 n 列 │ ├——┼———————————————————┤ │n+ │加号移动到下一列的第一个非空白字元 │ │ │前面加上数字可以指定移动到以下 n 列 │ ├——┼———————————————————┤ │nG │直接用数字 n 加上大写 G 移动到第 n 列 │
└——┴———————————————————┘ ┌————┬———————————————┐ │指令 │说明 │ ├————┼———————————————┤ │fx │往右移动到 x 字元上 │ │Fx │往左移动到 x 字元上 │ ├————┼———————————————┤ │tx │往右移动到 x 字元前 │ │Tx │往左移动到 x 字元前 │ ├————┼———————————————┤ │; │配合 f&t 使用,重复一次 │ │, │配合 f&t 使用,反方向重复一次 │ ├————┼———————————————┤ │/string │往右移动到有 string 的地方 │ │?string │往左移动到有 string 的地方 │ ├————┼———————————————┤ │n │配合 /&? 使用,重复一次 │ │N │配合 /&? 使用,反方向重复一次 │ └————┴———————————————┘ ┌————┬———————————————————┬———————— ——┐ │指令 │说明 │备 注 │ ├————┼———————————————————┼———————— ——┤ │n( │左括号移动到句子的最前面 │句子是 以 │ │ │前面加上数字可以指定往前移动 n 个句子 │! . ? 三种符号 来界定 │ │n) │右括号移动到下个句子的最前 面 │ │ │ │前面加上数字可以指定往後移动 n 个句子 │ │ ├————┼———————————————————┼———————— ——┤ │n{ │左括弧移动到段落的最前面 │段落是 以 │ │ │前面加上数字可以指定往前移动 n 个段落 │段落间的空白列 界定 │ │n} │右括弧移动到下个段落的最前 面 │ │ │ │前面加上数字可以指定往後移动 n 个段落 │ │ └————┴———————————————————┴———————— ——┘ (三)、更多的编辑指令 这些编辑指令非常有弹性,基本上可以说是由指令与范围所构成。例如 dw 是由 删除指令 d 与范围 w 所 组成,代表删除一个字 d(elete) w(ord) 。 指令列表如下: d 删除(delete) y 复制(yank) p 放置(put) c 修改(change) 范围可以是下列几个: e 光标所在位置到该字的最後一个字母 w 光标所在位置到下个字的第一个字母 b 光标所在位置到上个字的第一个字母 $ 光标所在位置到该列的最後一个字母 0 光标所在位置到该列的第一个字母 ) 光标所在位置到下个句子的第一个字母 ( 光标所在位置到该句子的第一个字母 } 光标所在位置到该段落的最後一个字母 { 光标所在位置到该段落的第一个字母 说实在的,组合这些指令来编辑文件有一点点艺术气息。不管怎麽样,它们提供 更多编辑文字的能力。值得 注意的一点是删除与复制都会将指定范围的内容放到暂存区里, 然後就可以用指 令 p 贴到其它地方去,这 是 vi 用来处理区段拷贝与搬移的办法。 某些 vi 版本,例如 Linux 所用的 elvis 可以大幅简化这一坨指令。如果稍微 观察一下这些编辑指令 就会发现问题其实是定范围的方式有点杂, 实际上只有四个指令罢了。 指令 v
非 常好用,只要按下 v 键, 光标所在的位置就会反白,然後就可以移动光标来设定范围,接著再直接下指令 进行编辑即可。 对於整列操作, vi 另外提供了更方便的编辑指令。前面曾经提到过删除整列文 字的指令 dd 就是其中一个 ;cc 可以修改整列文字;而 yy 则是复制整列文字;指令 D 则可以删除光标到 该列结束为止所有的文字。 (四)、文件操作指令 文件操作指令多以 : 开头,这跟编辑指令有点区别。 :q 结束编辑(quit) :q! 不存档而要放弃编辑过的文件。 :w 保存文件(write)其後可加所要存档的档名。 :wq 即存档後离开。 zz 功能与 :wq 相同。 与:wq 相同 Posted in linux | No Comments gcc
星期一, 11 月 12th, 2007
前言 Linux 的发行版中包含了很多软件开发工具. 它们中的很多是用于 C 和 C++应 用程序开发的. 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具. 本文的主旨是介绍如何在 Linux 下使用 C 编译器和其他 C 编程工具, 而非 C 语言编程的教程. 在本文中你将学到以下知识: 什么是 C GNU C 编译器 用 gdb 来调试 GCC 应用程序 你也能看到随 Linux 发行的其他有用的 C 编程工具. 这些工具包括源程 序美化程序(pretty print programs), 附加的调试工具, 函数原型自动生成工 具(automatic function prototypers). ——————————————————————————– 注意: 源程序美化程序(pretty print programs)自动帮你格式化源代码产生始 终如一的缩进格式. ——————————————————————————– 什么是 C? C 是一种在 UNIX 操作系统的早期就被广泛使用的通用编程语言. 它最早 是由贝尔实验室的 Dennis Ritchie 为了 UNIX 的辅助开发而写的, 开始时 UNIX 是用汇编语言和一种叫 B 的语言编写的. 从那时候起, C 就成为世界上 使用最广泛计算机语言. C 能在编程领域里得到如此广泛支持的原因有以下一些: 它是一种非常通用的语言. 几乎你所能想到的任何一种计算机上都有至少一种 能用的 C 编译器. 并且它的语法和函数库在不同的平台上都是统一的, 这个特 性对开发者来说很有吸引力. 用 C 写的程序执行速度很快. C 是所有版本的 UNIX 上的系统语言. C 在过去的二十年中有了很大的发展. 在 80 年代末期美国国家标准协会 (American National Standards Institute)发布了一个被称为 ANSI C 的 C 语 言标准.这更加保证了将来在不同平台上的 C 的一致性. 在 80 年代还出现了一 种 C 的面向对象的扩展称为 C++. C++ 将在另一篇文章 “C++ 编程”中描述. Linux 上可用的 C 编译器是 GNU C 编译器, 它建立在自由软件基金会的 编程许可证的基础上, 因此可以自由发布. 你能在 Linux 的发行光盘上找到 它. GNU C 编译器 随 Slackware Linux 发行的 GNU C 编译器(GCC)是一个全功能的 ANSI C 兼容编译器. 如果你熟悉其他操作系统或硬件平台上的一种 C 编译器, 你将能 很快地掌握 GCC. 本节将介绍如何使用 GCC 和一些 GCC 编译器最常用的选项.
使用 GCC 通常后跟一些选项和文件名来使用 GCC 编译器. gcc 命令的基本用法如下: gcc [options] [filenames] 命令行选项指定的操作将在命令行上每个给出的文件上执行. 下一小节将 叙述一些你会最常用到的选项. GCC 选项 GCC 有超过 100 个的编译选项可用. 这些选项中的许多你可能永远都不会 用到, 但一些主要的选项将会频繁用到. 很多的 GCC 选项包括一个以上的字符. 因此你必须为每个选项指定各自的连字符, 并且就象大多数 Linux 命令一样你 不能在一个单独的连字符后跟一组选项. 例如, 下面的两个命令是不同的: gcc -p -g test.c gcc -pg test.c 第一条命令告诉 GCC 编译 test.c 时为 prof 命令建立剖析(profile)信 息并且把调试信息加入到可执行的文件里. 第二条命令只告诉 GCC 为 gprof 命令建立剖析信息. 当你不用任何选项编译一个程序时, GCC 将会建立(假定编译成功)一个名 为 a.out 的可执行文件. 例如, 下面的命令将在当前目录下产生一个叫 a.out 的文件: gcc test.c 你能用 -o 编译选项来为将产生的可执行文件指定一个文件名来
代替 a.out. 例如, 将一个叫 count.c 的 C 程序编译为名叫 count 的可执行文件, 你将输入下面的命令: gcc -o count count.c ——————————————————————————– 注意: 当你使用 -o 选项时, -o 后面必须跟一个文件名. ——————————————————————————– GCC 同样有指定编译器处理多少的编译选项. -c 选项告诉 GCC 仅把源代 码编译为目标代码而跳过汇编和连接的步骤. 这个选项使用的非常频繁因为它 使得编译多个 C 程序时速度更快并且更易于管理. 缺省时 GCC 建立的目标代 码文件有一个 .o 的扩展名. -S 编译选项告诉 GCC 在为 C 代码产生了汇编语言文件后停止编译. GCC 产生的汇编语言文件的缺省扩展名是 .s . -E 选项指示编译器仅对输入文件进 行预处理. 当这个选项被使用时, 预处理器的输出被送到标准输出而不是储存 在文件里. 优 化 选 项 当你用 GCC 编译 C 代码时, 它会试着用最少的时间完成编译并且使编译 后的代码易于调试. 易于调试意味着编译后的代码与源代码有同样的执行次序, 编译后的代码没有经过优化. 有很多选项可用于告诉 GCC 在耗费更多编译时间 和牺牲易调试性的基础上产生更小更快的可执行文件. 这些选项中最典型的是
-O 和 -O2 选项. -O 选项告诉 GCC 对源代码进行基本优化. 这些优化在大多数情况下都会 使程序执行的更快. -O2 选项告诉 GCC 产生尽可能小和尽可能快的代码. -O2 选项将使编译的速度比使用 -O 时慢. 但通常产生的代码执行速度会更快. 除了 -O 和 -O2 优化选项外, 还有一些低级选项用于产生更快的代码. 这 些选项非常的特殊, 而且最好只有当你完全理解这些选项将会对编译后的代码 产生什么样的效果时再去使用. 这些选项的详细描述, 请参考 GCC 的指南页, 在命令行上键入 man gcc . 调试和剖析选项 GCC 支持数种调试和剖析选项. 在这些选项里你会最常用到的是 -g 和 -pg 选项. -g 选项告诉 GCC 产生能被 GNU 调试器使用的调试信息以便调试你的程序. GCC 提供了一个很多其他 C 编译器里没有的特性, 在 GCC 里你能使 -g 和 -O (产生优化代码)联用. 这一点非常有用因为你能在与最终产品尽可能相近的情 况下调试你的代码. 在你同时使用这两个选项时你必须清楚你所写的某些代码 已经在优化时被 GCC 作了改动. 关于调试 C 程序的更多信息请看下一节”用 gdb 调试 C 程序” . -pg 选项告诉 GCC 在你的程序里加入额外的代码, 执行时, 产生 gprof 用的剖析信息以显示你的程序的耗时情况. 关于 gprof 的更多信息请参考 “gprof” 一节. Posted in linux | No Comments
【实验报告】练习三:内核定时器 实验报告】练习三: 星期一, 11 月 12th, 2007
练习三: 练习三:内核定时器 一 Linux 下得时间 1.时间的测量 有时候要计算程序执行的时间.比如要对算法进行时间分析.这个时候可以使 用下面这个函数. #include int gettimeofday(struct timeval *tv,struct timezone *tz); 之中.tz 来代替. gettimeofday 将时间保存在结构 tv 之中.tz 一般使用 NULL 来代替. 2. 什么是 linux 的 itimer 机制
itimer 是 internal timer 的缩写,它是建立在 linux 的 timer 机制上的,可 以在定时 器时间到并触发后,自我进行重新设置的间隔定时器。系统为每个进程提供了三 个间隔定时器,分别在各自的一个时间区域内递减。当其中任意一个定时器 到期时,就会发出一个信号给进程,同时,定时器重新开始运作。三种定时器描 述如下: ITIMER_REAL 真实时钟,到期时送出 SIGALRM 信号。 ITIMER_VIRTUAL 仅在进程运行时的计时,到期时送出 SIGVTALRM 信号。 ITIMER_PROF 不仅在进程运行时计时,在系统为进程运作而运行时它也计 时,与 ITIMER_VIRTUAL 对比,该定时器通常为那些在用户态和核心态空间运行 的应用所花去的时间计时,到期时送出 SIGPROF 信号。 3、 Linux 的普通 timer 机制和它的数据结构 1)Jiffies: 这是一个长整数,表示以 10ms 为单位的系统时间。Jiffies 初始 设置为 0,只要系统一直运行,Jiffies 就不断增加。 结构:用于保存时间值,分为两部分,其中 tv_vec
保存的是整数 2)Timeval 结构 秒部分,tv_usec 保存的是毫秒部分。 struct timeval { int tv_sec; /* seconds */ int tv_usec; /* microseconds */ }; 结构:用于保存某一个 interval timer 的数据结构,其中两个 3)Itimerval 结构 成员是 timeval 结构,一个是 it_interval,用于保存 timer 的时间间隔,也 就是 timer 重新设置是的触发时间,另一个是 it_value,保存的是当前在 timer 中还剩下的时间值。 struct itimerval { struct timeval it_interval; /* timer interval */ struct timeval it_value; /* current value */ };
4)进程控制块中的 itimer 数据成员 在 task_struct 结构中也有几个成员与三个 itimer 有关: struct task_struct { ┅ ┅ unsigned long it_real_value, it_prof_value, it_virt_value; unsigned long it_real_incr, it_prof_incr, it_virt_incr; ┅ ┅ } 其中,it_****_value 用于设置 itimer 第一次设置时的时间量,而 it_****_incr 用于设置以后每次 itimer 的重置。 关于这 3 个时间具体的操作函数是: #include
int getitimer(int which,struct itimerval *value); int setitimer(int which,struct itimerval *newval,struct itimerval *oldval); 函数得到间隔计时器的时间值. setitimer getitimer 函数得到间隔计时器的时间值.保存在 value 中 setitimer 函数设置 newval.并将旧值保存在 间隔计时器的时间值为 newval.并将旧值保存在 oldval 中. which 表示使用三 个计时器中的哪一个. 是减少的时间, 个计时器中的哪一个. itimerval 结构中的 it_value 是减少的时间,当这个值为 的时候就发出相应的信号了. 0 的时候就发出相应的信号了. 然后设置为 it_interval 值. 二 Linux 下的信号 1。信号的产生 信号事件的发生有两个来源: 一个是硬件的原因(比如我们按下了键盘),一个是 软件的原因(比如我们使用系统函数或者是命令发出信号). 最常用的四个发出 信号的系统函数是 kill, raise, alarm 和 setitimer 函数. #include #include #i nclude int int
kill(pid_t pid,int sig); raise(int sig); alarm(unsigned int seconds); unisigned int
kill 系统调用负责向进程发送信号 sig. 如果 pid 是正数,那么向信号 sig 被发送到进程 pid. 如果 pid 等于 0,那么信号 sig 被发送到所以和 pid 进程在同一个进程组的进程 如果 pid 等于-1,那么信号发给所有的进程表中的进程,除了最大的哪个进程号. 如果 pid 由于-1,和 0 一样,只是发送进程组是-pid. 2。信号的使用 信号是通过调用 kill()函数产生并标识接受这个信号和信号类型的进程。实际 上,正在接受的应用程序进程可以将一个信号按照默认的方式来处理,也可以将 其忽略者使用用户定义的代码将其截获。系统调用 signal()函数来标识编号 和该信号的处理方法。 #include void (*signal(int signo,void(*fun)(int)))(int) 说明:1 返回值是一个 void (*)(int)类型的函数指针 2 signo 是信号名 3 第二个参数为该信号的处理函数指针,类型为 void (*)(int) 4 函数返回:成功则返回以前的信号处理配置函数指针,出错返回 SIG_ERR 5 SIG_ERR 原形为 #define SIG_ERR (void (*)())-1 程序中的信号处理例程; static void p_sig_handler(int signo) {
switch(signo) { case SIGALRM: { p_realt_secs++; //父进程 real 运行时间+1 秒 //SIGALRM 信号到来
signal(SIGALRM, p_sig_handler); //调用信号处理函数时间继 续走 break; } case SIGVTALRM: { p_virtt_secs++; //同上 //同上
signal(SIGVTALRM, p_sig_handler ); //同上 break; } case SIGPROF: { p_proft_secs++; signal(SIGPROF, p_sig_handler ); break; } default: break; } } //不要出现 //同上 //同上 //同上
Posted in linux | No Comments
【实验报告】练习二:shell 编程 实验报告】练习二: 星期一, 11 月 12th, 2007 练习二: 练习二:shell 编程 1. 1. 1)进程的概念 进程的概念 linux 进程 linux
什么是程序,什么是进程呢? 通俗的讲程序是一个包含可以执行代码的文件,是 一个静态的文件.而进程是一个开始执行但是还没有结束的程序的实例.就是可 执行文件的具体实现. 一个程序可能有许多进程,而每一个进程又可以有许多子 进程.依次循环下去,而产生子孙进程. 当程序被系统调用到内存以后,系统会给 程序分配一定的资源(内存,设备等等)然后进行一系列的复杂操作,使程序变成 进程以供系统调用.在系统里面只有进程没有程序,为了区分各个不同的进程,系 统给每一个进程分配了一个 ID 以便识别. 为了充分的利用资源,系统还对进程 区分了不同的状态.将进程分为新建,运行,阻塞,就绪和完成五个状态 新建表 进程分为新建, 进程分为新建 运行,阻塞,就绪和完成五个状态. 示进程正在被创建,运行是进程正在运行,阻塞是进程正在等待某一个事件发生, 就绪是表示系统正在等待 CPU 来执行命令,而完成表示进程已经结束了系统正在 回收资源. 2) 进程的创建 创建一个进程只要调用 fork 函数就可以了. #include pid_t fork();
当一个进程调用了 fork 以后,系统会创建一个子进程.这个子进程和父进程不同 的地方只有他的进程 ID 和父进程 ID,其他的都是一样.就象符进程克隆(clone) 自己一样.当然创建两个一模一样的进程是没有意义的.为了区分父进程和子进 程,我们必须跟踪 fork 的返回值. 当 fork 掉用失败的时候(内存不足或者是用户 掉用失败的时候( 的最大进程数已到)fork 返回-1,否则 的返回值有重要的作用 要的作用. 的最大进程数已到)fork 返回-1,否则 fork 的返回值有重要的作用.对于父进程 ID,而对于 0.就是根据这个返回值来区分父 fork 返回子进程的 ID,而对于 fork 子进程返回 0.就是根据这个返回值来区分父 子进程的. 子进程的. 父进程为什么要创建子进程呢?Linux 是一个多用户操作系统,在同 一时间会有许多的用户在争夺系统的资源.有时进程为了早一点完成任务就创建 子进程来争夺资源. 一旦子进程被创建,父子进程一起从 fork 处继续执行,相互
竞争系统的资源.有时候我们希望子进程继续执行,而父进程阻塞直到子进程完 成任务.这个时候可以调用 wait 或者 waitpid 系统调用. #include #include pid_t wait(int *stat_loc); pid_t waitpid(pid_t pid,int *stat_loc,int options); wait 系统调用会使父进程阻塞直到一个子进程结束或者是父进程接受到了一个 信号.如果没有父进程没有子进程或者他的子进程已经结束了 wait 回立即返回. 回立即返回. . 成功时(因一个子进程结束)wait ID,否则返回 1,并设置全局变 否则返回成功时(因一个子进程结束)wait 将返回子进程的 ID,否则返回-1,并设置全局变 是子进程的退出状态. exit,_exit 量 errno.stat_loc 是子进程的退出状态.子进程调用 exit,_exit 或者是 来设置这个值. return 来设置这个值. 为了得到这个值 Linux 定义了几个宏来测试这个返回 值. WIFEXITED:判断子进程退出值是非 0 WEXITSTATUS:判断子进程的退出值(当子进程退出时非 0). WIFSIGNALED:子进程由于有没有获得的信号而退出. WTERMSIG:子进程没有获得的信号号(在 WIFSIGNALED 为真时才有意义). waitpid 等待指定的子进程直到子进程返回.如果 pid 为正值则等待指定的进程 (pid).如果为 0 则等待任何一个组 ID 和调用者的组 ID 相同的进程.为-1 时等同 于 wait 调用.小于-1 时等待任何一个组 ID 等于 pid 绝对值的进程. stat_loc 和 wait 的意义一样. options 可以决定父进程的状态.可以取两个值 WNOHANG:父进程立即返回当没有子进程存在时. WUNTACHED:当子进程结束时 waitpid 返回,但是子进程的退出状态不可得 到. 父进程创建子进程后,子进程一般要执行不同的程序.为了调用系
统程序,可以使 用系统调用 exec 族调用.exec 族调用有着 6 个函数. #include int 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(const char *path,char *const argv[]); int execvp(const char *file,char *const argv[]): int execve(const char *path,const char *arg, char * const envp[]); 六个函数的区别: 六个函数的区别: 四个函数——-execl、execv、execle、execve——–第一个参数都是路径名。 四个函数——-execl、execv、execle、execve——–第一个参数都是路径名。 —— —— 的第一个参数则是文件名,如果文件名没有包含“ 恶性 execvp 和恶性 eclp 的第一个参数则是文件名,如果文件名没有包含“/”, 的行为搜索$PATH 找到要执行的的二进制文件。 他们会模仿 shell 的行为搜索$PATH 找到要执行的的二进制文件。 的函数希望接受以逗号分隔的参数列表, 三个名字中含有 l 的函数希望接受以逗号分隔的参数列表,列表以 NULL 指针作 为结束标志,这些参数将传递给被执行的程序。但是, 为结束标志,这些参数将传递给被执行的程序。但是,名字中包含 v 的函数者 接受一个向量,也就是指向以空结尾的字符串的指针数组。 接受一个向量,也就是指向以空结尾的字符串的指针数组。但如果用带 v 的函 数之一, 数组,然后把这个数组传递给 函数。 数之一,必须先构造一个 argv 数组,然后把这个数组传递给 exec 函数。 结尾的函数— execle— 最后两个以 e 结尾的函数—–execve 和 execle—可以让你为被执行的程序创建 专门的环境。 专门的环境。这个环境保存在 evnp 中,他也是一个指向以空结尾的字符串数组 指针,数组中每个字符串也是以空结尾。 指针,数组中每个字符串也是以空结尾。 接受他们的环境。 其他四个函数隐士地通过全局变量 environ 接受他们的环境。 Environ 是一个指 向字符串数组的指针,数组中包含了调用进程的环境。 向字符串数组的指针,数组中包含了调用进程的环境。使用 putenv 和 getenv 函数可以操控这些函数继承的环境( 函数可以操控这些函数继承的环境(见 6。)
2. Linux 下文件的操作 1)文件的创建和读写 ) 当要打开一个文件进行读写操作的时候,我们可以使用系统调用函数 open.使用 完成以后调用另外一个 close 函数进行关闭操作. #include int open(const char *pathname,int flags,mode_t mode); int close(int fd); open 函数有两个形式.其中 pathname 是要打开的文件名(包含路径名称,缺省是 认为在当前路径下面).flags 可以去下面的一个值或者是几个值的组合. O_RDONLY:以只读的方式打开文件. O_WRONLY:以只写的方式打开文件. O_RDWR:以读写的方式打开文件. O_APPEND:以追加的方式打开文件. O_CREAT:创建一个文件. O_EXEC:如果使用了 O_CREAT 而且文件已经存在,就会发生一个错误. O_NOBLOCK:以非阻塞的方式打开一个文件. O_TRUNC:如果文件已经存在,则删除文件的内容. 如果打开文件成功,open 会返回一个文件描述符. 如果打开文件成功,open 会返回一个文件描述符.我们以后对文件的所有操作就 可以对这个文件描述符进行操作了. 当我们操作完成以后,我们要关闭文件了, 可以对这个文件描述符进行操作了. 当我们操作完成以后,我们要关闭文件了, 就可以了, 是我们要关闭的文件描述符. 只要调用 close 就可以了,其中 fd 是我们要关闭的文件描述符. 文件打开了以后,我们就要对文件进行读写了.我们可以调用函数 read 和 write 进行文件的读写. #i nclude ssize_t read(int fd, void *buffer,size_t count); ssize_t write(int fd, const void *buffer,size_t count); fd 是要进行读写操作的文件描述符,buffer 是我们要写入文件内容或读出文件 内容的内存地址.count 是我们要读写的字节数. 从指定的文件(fd) (fd)中读取 对于普通的文件 read 从指定的文件(fd)中读取 count 字节到 buffer 缓冲区中 必须提供一个足够大的缓冲区), ),同时返回 (必须提 供一个足够大的缓冲区),同时返回 count. 如果 read 读到了文件的结尾 或者被一个信号所中断, count.如果是由信号中断引起返回 如果是由信号中断引起返回, 或者被一个信号所中断,返回值会小于 count.如果是由信号中断引起返回,而且 没有返回数据,read 会返回-1,且设置 errno 为 EINTR.当程序读到了文件结尾的 没有返回数据,read 会返回EINTR.当程序读到了文件结尾的 时候,read 时候,read 会返回 0. write 从 buffer 中写 count 字节到文件 fd 中,成功时返回 实际所写的字节数. 实际所写的字节数. 有时侯要判断文件是否可以进行某种操作(读,写等等).这 个时候可以使用 access 函数 函数. #include int access(const char *pathname,int mode); pathname:是文件名称,mode 是我们要判断的属性.可以取以下值或者是他们的 组合. R_OK 文件可以读,W_OK 文件可以写,X_OK 文件可以执行,F_OK 文件存在. 文件可以读,W_OK 文件可以写,X_OK 文件可以执行,F_OK 文件存在. 0,否则如果有一个条件不符时 返回否则如果有一个条件不符时, 当测试成功时函数返回 0,否则如果有一个条件不符时,返回-1. 2)目录文件的操作 目录文件的操作 得到当前的工作路径。 #include char *getcwd(char *buffer,size_t size); 提供一个 size 大小的 buffer,getcwd 会把当前的路径考到 buffer 中.如果 太小,函数会返回- 和一个错误号. buffer 太小,函数会返回-1 和一个错误号. 改变当前目录。 #include Int chdir(char *path ); 把当前目录改为 path 所包含的新目录执行成功返回 0,执行失败返回-1 并设置 errno 变量。 3.管道文件 系统调用 pipe 可以创建一个管道. #include int pipe(int fildes[2]); pipe 调用可以创建一个管道(通信缓冲区).当调用成功时,可以访问文件描述符 fildes[0],fildes[1].其中 fildes[0]是用来读的文件描述符,而 fildes[1]是 fildes[0]是用来读的文件描述符 是用来读的文件描述符, fildes[1]是 用来写的文件描述符. 用来写的文件描述符. 在实际使用中是通过创建一个子进程,然后一个进程写, 一个进程读来使用的. 为了实现重定向操作,需要调用另外一个函数 dup. #i nclude int dup (int oldfd); dup 将返回新的文件描述符。程序中应用了以下语句实现重定向。 close( A_to_B[0] ); close(1); dup( A_to_B[1] );. 传统上,但进程被创建时,该表中的第一个表项指向键盘而另外两个表 传统上,但进程被创建时, 项指向终端显示器。 stdin,stdout,stderr 项指向终端显示器。C 运行环境同时内和管理符号 stdin,stdout,stderr 以使 fileDescriptor[1], stdin 榜定到 fileDescripyor[0],stdout 绑定到 fileDescriptor[1],以及 fileDescriptor[2]。 stderr 绑定到 fileDescriptor[2]。所以上面语句意思为:关闭管道的读端,关 闭 stdout,复制管道写端与进程关联。 4.输出输入的重定向 4.输出输入的重定向 同管道操作类似,用了以下代码冲定向输出文件: int fid = open( out_file1, O_WRONLY|O_CREAT );//打开文件 close(1);//关闭 stdout dup(fid);//将 fid 重定向到进程 close(fid);//关闭 fid 5.关于几个函数 5.关于几个函数 1)#include pa = getenv(”PATH”);//查找名为 PATH 的环境变量并返回指向旗帜的指针 int putenv (const char *string) ;//添加或改变 string 中指定的 “name=value” 对 2)#include Current_dir = get_current_dir_name(),;//获取当前路径 Login_user = getlogin();//获取登录名 Posted in linux | No Comments 【实验报告】练习一:观察 Linux 行为 实验报告】练习一: 星期一, 11 月 12th, 2007 练习一: 练习一:观察 Linux 行为 1./proc 文件系统 /proc 文件系统是一种内核和内核模块用来向进程 (process) 发送信息的机制 (所以叫做 /proc)。这个伪文件系统让你可以和内核内部数据结构进行交互,获 取 有关进程的有用信息,在运行中 (on the fly) 改 变设置 (通过改变内核参 数)。 与其他文件系统不同,/proc 存在于内存之中而不是硬盘上。如果你察看 文件 /proc/mounts (和 mount 命令一样列出所有已经加载的文件系统),你会 看到其中 一行是这样的: grep proc /proc/mounts /proc /proc proc rw 0 0 /proc 由内核控制,没有承载 /proc 的设备。因为 /proc 主要存放由内核控制 的状态信息, 所以大部分这些信息的逻辑位置位于内核控制的内存。 /proc 进 对 行一次 ‘ls -l’ 可以看到大部分文件都是 0 字节大的;不过察看这些文件的 时候,确实可以看到一些信息。这怎么可能?这是因为 /proc 文件系统和其他 常规的文件系统一样把自己注册到虚拟文件系统层 (VFS) 了。 然而, 直到当 VFS 调用它,请求文件、目录的 i-node 的时候,/proc 文件系统才根据内核中的信 息建立相应的文件和目录。 1)加载 proc 文件系统 如果系统中还没有加载 proc 文件系统,可以通过如下命令加载 proc 文件系 统: mount -t proc proc /proc 上述命令将成功加载你的 proc 文件系统。更多细节请阅读 mount 命令的 man page。 /proc 的文件可以用于访问有关内核的状态、计算机的属性、正在运行的进程的 状态等信息。大部分 /proc 中的文件和目录提供系统物理环境最新的信息。尽 管 /proc 中的文件是虚拟的,但它们仍可以使用任何文件编辑器或像’more’, ‘less’或 ‘cat’这样的程序来查看。当编辑程序试图打开一个虚拟文件时, 这个文件就通过内核中的信息被凭空地 (on the fly) 创建了。 2)得到有用的系统/内核信息 得到有用的系统/ proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。下面是 一些重要的文件: * /proc/cpuinfo - CPU 的信息 (型号, 家族, 缓存大小等) * /proc/meminfo - 物理内存、交换空间等的信息 * /proc/mounts - 已加载的文件系统的列表 * /proc/devices - 可用设备的列表 * /proc/filesystems - 被支持的文件系统 * /proc/modules - 已加载的模块 * /proc/version - 内核版本 * /proc/cmdline - 系统启动时输入的内核命令行参数 proc 中的文件远不止上面列出的这么多。想要进一步了解的读者可以对 /proc 的每一个文件都’more’一下或读参考文献[1]获取更多的有关 /proc 目录中的 文件的信息。建议使用’more’而不是’cat’,除非你知道这个文件很小,因 为有些文件 (比如 kcore) 可能会非常长。 3)proc 的文件的打开关闭及读取 /pro 中的文件可以象普通的 ASCII 文件一样进行读取,为了读取一个伪文件的 内容,可以打开文件然后使用 stdio 程序库中的例程如 fgets()或者 fsacnf ()来读取文件。下面是实验中一个读取/proc/sys/kernel/hostname 的例子: FILE *thisProcFile = NULL; thisProcFile = fopen( “/proc/sys/kernel/hostname”, “r” fgets( lineBuf, LB_SIZE + 1, thisProcFile ); printf( “Machine hostname: %s”, lineBuf ); fclose( thisProcFile ); ); 2.实验中使用的一些 linux 函数和 c 函数及方法 函数—time()和 1)linux 函数—time()和 ctime() 要输出系统当前的时间,可以使用下面两个函数 #innclude char *ctime(const time_t *clock); time 函数返回从 1970 年 1 月 1 日 0 点以来的秒数.存储在 time_t 结构之中.不 过这个函数的返 回值对于我们来说没有什么实际意义.这个时候我们使用第二个函数将秒数转化 为字符串 . 这个函数的返回类型是固定的:一个可能值为. Thu Dec 7 14:58:59 2006 这 个字符串 的长度是固定的为 26。 2)c 语言中的 argc 和 argv 一个 c 程序可能具有以下形式的头部: int main( int argc, char *argv[]) 如果不向 shell 传递参数,可以省略这两个参数, Argc 的含义是所输入的文件名和参数的个数, argv[0]则指向文件名 argv[]其他 则指向参数,比如 observer –l 10 600 Argc = 4, argv[0] = “observer”, argv[1]= “-l”„„ 现在 c 主程序可以采用如下方式引用这些参数: int main( int argc, char *argv[] ) //决定报告类型 reportType = STANDARD; strcpy( repTypeName, “Standard” ); if ( argc > 1) { sscanf( argv[1], “%c%c”, &c1, &c2 ); } if ( c1 != ‘-’) { fprintf(stderr, “usage: observer [-s][-l int dur]\\n” ); exit(1); } if ( c2 == ’s’ ) { reportType = SHORT; strcpy( repTypeName, “Short” ); } if ( c2 == ‘l’ ) { reportType = LONG; strcpy( repTypeName, “Long” ); interval = atoi( argv[2] ); duration = atoi( argv[3] ); } 3)c 中的一些函数 <1>转换函数 <1>转换函数 atoi 函数:把字符串转换成整型数 相关函数 表头文件 定义函数 atoi,atol,strtod,strtol,strtoul #include int atof(const char *nptr); <2>输入输出函数 <2>输入输出函数 —printf(),sprintf(),sscanf(),gets(),getch(),scanf(),puts() <3>文件操作函数 <3>文件操作函数 — fopen(),fclose(),fgets(),fputs(),fscanf(),fprintf(),fgetc(),fputc(),f real(),fwrite() 结论: 结论: /proc 文件系统提供了一个基于文件的 Linux 内部接口。它可以用于确定系统的 各种不同设备和进程的状态。 C 函数库和 linux 函数库定义了很多关于各种操作的函数,可以利用它们来实现 对/proc 的读取和修改。 Posted in linux | No Comments LINUX 系统中动态链接库的创建与使用 星期一, 11 月 12th, 2007 大家都知道, WINDOWS 系统中有很多的动态链接库(以.DLL 为后缀的文件, 在 DLL 即 Dynamic Link Library)。这种动态链接库,和静态函数库不同,它里面的函 数并不是执行程序本身的一部分,而是根据执行程序需要按需装入,同时其执行 代码可在多个 执行程序间共享,节省了空间,提高了效率,具备很高的灵活性, 得到越来越多程序员和用户的青睐。那么,在 LINUX 系统中有无这样的函数库 呢? 答 案是肯定的,LINUX 的动态链接库不仅有,而且为数不少。在/lib 目录下, 就有许多以.so 作后缀的文件,这就是 LINUX 系统应用的动态链接库,只 不过 与 WINDOWS 叫法不同,它叫 so,即 Shared Object,共享对象。(在 LINUX 下, 静态函数库是以.a 作后缀的) X-WINDOW 作为 LINUX 下的标准图形窗口界面,它 本身就采用了很多的动态链接库(在/usr/X11R6/lib 目录下),以方便程序间的 共享, 节省占用空间。著名的 APACHE 网页服务器,也采用了动态链接库,以便 扩充程序功能。你只需将 PHP 动态链接库拷到其共享目录,修改一下配置, APACHE 就可以支持 PHP 网页了。 如果你愿意, 可以自己编写动态链接库, APACHE 让 支持你自己定义的网页格式。这就是动态链接的好处。 1、LINUX 下动态链接库的创建 在 LINUX 系统下,创建动态链接库是件再简单不过的事情。只要在编译函数库源 程序时加上-shared 选项即可,这样所生成的执行程序即为动态链接库。从某种 意义上来说, 动态链接库也是一种执行程序。 按一般规则, 程序名应带.so 后缀。 下面举个例子说说。 我准备编写两个函数,一个用于查询当前日期 getdate,一个用于查询当前时间 gettime,并将这两个函数存于动态链接库 my.so 中。为此,需要做以下几项工 作。 1.1 编写用户接口文件 datetime.h,内容如下(每行前面的数字为行号): ———————————————————————1 /* datetime.h : 纵横软件制作中心雨亦奇编写, 2001-06-28. */ 2 3 #ifndef __DATETIME_H 4 5 #define __DATETIME_H 6 7 /* 日期结构 */ 8 typedef struct 9 { 10 int year; 11 int mon; 12 int day; 13 }DATETYPE; 14 15 /* 时间结构 */ 16 typedef struct 17 { 18 char hour; 19 char min; 20 char sec; 21 }TIMETYPE; 22 23 /* 函数原型说明 */ 24 25 #ifdef SHARED 26 int (*getdate)(DATETYPE *d); 27 #else 28 int getdate(DATETYPE *d); 29 #endif 30 31 #ifdef SHARED 32 int (*gettime)(TIMETYPE *t); 33 #else 34 int gettime(TIMETYPE *t); 35 #endif 36 37 #endif 38 ———————————————————————- 这个用户接口文件中,先定义了日期与时间结构,接着定义一下函数的原型。动 态函数与静态函数的原型说明不同的是,动态函数应使用(*函数名)的形式,以 便引用其指针。若要引用文件中的动态函数说明,用户应该定义一下 SHARED 宏, 这样才能使用。 1.2 编写 getdate.c,源程序如下: ———————————————————————1 /* getdate.c : 纵横软件制作中心雨亦奇编写, 2001-06-28. */ 2 3 #include “time.h” 4 #include “datetime.h” 5 6 int getdate(DATETYPE *d) 7 { 8 long ti; 9 struct tm *tm; 10 11 time(&ti); 12 tm=localtime(&ti); 13 d->year=tm->tm_year+1900; 14 d->mon=tm->tm_mon+1; 15 d->day=tm->tm_mday; 16 } 17 ———————————————————————在 getdate 函数中,先调用 time 取得以秒计的系统时间,再用 localtime 函数 转换一下时间结构,最后调整得到正确的日期。 1.3 编写 gettime.c,源程序如下: ———————————————————————1 /* gettime.c : 纵横软件制作中心雨亦奇编写, 2001-06-28. */ 2 3 #include “time.h” 4 #include “datetime.h” 5 6 int gettime(TIMETYPE *t) 7 { 8 long ti; 9 struct tm *tm; 10 11 time(&ti); 12 tm=localtime(&ti); 13 t->hour=tm->tm_hour; 14 t->min=tm->tm_min; 15 t->sec=tm->tm_sec; 16 } 17 ———————————————————————gettime 函数与 getdate 函数相仿,先用 time 函数取得以秒计的系统时间,再 用 localtime 函数转换一下时间结构,最后返回当前的时间(不需调整)。 1.4 编写维护文件 makefile-lib,内容如下: ———————————————————————1 # makefile-lib : 纵横软件制作中心雨亦奇编写, 2001-06-28. 2 3 all : my.so 4 5 SRC = getdate.c gettime.c 6 7 TGT = $(SRC:.c=.o) 8 9 $(SRC) : datetime.h 10 @touch $@ 11 12 %.o : %.c 13 cc -c $? 14 15 # 动态函数库(my.so)生成 16 my.so : $(TGT) 17 cc -shared -o $@ $(TGT) 18 ———————————————————————编 写维护文件的目的,在于方便程序员维护程序,尤其是维护比较大的工程项 目。一个素质良好的程序员应该学会熟练地编写维护文件 makefile。定义了文 件 间的依赖关系后,一旦源文件发生变化,仅需 make 一下,其目标文件维护代 码会自动执行,从而自动更新目标文件,减少了许多工作量。注意: 每行维护代 码必须以 TAB(跳格键)开始,不是的话 make 时将出错。 本维护文件第 1 行是注释行,以#号开头;文件第 3 行定义所有需要维护的 函数 库;第 5 行定义相关源程序文件;第 7 行定义目标文件;第 9-10 行说明所有源 程序依赖于 datetime.h 头文件,并有相应维护代码,即 touch 一下,更新一下 源文件的时间;第 12-13 行定义.o 文件依赖于相应的.c 文件,并指定了维护代 码,即用 cc 编译一下;第 16-17 行定义共享库 my.so 依赖的目标文件,维护代 码中用-shared 编译选项,以生成动态链接库 my.so。 1.5 运行 make -f makefile-lib 命令 make 运行后,动态链接库 my.so 就产生了,我们就可以在程序中调用了。如果 想让系统所有用户都可以使用,则应以 root 用户登录系统,将这个库拷贝到 /lib 目录下(命令:cp my.so /lib),或者在/lib 目录下建个符号连接即可(命 令:ln -s `pwd`/my.so /lib)。 2、LINUX 下动态链接库的使用 2.1 重要的 dlfcn.h 头文件 LINUX 下使用动态链接库,源程序需要包含 dlfcn.h 头文件,此文件定义了调用 动态链接库的函数的原型。下面详细说明一下这些函数。 2.1.1 dlerror 原型为: const char *dlerror(void); 当动态链接库操作函数执行失败时,dlerror 可以返回出错信息,返回值为 NULL 时表示操作函数执行成功。 2.1.2 dlopen 原型为: void *dlopen (const char *filename, int flag); dlopen 用于打开指定名字(filename)的动态链接库,并返回操作句柄。 filename: 如果名字不以/开头,则非绝对路径名,将按下列先后顺序查找该文 件。 (1) 用户环境变量中的 LD_LIBRARY 值; (2) 动态链接缓冲文件/etc/ld.so.cache (3) 目录/lib,/usr/lib flag 表示在什么时候解决未定义的符号(调用)。取值有两个: 1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。 2) RTLD_NOW : 表明在 dlopen 返回前就解决所有未定义的符号,一旦未解决, dlopen 将返回错误。 dlopen 调用失败时,将返回 NULL 值,否则返回的是操作句柄。 2.1.3 dlsym : 取函数执行地址 原型为: void *dlsym(void *handle, char *symbol); dlsym 根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数 的执行代码地址。由此地址,可以带参数执行相应的函数。 如程序代码: void (*add)(int x,int y); /* 说明一下要调用的动态函数 add */ add=dlsym(”xxx.so”,”add”); /* 打开 xxx.so 共享库,取 add 函数地址 */ add(,369); /* 带两个参数 和 369 调用 add 函数 */ 2.1.4 dlclose : 关闭动态链接库 原型为: int dlclose (void *handle); dlclose 用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为 0 时,才会真正被系统卸载。 2.2 在程序中使用动态链接库函数 2.2.1 程序范例 下面的程序装载了动态链接库 my.so,并用 getdate,gettime 取得当前日期与时 间后输出。 ———————————————————————1 /************************************/ 2 /* 文件名称: dy.c */ 3 /* 功能描述: 动态链接库应用示范程序 */ 4 /* 程序编写: 纵横软件制作中心雨亦奇 */ 5 /* 编写时间: 2001-06-28 */ 6 /************************************/ 7 8 #include “stdio.h” /* 包含标准输入输出文件 */ 9 10 #include “dlfcn.h” /* 包含动态链接功能接口文件 */ 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #define SOFILE “./my.so” /* 指定动态链接库名称 */ #define SHARED /* 定义宏,确认共享,以便引用动态函数 */ #include “datetime.h” /* 包含用户接口文件 */ main() { DATETYPE d; TIMETYPE t; void *dp; char *error; puts(”动态链接库应用示范”); dp=dlopen(SOFILE,RTLD_LAZY); /* 打开动态链接库 */ if (dp==NULL) /* 若打开失败则退出 */ { fputs(dlerror(),stderr); exit(1); } getdate=dlsym(dp,”getdate”); /* 定位取日期函数 */ error=dlerror(); /* 检测错误 */ if (error) /* 若出错则退出 */ { fputs(error,stderr); exit(1); } getdate(&d); /* 调用此共享函数 */ printf(”当前日期: %04d-%02d-%02d\\n”,d.year,d.mon,d.day); gettime=dlsym(dp,”gettime”); /* 定位取时间函数 */ error=dlerror(); /* 检测错误 */ if (error) /* 若出错则退出 */ { fputs(error,stderr); exit(1); } gettime(&t); /* 调用此共享函数 */ 55 printf(”当前时间: %02d:%02d:%02d\\n”,t.hour,t.min,t.sec); 56 57 dlclose(dp); /* 关闭共享库 */ 58 59 exit(0); /* 成功返回 */ 60 61 } ———————————————————————程序说明: 第 8 行: 包含标准输入输出头文件,因为程序中使用了 printf,puts,fputs 等标 准输入输出函数,需要让编译器根据头文件中函数的原型,检查一下语法; 第 10-11 行: 包含动态链接库功能头文件,并定义动态链接库名称; 第 13-14 行: 定义宏 SHARED 以便引用 14 行的头文件 datetime.h 中的动态函数 说明; 第 25 行: 用 dlopen 打开 SOFILE 共享库,返回句柄 dp; 第 27-31 行: 检测 dp 是否为空,为空则显示错误后退出; 第 33 行: 用 dlsym 取得 getdate 函数动态地址; 第 35-40 行: 如果 dlerror 返回值不为空,则 dlsym 执行出错,程序显示错误后退 出; 第 42-43 行: 执行 getdate 调用,输出当前日期; 第 45 行: 用 dlsym 取得 gettime 函数动态地址; 第 47-52 行: 如果 dlerror 返回值不为空,则 dlsym 执行出错,程序显示错误后退 出; 第 54-55 行: 执行 gettime 调用,输出当前时间; 第 57 行: 用 dlclose 关闭 dp 所指示的动态链接库; 第 59 行: 程序退出,返回 0 值。 2.2.2 编写维护文件 维护文件 makefile 内容如下: ———————————————————————1 # makefile : 纵横软件制作中心雨亦奇编写, 2001-06-28. 2 3 all : dy 4 5 DYSRC = dy.c 6 7 DYTGT = $(DYSRC:.c=.o) 8 9 %.o : %.c 10 cc -c $? 11 12 # 动态库应用示范程序 13 dy : $(DYTGT) 14 cc -rdynamic -s -o $@ $(DYTGT) -ldl 15 ———————————————————————维护文件说明: 第 3 行: 定义所有需要维护的模块; 第 5 行: 定义源程序; 第 7 行: 定义目标文件; 第 9-10 行: 定义.o 文件依赖于.c 文件,维护代码为“cc -c 变动的源文件名”; 第 13-14 行: 定义 dy 依赖于变量 DYTGT 指示的值,维护代码中采用-rdynamic 选 项以指定输出文件为动态链接的方式,选项-s 指定删除目标文件中的符号表,最 后的选项-ldl 则指示装配程序 ld 需要装载 dl 函数库。 2.2.3 运行 make 命令 运行 make 后将产生执行文件 dy,运行后将产生如下类似信息: 动态链接库应用示范 当前日期: 2001-06-28 当前时间: 10:06:21 当删除 my.so 文件时,将出现以下信息: 动态链接库应用示范 my.so: cannot open shared object file: 文件或目录不存在 3、小结 LINUX 创建与使用动态链接库并不是一件难事。 编译函数源程序时选用-shared 选项即可创建动态链接库,注意应以.so 后缀命 名,最好放到公用库目录(如/lib,/usr/lib 等)下面,并要写好用户接口文件, 以便其它用户共享。 使用动态链接库,源程序中要包含 dlfcn.h 头文件,写程序时注意 dlopen 等函 数的正确调用,编译时要采用-rdynamic 选项与-ldl 选项,以产生可调用动态链 接库的执行代码。 点击这里下载源程序。 Posted in linux | No Comments Linux 的动态连接库 星期一, 11 月 12th, 2007 一. 动态链接库的原理及使用 动态链接库是一种通用的软件组件技术,是多种操作系统中提供基本服务的方 式。比如 Win32 内核就是 3 个 DLL 文件构成。这种技术在 Linux 操作系统下也有 对应的实现,就是 Linux 标准对象 Standard Ojbect,对应的文件扩展名为.so。 大 家对 Windows 操作系统中的 DLL 文件一定十分熟悉,其实这种软件组件化的 方法在 Linux 中也可以实现。其实插件和 DLL 通常是用来无须编写整个新应用 程序而添加功能的极好方法,一般来讲,在不更改原有应用程序的情况下,插件 为现有应用程序提供新功能。Linux 环境下甚至 做的更好。 Linux 提供 4 个库函数、一个头文件 dlfcn.h 以及两个共享库(静态库 libdl.a 和动态库 libdl.so)支持动态链接。 ? ? ? dlopen:打开动态共享目标文件并将其映射到内存中,返回其首地址 dlsym:返回锁请求的入口点的指针 dlerror:返回 NULL 或者指向描述最近错误的字符串 dlclose:关闭动态共享文件 函数 dlopen 需要在文件系统中查找目标文件并为之创建句柄。有四种方法指定 目标文件的位置: ? ? ? ? 绝对路径 在环境变量 LD_LIBRARY_PATH 指定的目录中 在/etc/ld.so.cache 中指定的库列表中 在/usr/lib 或者/lib 中 下面举一个例子。 主程序文件 hello.c: #include #include void* slib=0; void (*func)(char*); const char* hError; int main(int argc,char* argv[]) { slib=dlopen(”./slib.so”,RTLD_LAZY); hError=dlerror(); if (hError) { printf(”dlopen Error!\\n”); return 1; } func=dlsym(slib,”func”); hError=dlerror(); if (hError) { dlclose(slib); printf(”dlsym Error!\\n”); return 1; } func(”How do you do?\\n”); dlclose(slib); hError=dlerror(); if (hError) { printf(”dlclose Error!\\n”); return 1; } return 0; } 函数 dlopen 的第二个参数有两种选择: ? ? RTLD_LAZY:推迟解析 DLL 中的外部引用,直到 DLL 被执行 RTLD_NOW:在返回之前解析所有的外部引用 以下是 DLL 文件源码 slib.c: int func(char* msg) { printf(”func be Executed!\\n”); printf(msg); return 0; } 是不是很简单? 源代码写好后,在编译和链接时有点复杂。为此,我们编写了一个 makefile: all:hello slib.so hello: gcc -o hello hello.c -ldl slib.so:slib.o gcc -shared -lc -o slib.so slib.o slib.o: gcc -c -fpic slib.c 生成这个程序需要三步: ? ? ? 将 DLL 编译为位置无代码 创建 DLL 共享目标文件 编译主程序并与 DLL 相链接 编译 slib.c 时,使用了-fpic 或者-fPIC 选项,使生成的代码是位置无关的,因 为重建共享目标库需要位置无关,并且这类代码支持大的偏移。 创建 DLL 共享目标文件时使用了-shared 选项,该选项产生适合动态链接的共享 目标文件 slib.so。 生成主程序时,使用-ldl 选项,这是链接选项,即主程序中的部分符号为动态 链接库中的符号,也就是说,在运行时需要到 dll 文件中才能够解决引用。 二. 通用类型的动态函数库的建立 Linux 操作系统和各种软件包为软件开发人员提供了很多的动态函数库文件。 但 是一般情况下这些库还不能满足用户的所有需求。 开发人员会根据自己的需要编 写很多的函 数。对于这些函数,如果总是将源文件与调用它们的程序链接起来, 虽然也可以,但是,缺点是显然的。下面就将它们加入动态函数库中。 在 Linux 中,建立动态函数库不需要额外的工具,只需要 gcc 就可以了。 通过 ldd 命令可以很方便的察看程序用到了哪些库。 下面通过一个简单的例子说明动态函数库的建立过程。 文件 mylib.c 是函数库的源程序文件,内容如下: int myadd(int a1, int a2) { return a1+a2; } 文件 testlib.c 是测试程序的源程序文件: #incoude extern int myadd(int, int); int main() { printf(“%d\\n”,myadd(1, 2)); return 0; } 下面给出 makefile 的内容: all:libmylib.so.1.0 testlib libmylib.so.1.0 : mylib.o ld –m elf_i386 –shared –soname libmylib.so.1 –o libmylib.so.1.0 mylib.o ln –sf libmylib.so.1.0 libmylib.so.1 ln –sf libmylib.so.1 libmylib.so testlib : testlib.c gcc –Wall –O2 –L. –lmylib –o testlib testlib.c mylib.o : mylib.c gcc –c –Wall –O2 –fPIC –o mylib.o mylib.c clean : -rm –f libmylib.so* testlib *.o 在 Linux 的 shell 中输入 make 命令,动态函数库 libmylib.so.1.0 和它的测试 程序就生成了。运行./testlib 试试看。 如果你不走运的话,系统会提示找不到 libmylib.so.1 动态函数库,因为系统认 为没有这样的文件或目录。 不要慌。 你可能需要使用 LD_LIBRARY_PATH 环境变量。 [root@localhost home]export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH 再运行一次测试程序吧。 Posted in linux | No Comments 挂载 星期一, 11 月 12th, 2007 ◆挂载概念 在 windows 操作系统中, 挂载通常是指给磁盘分区(包括被虚拟出来的磁盘分区)分配一个盘符。 这个操作可以通过“计算机管理”中的“磁盘管理”来进行。 第三方软件,如磁盘分区管理软件、虚拟磁盘软件等,通常也附带挂载功能。 在 linux 操作系统中, 挂载是一个非常重要的功能,使用非常频繁。 它指将一个设备(通常是存储设备)挂接到一个已存在的目录上。 (这个目录可以不为空,但挂载后这个目录下以前的内容将不可用。) 需要理解的是, linux 操作系统将所有的设备都看作文件, 它将整个计算机的资源都整合成一个大的文件目录。 我们要访问存储设备中的文件, 必须将文件所在的分区挂载到一个已存在的目录 上, 然后通过访问这个目录来访问存储设备。 ◆linux 下挂载指南:(转载自 jaychun.cublog.cn) ◆挂载条件: 1、挂载点必须是一个目录。 2、一个分区挂载在一个已存在的目录上,这个目录可以不为空,但挂载后这个 目录下以前的内容将不可用。对于其他操作系统建立的文件系统的挂载也是这 样。 ◆文件系统格式 需要理解的是:光盘、软盘、其他操作系统使用的文件系统的格式与 linux 使用 的文件 系统格式是不一样的。光盘是 ISO9660;软盘是 fat16 或 ext2; windows NT 是 fat16、NTFS; windows98 是 fat16、fat32; windows2000 和 windowsXP 是 fat16、fat32、NTFS。 挂载前要了解 linux 是否支持所要挂载的文件系统格式。 ◆挂载命令 挂载时使用 mount 命令: 格式:mount [-参数] [设备名称] [挂载点] 其中常用的参数有: -t<文件系统类型> 指定设备的文件系统类型, 常见的有: minix linux 最早使用的文件系统 ext2 linux 目前常用的文件系统 msdos MS-DOS 的 fat,就是 fat16 vfat windows98 常用的 fat32 nfs 网络文件系统 iso9660 CD-ROM 光盘标准文件系统 ntfs windows NT 2000 的文件系统 hpfs OS/2 文件系统 auto 自动检测文件系统 -o<选项> 指定挂载文件系统时的选项。有些也可用在/etc/fstab 中。 常用的有 codepage=XXX 代码页 iocharset=XXX 字符集 ro 以只读方式挂载 rw 以读写方式挂载 nouser 使一般用户无法挂载 user 可以让一般用户挂载设 备 ◆示例 提醒一下,你要把文件系统挂载到哪,首先要先建上个目录。 例子:windows98 装在 hda1 分区,同时计算机上还有软盘和光盘需要挂载。 # mk /mnt/winc # mk /mnt/floppy # mk /mnt/cdrom # mount -t vfat /dev/hda1 /mnt/winc # mount -t msdos /dev/fd0 /mnt/floppy # mount -t iso9660 /dev/cdrom /mnt/cdrom 现在就可以进入/mnt/winc 等目录读写这些文件系统了。 要保证最后两行的命令不出错,要确保软驱和光驱里有盘。 ◆Linux 下分区标识 介绍 Linux 下分区是如何标识的, 通常是 hdyN (IDE 磁盘)或 sdyN(SCSI 磁盘)。 y 这个字母标明分区所在的设备。 例如,/dev/hda(第一个 IDE 磁盘)或 /dev/sdb(第二个 SCSI 磁盘) N 这个数字代表分区的编号。 前四个分区 (主分区或扩展分区) 是用数字从 1 排列到 4。 逻辑分区从 5 开始。 例如,/dev/hda3 是在第一个 IDE 硬盘上的第三个主分区或扩展分区; /dev/sdb6 是在第二个 SCSI 硬盘上的第二个逻辑分区。 ◆中文支持 如果你的 windows98 目录里有中文文件名,使用上面的命令挂载后,显示的是一 堆乱码。 这就要用到 -o 参数里的 codepage iocharset 选项。 codepage 指定文件系统的代码页,简体中文中文代码是 936; iocharset 指定字符集,简体中文一般用 cp936 或 gb2312。 当挂载的文件系统 linux 不支持时,mount 一定报错,如 windows2000 的 ntfs 文件系统。 可以重新编译 linux 内核以获得对该文件系统的支持或者下个支持 ntfs 的补丁。 ◆自动挂载 每次开机访问 windows 分区都要运行 mount 命令显然太烦琐, 我们可以设定让 linux 在启动的时候也挂载我们希望挂载的分区,如 windows 分区, 以实现文件系统的自动挂载。 在/etc 目录下有个 fstab 文件,它里面列出了 linux 开机时自动挂载的文件系 统的列表。 我的/etc/fstab 文件如下: LABEL=/ / ext3 defaults 1 1 LABEL=/boot /boot ext3 defaults 1 2 none /dev/pts devpts gid=5,mode=620 0 0 none /proc proc defaults 0 0 none /dev/shm tmpfs defaults 0 0 /dev/hda8 swap swapdefaults 0 0 /dev/cdrom /mnt/cdrom udf,iso9660 noauto, owner, kudzu, ro 0 0 /dev/fd0 /mnt/floppy auto noauto,owner,kudzu 0 0 /dev/cdrom1 /mnt/cdrom1 udf, iso9660 noauto, owner, kudzu, ro 0 0 /dev/hdb1 /mnt/winc ntfs defaults,codepage=936,iocharset=cp936 0 0 /dev/hda5 /mnt/wind vfat defaults,codepage=936,iocharset=cp936 0 0 /dev/hda6 /mnt/winf vfat defaults,codepage=936,iocharset=cp936 0 0 在/etc/fstab 文件里, 第一列是挂载的文件系统的设备名, 第二列是挂载点, 第三列是挂载的文件系统类型, 第四列是挂载的选项, 选项间用逗号分隔。 在最后三行是我手工添加的 windows 下的 C、D、F 盘, 加了 codepage=936 和 iocharset=cp936 参数以支持中文文件名。 参数 defaults 实际上包含了一组默认参数: rw 以可读写模式挂载 suid 开启用户 ID 和群组 ID 设置位 dev 可解读文件系统 上的字符或区块设备 exec 可执行二进制文件 auto 自动挂载 nouser 使一般用户无法挂载 async 以非同步方式执行文件系统的输入输出 操作 大家可以看到在这个列表里,光驱和软驱是不自动挂载的,参数设置为 noauto。 ◆挂载 ISO 文件 挂载 ISO 格式的命令: mount -loop -o iocharset=cp936 xxxxx.iso /mnt/iso Posted in linux | No Comments 实验二 Shell 编程 星期一, 11 月 12th, 2007 Shell 编程 Shell 编程 [ 实验目的 ] 学习如何编写一个 Unix Shell 程序,使得有机会了解如何创建子进程来执行一 项专门的工作以及父进程如何继续进行子进程的工作。 [ 功能要求 ] 编写一个 C 语言程序作为 Linux 内核的 Shell 命令行解释程序, 所执行的结果需 和系统命令方式保持一致。基本运行方式如下: 当敲入命令行如:identifier[identifier[identifier]],Shell 应该解析命令 行参数指针数组 argv[argc]。 Shell 程序需要具有以下几种功能和健壮性: 1. 2. 支持目录检索功能,即文件不存在,继续打印提示符 支持以“&”结束的输入,进行并发执行(前台与后台) 3. 4. 5. 支持输入输出重定向,“<”,“>”为标志符 支持以“|”进行进程间通信操作(管道功能) 支持一定的错误输入处理。 [ 函数功能与参数说明 ] 子函数的功能与参数: ◆ void get_string(char command[]): 功能:接受用户输入的命令字符串。 参数:command[]存放用户输入的字符串。 ◆ int split(char* argv[],char usrComm[]): 功能:将用户输入的命令字符串分割成一个一个的单词,其中“<”, “>”, “|” 也各自当做一个单独的单词,同时若有这些符号,将相应标志位置成相应值。 参数:usrComm[]存放的是待分割的命令,argv[]是存放分割好的单词的数组, 返回值是分割后得到的单词数目(传给 argc)。 ◆ int set_Background(char *argv[],int argc): 功能: 检测是否存在并发执行的符号 “&” 并设置并发执行的标志 , (全局变量) 。 参数:argv[]是将用户输入的命令进行分割后的单词数组,argc 是命令中单词 个数,由于&符号占一个单词的数目,所以如果发现&,命令中单词个数 argc 实 际上应该减一,故有一个返回值给 argc。 ◆ int delet_space(int ptr,char usrComm[]): 功能:过滤掉用户输入字符串的当前分析位置开始的连续空格 参数:usrComm[]是正在分析的用户输入的命令,ptr 指示 usrComm[]中当前待分 析的字符下标。返回连续空格后面第一个不是空格的字符下标,即过滤掉连续空 格后的分析位置。 ◆ int get_a_word(char *argv1,int ptr,char com[]): 功能:从用户输入字符串的当前待分析的字符开始向后分析出一个单词,或者一 个符号(<、>、|)。 参数:argv1 存放这个分析出来的单词,ptr 代表待分析的字符的数组下标,com 代表用户输入的字符串。返回值是这个单词之后的第一个字符的数组下标。 ◆ void setOutFile(char outFile[]): 功能:设置输出重定向到文件。 参数:outFile[],输出文件名称。 ◆ void setInFile(char inFile[]): 功能:设置输入重定向到文件。 参数:inFile[],输入文件名称。 ◆ int GetPath(char *paths[]): 功能:将从 PATH 读出的绝对路径列表分割成一个一个的单独路径。 参数:*paths[]存放分割好的存放一组路径的数组,返回这组路径的个数。 ◆ void ScanFile(char argv1[],char pathname[]): 功能:在从 PATH 分割出来的一组路径种,寻找 argv1 命令对应的文件,确定用 户输 入的命令文件的位置。 参数: argv1 是用户输入的命令, pathname[]用来存放带有正确的路径的文件名, 即“路径”+“/”+“命令名”。 ◆ void Execute(char *[],int); 功能:执行没有特殊功能(即无重定向或管道)的用户命令。 参数:argv[]存放的是用户的命令和参数,argc 是命令和参数的个数。 ◆ void ExecuteF1(char *argv[],int argc): 功能:执行带输入或输出重定向功能的用户命令。 参数:argv[]存放的是用户的命令和参数,argc 是命令和参数的个数。 ◆ void ExecuteP1(char *argv[],int argc): 功能:执行代管道功能的用户命令。 参数:argv[]存放的是用户的命令和参数,argc 是命令和参数的个数。 ◆ void ExecuteCd(char argv1[]); 功能:执行 cd 命令,当有错误时显示错误信息(因为 cd 命令是 shell 自身实现 的,并不是某个可执行文件)。 参数:argv[]存放的是 cd 后面的参数 主 函数功能: 设置一个循环体,在循环体内,首先获得当前路径,打印提示 符,然后接受用户输入,get_string(_usrComm),判断输入是否是 leave, 若 是,跳出循环;若否,调用 split(argv,argc,_usrComm)解析用户输入,并将命 令及参数信息填入 argv 以及 argc,接着判断 argc 是否为 0,即是否为空输入, 若是,进行下一次循环;若不是,判断 argv[0]是否为 cd,若是,执行 ExecuteCd(argv1[]),然 后进行下一次循环;若否,根据标志位判断是否有重 定向或管道,进行决定执行哪个函数(ExecuteF1,ExecuteP1, Execute)。然 后进行下一次循环。 [ 主要功能设计说明 ] 程序完成的主要功能如下: 运 行目标代码后,在屏幕上打印当前路径的提示符,当敲入命令行如: identifier[identifier[identifier]],就解析命令行参 数指针数组 argv[argc]。然后执行。其中,支持“cd”命令,支持空输入,支持多余空格, 支持 I/O 重定向,支持管道,支持后台执行。下面分部分 进行功能说明: 1.打印提示符: 每次接收一个新的命令之前,都要在屏幕上打印提示符,[current_dir_name], current_dir_name 是当前路径。 2.接受用户输入并解析: 当 用户输入一行命令然后回车时,接收这行字符串,然后进行解析,要把其中 的命令和参数一一分离,然后填充到 argv 中,且要把命令和参数的个数赋给 argc。同时,在分析过程中,如果遇到“<”、“>”、“|”、“&”这些标志, 应该把相应的标志位变量赋上值。 4.执行命令 根 据命令以及标志位的值,转到相应的执行处理模块。如果是 cd 命令,那么就 转到执行 cd 命令的模块,因为 cd 命令是 shell 自己实现的。如果带有 “>”、 “<”,则转到带有输入输出重定向的模块执行。若带有“|”,则转到带有管道 功能的模块执行。若没有这两个符号,则转到普通的执行命 令模块。而“&”, 后台执行,则是在具体执行时决定是否阻塞父进程。 在每个执行命令模块中,要能判断该命令是否可执行,即该命令文件是否存在, 这就要利用环境变量。 执行命令(不包括 cd)通过创建一个子进程来实现。 对 于输入重定向,要能把输出流重定向到用户在“<”符号后面指定的文件中, 即输出都流到这个文件中,如果该文件不存在,要创建它;对于输出重定向,要 能把输入流重定向到“<”符号后面指定的文件中,即输入流均来自这个文件, 输入文件必须存在,若不存在,给出提示。 对于管道,当以 command1|command2 形式出现时,command1 的输出流将作为 command2 的输入流。即后一个进程的输入来自第一个进程的输出。故第二个进 程要等到第一个进程执行完毕才可以往下进行。 对 于并行执行,当没有这个符号时,每次接收并执行下一个用户命令时,都要 等到上一个用户命令执行完毕, 而当以 “&” 结束时, 立即开始接收下一个命 令, 不必等到这个命令执行完毕,反映在程序中,就是父进程不必等到子进程结束返 回就可以继续执行,不必为等待子进程而阻塞自己。 [ 程序设计实现说明 ] 分几个模块进行说明: 1.打印提示符: 通过系统内部定义好的函数 get_current_dir_name()实现,返回一个指向字符 串的指针,程序如下: p=get_current_dir_name(); printf(”[%s]”,p); 这样既可以 在屏幕上打印[当前路径]: 2.接受用户输入 get_string(char command[]): 即通过一个循环,每次接收一个字符,直到接收到回车为止。 ch=getchar(); while(ch!=’\\n’) { command[ptr++]=ch; ch=getchar(); } command[ptr]=”; 3.解析用户输入命令 split(char* argv[],char usrComm[]): 需要两个辅助子函数,一个是删除连续空格,即如果是空格,就一直向下分析。 int delet_space(int ptr,char usrComm[]) { while(usrComm[ptr]==’ ‘) ptr++; return ptr; } 一个是得到一个单词,其中“>”、“<”、“|”、“&”都单独算一个单词,若 遇到这些字符,则:直接得到一个单词,若是字母,数字或“_”、“/”、“.” 等符号,就继续分析直到不是这些符号,之前的这些字符就组成一个单词。 int get_a_word(char *argv1,int ptr,char com[]) { char temp1[20]={}; int i=0; if(com[ptr]==’>’||com[ptr]==’<’||com[ptr]==’|'||com[ptr]==’&’)// 符号单独算一个单词 { } else while((com[ptr]>=’a'&&com[ptr]<=’z')||(com[ptr]>=’A'&&com[ptr]<=’ Z')||com[ptr]==’-'||com[ptr]==’_'||com[ptr]==’ /'||com[ptr]==’.'||(com[ptr]>=’0′&&com[ptr]<=’9′)) { } temp1[i]=”;//结束符 temp1[i++]=com[ptr++]; temp1[i++]=com[ptr++]; strcpy(argv1,temp1); return ptr;//返回此时的分析位置 } 还有一个是设置后台标志的符号 int set_Background(char *argv[],int argc) { Bkg=-1; if(strcmp(argv[argc-1],”&”)==0) //如果最后一个是“&”符号 { Bkg=1; argc=argc-1; argv[argc]=NULL; } return argc; } split 的实现方法如下:首先过滤掉前面的空格 delet_space(int ptr,char usrComm[]),然后进入一个循环体,循环条件是命令未分析完。在循环体内,每 次调用 get_a_word(char *argv1,int ptr,char com[])获得一个单词,然后命 令和参数个数增一,且过滤之后的空格,进入下一次循环。 int { split(char* argv[],int argc,char usrComm[]) int ptr=0; int i=0; ptr=delet_space(ptr,usrComm); //过滤前面多余空格 outfile=-1;infile=-1;Ppipe=-1; while(usrComm[ptr]!=”) //标志位复位 //待分析串未结束 { argv[i]=malloc(sizeof(char)*30); ptr=get_a_word(argv[i],ptr,usrComm); //得到一个单词 if(strcmp(argv[i],”>”)==0) //置输出重定向的标志位,其值代表出现这个符号的单词的序号 outfile=i; else if(strcmp(argv[i],”<”)==0) //置输入重定向的标志位,其值代表出现这个符号的单词的序号 infile=i; else if(strcmp(argv[i],”|”)==0) //置管道的标志位,其值代表出现这个符号的单词的序号 Ppipe=i; i++; argc++; ptr=delet_space(ptr,usrComm);//过滤中间多余空格 } if(argc>=1) //如果命令和参数个数大于 1 //个数增一 argc=set_Background(argv,argc); //设置&后台标志位 argv[i]=NULL; return argc; } //最后一个赋空代表结束 4.寻找命令文件,判断命令是否可执行: 为 了判断命令是否可执行,在程序最前面首先要调用 GetPath(char *paths[]) 得到路径数组,这里要用到系统定义好的函数 getenv(),得到的是以一个字符 串形式存在的路径列表,为了方便使用,要把它分解到以 单个路径存在的数组 中。因路径之间以“:”分割,所以可以利用一个循环,每次碰到“:”,就把 之前的路径提取出来作为一个单独路径。 int GetPath(char *paths[]) { char *pa; int i=0; int ptr=0; int pn=0; char temp[30]={}; pa=getenv(”PATH”); while(pa[ptr]!=”) { while(pa[ptr]!=’:'&&pa[ptr]!=”) //得到路径列表 //未碰到“:”,且未结束 { } temp[i]=”; paths[pn]=malloc(sizeof(temp)); strcpy(paths[pn],temp); if(pa[ptr]!=’’) ptr++; i=0; pn++; //放到 paths 数组中 //如果未结束 temp[i++]=pa[ptr++]; } } return pn; } 然后当要寻找命令文件时,依次将每个路径和 argv[0]组合判断这个文件是否存 在,若存在,当前这个路径和文件名就是该文件的位置。 char* ScanFile(char argv1[],char pathname[]) { int i=0; int p=0; for(i=0;i<20;i++) pathname[i]=’ ‘; for(i=0;i 和参数的数组,以 NULL 代表结束。调用 waitpid()阻塞父进程等待子进程的完 成。下面具体介绍这三个系统调用: 实现执行没有特殊功能的用户命令: void Execute(char *argv[],int argc) { char pathname[20]={”\ if(ScanFile(argv[0],pathname)==NULL) //判断命令文件是否存在 return ; pid_t pid4=fork(); if(pid4==0) execvp(argv[0],argv); else if(Bkg!=1) waitpid(pid4,NULL,0); //阻塞父进程,等待 ID 号为 pid4 的子进程结束 } 6.输入输出重定向的实现: 通 过操纵子进程的文件描述符来重定向 I/O。一个新创建的子进程将继承其父 进程的打开文件描述符,特别是同样将键盘作为 stdin 以及将终端显示器作为 stdout 和 stderr。每个进程在内核中都有自己的文件描述符表,其中 stdin 绑 定到第 0 项, stdout 绑定到第 1 项, stderr 绑定到第 2 项。 由于 open(), dup() 总是使用文件描述符中最早可用的表项. 其中:int dup (int oldfd),用来复制参数 oldfd 所指的文件描述词,并将它 返回。此新的文件描述词和参数 oldfd 指的是同一个文件,共享所有的锁定、读 写位置和各项权限或旗标。 这样,可以通过关闭我们想要重定向的流文件,然后利用 dup()重新定向到用户 制定的文件中。 void setOutFile(char outFile[]) //设置输出重定向 //若不是后台执行 //子进程执行用户输入命令 //不可执行返回 //创建一个子进程,ID 号为 pid4 { int fid=open(outFile,O_WRONLY|O_CREAT); 户指定文件 close(1); dup(fid); close(fid); } void setInFile(char inFile1[]) { int fid2=open(inFile1,O_RDONLY); //设置输入重定向 //关闭 stdout //将 stdout 重定向到 fid 中 //关闭 fid //打开用 //打开用户指定文件 close(0); dup(fid2); close(fid2); } 利用这两个函数,就可以实现带有输入输出重定向的用户命令的执行,只要在上 文提到的 Execute 函数中 pid==0 后面的代码部分有所变化。另外,在输入重 定向之前,要对输入文件是否存在进行检测: void ExecuteF1(char *argv[],int argc) { char pathname[20]={”\//关闭 stdin //将 stdin 重定向到 fid 中 //关闭 fid2 char *p; p=get_current_dir_name(); if(ScanFile(argv[0],pathname)==NULL) return ; if(infile>=0) { strcat(p,”/”); //得到输入文件完整路径名 //若有输入重定向 strcat(p,argv[infile+1]); if(access(p,F_OK)!=0) { //检测该文件是否存在 printf(”No such File!\\n”); return; } } pid_t pid1=fork(); if(pid1==0) { { if(outfile>=0) setOutFile(argv[outfile+1]); //调用输出重定向文件设置 argv[outfile]=NULL; } if(infile>=0) { setInFile(argv[infile+1]); //调用输入重定向文件设置 argv[infile]=NULL; } execv(pathname,argv); } else if(Bkg!=1) waitpid(pid1,NULL,0); } 这样就可以实现带有“>”、“<”符号的命令的执行,包括有一个“>”或“<” 以及既有“>”又有“<”的命令的执行。 6.管道的实现: 通 过函数 int pipe(int filedes[2]),pipe()会建立管道,并将文件描述词由 参数 filedes 数组返回。filedes[0]为管道里的读取端,filedes [1]则为管道 的写入端。若成功则返回零,否则返回-1,错误原因存于 errno 中。 这样,通过创建两个进程,第一个子进程执行管道符前的命令,并将输出重定向 到管道的写端, 第二个进程执行管道符后的指令, 并将输入重定向到管道的读端, 从管道读入。 void ExecuteP1(char *argv[],int argc) { char pathname1[20]={”\ char pathname2[20]={”\判断第一个进程命令文件是否存在 if(ScanFile(argv[Ppipe+1],pathname2)==NULL) return ; //判断第二个进程命令文件是否存在 //因为是两个命令要执行,所以两个文件都要查看是否存在。 int A_to_B[2]; pipe(A_to_B); pid_t pid2=fork(); if(pid2==0) { argv[Ppipe]=NULL; //创建管道 //创建第一个子进程 //该位置命令置空,代表命令参数结束 close(A_to_B[0]); close(1); dup(A_to_B[1]); //关闭管道的读端 //关闭 stdout //将 stdout 重定向到管道写端 execv(pathname1,argv); } else { waitpid(pid2,NULL,0); //执行 //等待第一个进程执行完毕 close(A_to_B[1]); } pid_t pid3=fork(); if(pid3==0) { int i,p; //创建第二个子进程 //关闭管道写端 for(i=Ppipe+1,p=0;i 定义函数 double atof(const char *nptr); 函数说明 atof()会扫描参数 nptr 字符串,跳过前面的空格字符,直到遇上数字或正 负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结 果返回。参数 nptr 字符串可包含正负号、小数点或 E(e)来表示指数部分,如 123.456 或 123e-2。 返回值 返回转换后的浮点型数。 附加说明 atof()与使用 strtod(nptr,(char**)NULL)结果相同。 范例 /* 将字符串 a 与字符串 b 转换成数字后相加*/ #include main() { char *a=”-100.23”; char *b=”200e-2”; float c; c=atof(a)+atof(b); printf(“c=%.2f ”,c); } 执行 c=-98.23 atoi(将字符串转换成整型数) 相关函数 atof,atol,atrtod,strtol,strtoul 表头文件 #include 定义函数 int atoi(const char *nptr); 函数说明 atoi()会扫描参数 nptr 字符串,跳过前面的空格字符,直到遇上数字或正 负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结 果返回。 返回值 返回转换后的整型数。 附加说明 atoi()与使用 strtol(nptr,(char**)NULL,10);结果相同。 范例 /* 将字符串 a 与字符串 b 转换成数字后相加*/ #include mian() { char a[]=”-100”; char b[]=”456”; int c; c=atoi(a)+atoi(b); printf(c=%d ”,c); } 执行 c=356 atol(将字符串转换成长整型数) 相关函数 atof,atoi,strtod,strtol,strtoul 表头文件 #include 定义函数 long atol(const char *nptr); 函数说明 atol()会扫描参数 nptr 字符串,跳过前面的空格字符,直到遇上数字或正 负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结 果返回。 返回值 返回转换后的长整型数。 附 加说明 atol()与使用 strtol(nptr,(char**)NULL,10);结果相同。 范例 /*将字符串 a 与字符串 b 转换成数字后相加*/ #include main() { char a[]=”1000000000”; char b[]=” 2345670”; long c; c=atol(a)+atol(b); printf(“c=%d ”,c); } 执行 c=12345670 gcvt(将浮点型数转换为字符串,取四舍五入) 相关函数 ecvt,fcvt,sprintf 表头文件 #include 定义函数 char *gcvt(double number,size_t ndigits,char *buf); 函数说明 gcvt()用来将参数 number 转换成 ASCII 码字符串, 参数 ndigits 表示显示 的位数。gcvt()与 ecvt()和 fcvt()不同的地方在于,gcvt()所转换后的字符串 包含小数点或正负符号。若转换成功,转换后的字符串会放在参数 buf 指针所指 的空间。 返回值 返回一字符串指针,此地址即为 buf 指针。 附加说明 范例 #include main() { double a=123.45; double b=-1234.56; char *ptr; int decpt,sign; gcvt(a,5,ptr); printf(“a value=%s ”,ptr); ptr=gcvt(b,6,ptr); printf(“b value=%s ”,ptr); } 执行 a value=123.45 b value=-1234.56 strtod(将字符串转换成浮点数) 相关函数 atoi,atol,strtod,strtol,strtoul 表头文件 #include 定义函数 double strtod(const char *nptr,char **endptr); 函数说明 strtod()会扫描参数 nptr 字符串,跳过前面的空格字符,直到遇上数字或 正负符号才开始做转换,到出现非数字或字符串结束时('')才结束转换,并将结 果返回。若 endptr 不为 NULL,则会将遇到不合条件而终止的 nptr 中的字符指 针由 endptr 传回。参数 nptr 字符串可包含正负号、小数点或 E(e)来表示指数 部分。如 23.456 或 123e-2。 返回值 返回转换后的浮点型数。 附加说明 参考 atof()。 范例 /*将字符串 a,b,c 分别采用 10,2,16 进制转换成数字*/ #include mian() { char a[]=”1000000000”; char b[]=”1000000000”; char c[]=”ffff”; printf(“a=%d ”,strtod(a,NULL,10)); printf(“b=%d ”,strtod(b,NULL,2)); printf(“c=%d ”,strtod(c,NULL,16)); } 执行 a=1000000000 b=512 c=65535 strtol(将字符串转换成长整型数) 相关函数 atof,atoi,atol,strtod,strtoul 表头文件 #include 定义函数 long int strtol(const char *nptr,char **endptr,int base); 函数说明 strtol()会将参数 nptr 字符串根据参数 base 来转换成长整型数。参数 base 范围从 2 至 36,或 0。参数 base 代表采用的进制方式,如 base 值为 10 则采用 10 进制,若 base 值为 16 则采用 16 进制等。当 base 值为 0 时则是采用 10 进制做转换, 但遇到如'0x'前置字符则会使用 16 进制做转换。 一开始 strtol() 会扫描参数 nptr 字符串,跳过前面的空格字符,直到遇上数字或正负符号才开 始做转换,再遇到非数字或字符串结束时('')结束转换,并将结果返回。若参数 endptr 不为 NULL,则会将遇到不合条件而终止的 nptr 中的字符指针由 endptr 返回。 返回值 返回转换后的长整型数,否则返回 ERANGE 并将错误代码存入 errno 中。 附加说明 ERANGE 指定的转换字符串超出合法范围。 范例 /* 将字符串 a,b,c 分别采用 10,2,16 进制转换成数字*/ #include main() { char a[]=”1000000000”; char b[]=”1000000000”; char c[]=”ffff”; printf(“a=%d ”,strtol(a,NULL,10)); printf(“b=%d ”,strtol(b,NULL,2)); printf(“c=%d ”,strtol(c,NULL,16)); } 执行 a=1000000000 b=512 c=65535 strtoul(将字符串转换成无符号长整型数) 相关函数 atof,atoi,atol,strtod,strtol 表头文件 #include 定义函数 unsigned long int strtoul(const char *nptr,char **endptr,int base); 函数说明 strtoul()会将参数 nptr 字符串根据参数 base 来转换成无符号的长整型 数。参数 base 范围从 2 至 36,或 0。参数 base 代表采用的进制方式,如 base 值为 10 则采用 10 进制,若 base 值为 16 则采用 16 进制数等。当 base 值为 0 时则是采用 10 进制做转换,但遇到如'0x'前置字符则会使用 16 进制做转换。一 开始 strtoul()会扫描参数 nptr 字符串,跳过前面的空格字符串,直到遇上数 字或正负符号才开始做转换,再遇到非数字或字符串结束时('')结束转换,并将 结果返回。若参数 endptr 不为 NULL,则会将遇到不合条件而终止的 nptr 中的 字符指针由 endptr 返回。 返回值 返回转换后的长整型数,否则返回 ERANGE 并将错误代码存入 errno 中。 附加说明 ERANGE 指定的转换字符串超出合法范围。 范例 参考 strtol() toascii(将整型数转换成合法的 ASCII 码字符) 相关函数 isascii,toupper,tolower 表头文件 #include tolower(将大写字母转换成小写字母) 相关函数 isalpha,toupper 表头文件 #include 定义函数 int tolower(int c); 函数说明 若参数 c 为大写字母则将该对应的小写字母返回。 返回值 返回转换后的小写字母,若不须转换则将参数 c 值返回。 附加说明 范例 /* 将 s 字符串内的大写字母转换成小写字母*/ #include
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- obuygou.com 版权所有 赣ICP备2024042798号-5
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务