学习《精通UNIX下C语言编程及项目实践》.docx
《学习《精通UNIX下C语言编程及项目实践》.docx》由会员分享,可在线阅读,更多相关《学习《精通UNIX下C语言编程及项目实践》.docx(26页珍藏版)》请在冰豆网上搜索。
学习《精通UNIX下C语言编程及项目实践》
学习《精通UNIX下C语言编程及项目实践》
1.第一篇:
起步篇
千里之行,始于足下。
一、UNIX初步
UNIX自1969年诞生以来,已经发展为SystemIII&V、BSD和Linux三大分支。
UniX通过shell与用户交互,它是用户与系统间的界面。
使用好shell对于学习使用UNIX来说是必须的。
不需要你记住所有的命令,但基础的文件操作、目录操作及系统命令等却是必须的。
Vi对于初学者是难点,不过只要通过一段时间的练习就能习惯;而且你会很快发现,它功能强大、更加灵活。
这里不多说了:
)
二、编程套件
学习UNIX对于初学者有几种选择。
一种就是最直接的,在本机上安装UNIX系统,不如说Linux分支中的RedHat等;一种是在Windows环境下使用虚拟机方式安装UNIX系统;另一种则是使用网络终端登录到网络环境中的某个UNIX系统中。
如果你相对黑洞洞的UNIX字符界面更喜欢舒适美观的Windows界面,那么推荐你选择第三种方式。
开发套件包括:
编辑器UltraEdit、网络终端SecureCRT或其他。
编译器就不用说了,自然是cc(gcc或xlc系列)。
这是C语言开发必不可少的。
其中要注意-I(加载头文件路径)、-L(加载库文件路径)及-D(宏定义)参数的使用。
Make工具使用。
如何编写makefile是关键。
后面项目中使用时会详细介绍。
Gdb调试器。
Gdb乃符号级调试工具,它控制程序的内部执行,利用断点设置、单步运行等手段,将程序的执行过程逐步展示在调试者目前。
这种调试方式在短代码中可以发挥得很好。
事实上,随着软件项目的扩大化、复杂化和分布化,很少有程序员直接通过Gdb等工具调试;使用日志记录调试方法比Gdb等调试工具更为便捷和广泛。
C工具:
lint检查源代码是否正确,gprof分析程序时间消费量,cflow生成C语言流程图。
三、库的使用
库分静态库和动态库两种。
静态库的操作工具:
ar命令。
编写及使用静态库:
(1)设计库源码pr1.c和pr2.c
[root@billstonemake_lib]#catpr1.c
voidprint1()
{
printf("Thisisthefirstlibsrc!
\n");
}
[root@billstonemake_lib]#catpr2.c
voidprint2()
{
printf("Thisisthesecondsrclib!
\n");
}
(2)编译.c文件
[bill@billstonemake_lib]$cc-O-cpr1.cpr2.c
[bill@billstonemake_lib]$ls-lpr*.o
-rw-rw-r--1billbill8044月1511:
11pr1.o
-rw-rw-r--1billbill8044月1511:
11pr2.o
(3)链接静态库
为了在编译程序中正确找到库文件,静态库必须按照lib[name].a的规则命名,如下例中[name]=pr.
[bill@billstonemake_lib]$ar-rsvlibpr.apr1.opr2.o
a-pr1.o
a-pr2.o
[bill@billstonemake_lib]$ls-l*.a
-rw-rw-r--1billbill18224月1511:
12libpr.a
[bill@billstonemake_lib]$ar-tlibpr.a
pr1.o
pr2.o
(4)调用库函数代码main.c
[bill@billstonemake_lib]$catmain.c
intmain()
{
print1();
print2();
return0;
}
(5)编译链接选项
-L及-l参数放在后面.其中,-L加载库文件路径,-l指明库文件名字.
[bill@billstonemake_lib]$gcc-omainmain.c-L./-lpr
[bill@billstonemake_lib]$ls-lmain*
-rwxrwxr-x1billbill118054月1511:
17main
-rw-rw-r--1billbill504月1511:
15main.c
(6)执行目标程序
[bill@billstonemake_lib]$./main
Thisisthefirstlibsrc!
Thisisthesecondsrclib!
[bill@billstonemake_lib]$
编写动态库:
(1)设计库代码
[bill@billstonemake_lib]$catpr1.c
intp=2;
voidprint(){
printf("Thisisthefirstdllsrc!
\n");
}
[bill@billstonemake_lib]$
(2)生成动态库
[bill@billstonemake_lib]$gcc-O-fpic-shared-odl.sopr1.c
[bill@billstonemake_lib]$ls-l*.so
-rwxrwxr-x1billbill65924月1515:
19dl.so
[bill@billstonemake_lib]$
动态库的隐式调用:
在编译调用库函数代码时指明动态库的位置及名字,看下面实例
[bill@billstonemake_lib]$catmain.c
intmain()
{
print();
return0;
}
[bill@billstonemake_lib]$gcc-otdlmain.c./dl.so
[bill@billstonemake_lib]$./tdl
Thisisthefirstdllsrc!
[bill@billstonemake_lib]$
当动态库的位置活名字发生改变时,程序将无法正常运行;而动态库取代静态库的好处之一则是通过更新动态库而随时升级库的内容.
动态库的显式调用:
显式调用动态库需要四个函数的支持,函数dlopen打开动态库,函数dlsym获取动态库中对象基址,函数dlerror获取显式动态库操作中的错误信息,函数doclose关闭动态库.
[bill@billstonemake_lib]$catmain.c
#include
intmain()
{
void*pHandle;
void(*pFunc)();//指向函数的指针
int*p;
pHandle=dlopen("./d1.so",RTLD_NOW);//打开动态库
if(!
pHandle){
printf("Can'tfindd1.so\n");
exit
(1);
}
pFunc=(void(*)())dlsym(pHandle,"print");//获取库函数print的地址
if(pFunc)
pFunc();
else
printf("Can'tfindfunctionprint\n");
p=(int*)dlsym(pHandle,"p");//获取库变量p的地址
if(p)
printf("p=%d\n",*p);
else
printf("Can'tfindintp\n");
dlclose(pHandle);//关闭动态库
return0;
}
[bill@billstonemake_lib]$gcc-otdsmain.c-ldl
[bill@billstonemake_lib]$./tds
Thisisthefirstdllsrc!
p=2
[bill@billstonemake_lib]$
上面的程序tds显式调用了共享库d1.so中的函数print和变量p.
第二篇:
文件子系统
普天之下,莫非王土;率土之滨,莫非王臣.UNIX之中,莫非文件.
四、文件系统结构
磁盘在使用前,需要分区和格式化.格式化操作将在磁盘分区中创建文件系统,它们将确定文件的存储方式和索引方法,确定磁盘空间分配和回收算法.
UNIX文件系统的存储由<目录-i节点-数据块>三级构成,其中目录存储了文件的层次结构,数据块存储了文件的具体信息,i节点是连接文件层次结构与其数据内容的桥梁.
UNIX文件系统将磁盘空间划分为一系列大小相同的块,划分为引导块、超级块、i节点区和数据区四个部分.
文件系统通过i节点对文件进行控制和管理.其中,每个文件对应一个i节点,每个i节点具有唯一的节点号,记录了文件的属性和内容在磁盘上的存储位置.但文件名并不记录在i节点里,而是存储在目录文件中.
磁盘文件如何存储?
文件系统通过目录记载文件名及其对应的i节点编号,通过i节点记录文件的信息和内容.事实上,i节点直接记录的只是文件的属性,文件的具体内容存储在数据区的数据块中,i节点中仅保留了一个<磁盘地址表>来记录文件内容存储的位置.
<磁盘文件表>由13个块号组成,每个块号占用4个字节,代表了数据区中的一个数据块编号.UNIX文件系统采用三级索引结构存储文件,它把<磁盘地址表>分为直接索引地址,一级索引地址,二级索引地址和三级索引地址等四个部分.其中前10项为直接索引地址,直接指向文件数据所在磁盘快的块号.第11/12/13项分别为一级/二级/三级索引地址.一级间接索引的含义在于其存储的并非文件数据所在磁盘块的块号,而是先指向一个<磁盘块号表>然后再指向具体磁盘块的块号.同理,二级/三级间接索引则是先间接指向了两次<磁盘块号表>才指向具体磁盘块的块号.
如果文件系统的数据块大小为1kB,每个<磁盘块号表>能够记录256个数据项.那么,直接索引能管辖10个数据块,而一级索引能管辖1*256个数据块,二级索引能管辖1*256*256(65536)个数据块,三级索引能管辖1*256*256*256(16777216)个数据块.
例题:
大小为56000K的文件,占用多少索引块空间?
答:
因为(10+256)<56000<(10+256+65536),故该文件具有二级间接索引.(56000-10-256)/256=217.7,则文件需要二级间接索引块为218个,所以总索引块需要1(一级间接索引块)+1(二级间接索引块)+218=220.
磁盘文件读取示例(仿ls命令)
通过stat结构中st_mode判断文件类型
intGetFileType(mode_tst_mode,char*resp){
if(resp==NULL)
return0;
if(S_ISDIR(st_mode))resp[0]='d';//使用宏定义判断
elseif(S_ISCHR(st_mode))resp[0]='c';
elseif(S_ISBLK(st_mode))resp[0]='b';
elseif(S_ISREG(st_mode))resp[0]='-';
elseif(S_ISFIFO(st_mode))resp[0]='p';
elseif(S_ISLNK(st_mode))resp[0]='l';
elseresp[0]='';
return1;
}
同样,通过st_mode判断文件访问权限
intGetFileMode(mode_tst_mode,char*resp){
if(resp==NULL)
return0;
memset(resp,'-',9);
if(st_mode&S_IRUSR)resp[0]='r';//使用各种宏定义与st_mode做与处理判断
if(st_mode&S_IWUSR)resp[1]='w';
if(st_mode&S_IXUSR)resp[2]='x';
if(st_mode&S_IRGRP)resp[3]='r';
if(st_mode&S_IWGRP)resp[4]='w';
if(st_mode&S_IXGRP)resp[5]='x';
if(st_mode&S_IROTH)resp[6]='r';
if(st_mode&S_IWOTH)resp[7]='w';
if(st_mode&S_IXOTH)resp[8]='x';
return9;
}
处理文件其他属性如下
intGetFileOtherAttr(structstatinfo,char*resp){
structtm*mtime;
if(resp==NULL)
return0;
mtime=localtime(&info.st_mtime);
//按ls命令显示顺序处理其他属性
return(sprintf(resp,"%3d%6d%6d%11d%04d%02d%02d",info.st_nlink,info.st_uid,\
info.st_gid,info.st_size,mtime->tm_year+1900,mtime->tm_mon+1,mtime->tm_mday));
}
设计类似于UNIX命令的程序lsl,主程序如下
[bill@billstoneUnix_study]$catlsl.c
#include
#include
#include
#include
intGetFileType(mode_tst_mode,char*resp);
intGetFileMode(mode_tst_mode,char*resp);
intGetFileOtherAttr(structstatinfo,char*resp);
intmain(intargc,char**argv)
{
structstatinfo;
charbuf[100],*p=buf;
if(argc!
=2){
printf("Usage:
lslfilename\n");
return;
}
memset(buf,0,sizeof(buf));
if(lstat(argv[1],&info)==0){
p+=GetFileType(info.st_mode,p);
p+=GetFileMode(info.st_mode,p);
p+=GetFileOtherAttr(info,p);
printf("%s%s\n",buf,argv[1]);
}
else
printf("Openfilefailed!
\n");
return0;
}
运行结果如下:
[bill@billstoneUnix_study]$makelsl
cclsl.c-olsl
[bill@billstoneUnix_study]$./lsl
Usage:
lslfilename
[bill@billstoneUnix_study]$./lsl/etc/passwd
-rw-r--r--100163920090328/etc/passwd
[bill@billstoneUnix_study]$ls-l/etc/passwd
-rw-r--r--1rootroot16393月2816:
38/etc/passwd
五标准文件编程库
在UNIX的应用中,读写文件是最常见的任务.标准文件编程库就是操作文件最简单的工具.
标准编程函数库对文件流的输入输出操作非常灵活,我们既可以采用所见即所得的方式,以无格式方式读写文件,又可以对输入输出数据进行转化,以有格式方式读写文件.
文件的无格式读写
无格式读写分三类:
按字符读写,按行读写和按块读写.
字符读写函数族:
#include
intgetc(FILE*stream);
intfgetc(FILE*stream);
intputc(intc,FILE*stream);
intfputc(intc,FILE*stream);
函数fgetc的功能类似于getc,不同的是,它的执行速度远低于getc.
行读写函数族:
#include
char*gets(char*s);
char*fgets(char*s,intn,FILE*stream);
intputs(constchar*s);
intfputs(constchar*s,FILE*stream);
函数fgets中加入了放溢出控制,应该优先选用.注意函数fputs把字符串s(不包括结束符'\0')写入文件流stream中,但不在输出换行符'\n';而函数puts则自动输出换行符.
块读写函数族:
#include
size_tfread(void*ptr,size_tsize,size_tnitems,FILE*stream);
size_tfwrite(constvoid*ptr,size_tsize,size_tnitems,FILE*stream);
函数fread和fwrite都不返回实际读写的字符个数,而返回的是实际读写的数据项数.块读写函数常用于保存和恢复内存数据.
文件的格式化读写
文件格式化读写时能够自动转换的数据格式有:
数据类型、精度、宽度、进制和标志等,而其一般格式为
%[标志][宽度][.精度]类型
格式化输出函数族
#include
intprintf(constchar*format,/*[arg,]*/...);
intfprintf(FILE*stream,constchar*format,/*[arg,]*/...);
intsprintf(char*s,constchar*format,/*[arg,]*/...);
在做字符串处理时应该善用sprintf函数.
格式化输入函数族
#include
intscanf(constcharformat,/*[pointer,]*/...);
intfscanf(FILE*stream,constcharformat,/*[pointer,]*/...);
intsscanf(constchar*s,constcharformat,/*[pointer,]*/...);
二进制读写与文本读写
记得刚开始学习C语言的文件操作时,这是一个最让我疑惑的问题.我们都知道在调用fopen函数时需要指定操作类型,比如说文本写'r'和二进制写'rb'.
那么它们究竟有何区别呢?
这要牵涉到两种存储方式:
以字符为单位存储的文本文件和以二进制数据为单位存储的二进制文件.举个例子:
我们通常阅读的Readme.txt文件就是文本文件,该类型文件存储的是一个一个的字符,这些字符往往是可以打印的;而我们的可执行程序(比如a.out)则是二进制文件,该文件是不可读的,需要解析才能识别.
那么在调用fopen函数时该如何选择呢?
如果你是先写入再从另外的地方读出,那么两种方式都可以;只要按写入时的方式读取就可以了.但是,比起文本方式,二进制方式在保存信息时有着优势:
a)加快了程序的执行速度,提高了软件的执行效率.内存中存储的都是二进制信息,直接以二进制方式与文件交互,可以免除二进制格式与文本格式之间的信息转换过程.
b)节省了存储空间.一般来讲,二进制信息比文件信息占用更少的空间,比如8位的整型数采用文本方式存储至少需要8字节,而采用二进制存储只需一个整型即4个字节.
编写变长参数函数
文件的格式化输入输出函数都支持变长参数.定义时,变长参数列表通过省略号'...'表示,因此函数定义格式为:
type函数名(参数1,参数2,参数n,...);
UNIX的变长参数通过va_list对象实现,定义在文件'stdarg.h'中,变长参数的应用模板如下所示:
#include
function(parmN,...){