整个嵌入式开发领域可以分为应用层、内核层与硬件层。
在之前的学习中,我们学习了C编程语言以及Linux命令。这些知识属于应用层的范围中,在掌握这些知识以后,需要更加深入的学习Linux高级编程。所谓Linux系统编程可以理解为系统内核层向应用层提供的接口,包括IO、进程通信、线程调度、网络等方面,掌握这些知识对于Linux应用开发,有着极大帮助。也为驱动程序的学习打下基础
Linux内核有以下功能:文件管理、进程管理、设备管理、内存管理、网络管理。
Linux下高级编程的特点:涉及到内核向用户空间提供的接口(函数)
内核提供接口的原则是:
- 应用服务需要调用基本函数代码来为自己服务,应用程序如果没有内核服务,则应用程序功能单一。
- 内核是稳定的代码集,同时要为多个用户空间。为了防止用户空间的某些用户使用内核代码崩溃从而不能为其他用户服务。
所以内核需要向用户空间提供接口,但要在接口函数中添加一些保护,这样仅会为符合接口的应用提供服务,同时也保护了内核。
Linux高级编程下,主要任务就是掌握系统提供的接口(函数),接口的功能,参数,和返回值的应用。
在IO方面主要对1. 文件IO 2. 标准IO 3.目录IO进行学习。
文件IO
文件IO涉及哪些接口?
- 从内核读取数据或从文件中读取数据——read函数
- 写数据到内核或者写数据到文件中——write函数
- 在文件读写操作前,需要打开文件——open函数
- 读写操作结束后,需要关闭文件——close函数
open —打开或者创建一个文件
函数声明:
open(char *,flag,mode);
在fcntl.h文件中声明。函数的作用:创建或者打开某个文件,其有三个参数,
第一个参数char * 包含有文件名和路径。
第二个参数:flag 打开文件的方式。
第三个参数:mode 设置文件的权限。
flag内容有以下形式:
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_CREAT 创建一个文件
O_EXCL 测试文件是否存在,如果同名文件存在返回-1报错。
O_TRUNC 打开文件(删除已经存在的内容)
O_APPEND 追加方式打开文件(不会删除已有内容)
返回值:
成功:文件描述符。它是一个非负整数,即文件的ID号,相当于人的身份证号。用于区分存储的文件。
出错:-1。
ID号的规律为:程序执行时,内核会自动打开3个文件描述符“0,1,2”,分别对应“标准输入,标准输出,出错”,此后进程每打开一个文件,文件描述符值从3开始累加。
文件IO操作示例
int main(int argc,char *argv[])
{
int fd;
fd = open(argv[1],O_CREAT,0777);
if(fd < 0)
{
printf("creat file %s failure\n",argv[1]);
return -1;
}
printf("creat file %s sucess,fd=%d\n",argv[1],fd);
close(fd);
return 0;
}
注意——open函数创建文件时的权限: == mode &(~umask)
umask(掩码)值在终端用umask命令可以查看 umask xxx修改
write 函数
write(inf fd,void *buf,size_t count)
第一个参数:向哪一个文件中去写
第二个参数:写入什么内容
第三个参数:向这个文件写多少个内容。
函数的返回值:实际写入的字节数。
read函数
read(int fd,void *buf,size_t count)
第一个参数:从哪一个文件中去读
第二个参数:读到什么地方去
第三个参数:第三个参数:读多少个。
函数的返回值:实际读出的字节数。
lseek函数
lseek(int fd,off_t offset,int whence)//包含在头文件:sys/tyoes.h unistd.h中
他的功能:调整读写的位置指针。
第一个参数:要调整的文件的文件描述符;
第二个参数:偏移量,每一个读写操作所需要移动的距离,单位是字节的数量,可负可正(向前移,向后移)
第三个参数:当前位置的基点,包括三个标志
- SEEK_SET:当前位置为文件的开头,新位置为偏移量大小
- SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
- SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。
函数的返回值为:文件当前的位置,出错则为-1。
标准IO
不同于文件IO,标准IO是间接的调用系统调用函数,比如调用C库函数。
标准IO中的相关函数,不经可以读写普通文件,也可以像保准的输入或标准的输出中读或写。
缓存的概念
- 我们的程序中的缓存,用户希望从内核中读写的缓存(数组)——用户空间的缓存
- 没打开一个文件,内核在内核空间中也会开辟一块缓存,这个叫——内核空间的缓存。
文件IO中的写是将用户空间中的缓存写到内核空间的缓存中。
文件IO中的读是将内核空间的缓存写到用户空间中的缓存中。 - 标准IO的库函数中也有一个缓存,称为——库缓存。
文件IO直接对用户空间缓存和内科空间缓存进行操作。标准IO在两个缓存之间加入一个库缓存。
1.当库函数满足一定条件(比如,printf()函数遇到\n时,即会将库缓存的内容写到内核中,即调用了系统调用函数。)
2.库缓存写满时,会调用系统调用函数,将库缓存内容写到内核缓存中区
库缓存包括:行缓存、全缓存和无
读写函数包括以下三类
- 行缓存,遇到新行符“\n”或写满缓存时,即调用系统调用函数
读:fgets,gets,printf,sprintf,fprintf,getc,getchar
写fputs,puts,scanf,putc,putchar - 无缓存,只要用户调用这个函数,就会将其内容写到内核中。
- 全缓存,只有写满缓存再调用系统调用函数。
读:fread
写:fwrite
文件打开函数:
FILE *fopen(const char *path,const char *mode);
返回值:FILE* 文件流指针 类似于文件IO的文件描述符
FILE定义:struct _IO_FILE,在/user/include/libio.h中。结构包含读写缓存的首地址、大小、位置指针等。
标准输入流:stdin 0
标准输出流:stdout 1
标准出错流:stderr 2
常用用法:
FILE *fp;
fp=fopen(argv【1】,“w+”);
标准IO的Mode关键字
关键字 | 含义 |
---|---|
b | 二进制文件 |
r | 只读方式打开文件,文件必须存在 |
w或者a | 只写方式打开文件,文件不存在则创建( w会清除文件内容 a不会清空) |
+ | 读写方式打开文件,文件必须存在 |
行缓存的读写函数fgets和fputs
char *fgets(char *s,int size,FILE *stream)
第一个参数:缓存,即读到哪里去
第二个参数:读多少个字节
第三个参数:从什么地方读
返回值:若成功则为s(缓存的地址),若已处于文件微端或者出错则为NULL。
int fputs(const char *s,FILE *stream)
第一个参数:缓存,即写什么内容
第二个参数:写到哪里去
返回值:若成功则为非负值,若出错则为EOF文件结束符。
刷新缓存函数:
fflush(FILE *fp)
把库函数中的缓存的内容强制写到内核中。
无缓存函数:
stderr
调整读写位置指针函数:
fseek(FILE *stream,long offset,int formwhere)
参数与lseek相同但是返回值不同
lseek的返回值是当前文件的位置指针值;fseek的返回值成功返回0,识别返回-1
rewind(FILE *fp)
用于设定流的文件位置指示为文件开始,该函数调用成功无返回值。
等价于void fseek(fp,0,SEEK_SET)
ftell(FILE *fp)
用于取得当前的文件位置,调用成功则为当前文件位置指示,出错则为-1。
行缓存的读写函数gets和puts
char *gets(char *s)
int puts(const char *s)
从函数定义可知,这两个函数没有目标地址,只能在标准输入输出中读写,gets()没有指定缓存的长度,有造成缓存越界的风险。gets()对于fgets()的区别是:gets()不会将新行符存入缓存,puts()对比与fputs(),输出时会添加一个新的行符。
输出(行缓存)函数printf、fprintf、sprintf
int fprintf(FILE *stream,“字符串格式”),可以输出到显示器,也可以输出到文件。
int sprintf(str *,“字符串格式”),输出内容到一个字符串数组中。
一个字符读写函数fgetc和fputc
int fgetc(FILE *fp)
将文件中内容的一个字符输出到显示器,到文件结尾时返回EOF。
int fputc(int c,FILE *fp);
输入一个字符到文件中,成功则返回输入的字符,出错返回EOF。
判断错误类型:
int feof(FILE *stream);
功能:判断是否已经到达文件结束
参数:文件流
返回值:到文件结束,返回值为非0值,没有则返回0。
int ferror(FILE *stream);
功能:判断是否读写错误
参数:文件流
返回值:是读写错误,返回为非0,不是则返回0。
voidclearerr(FILE *stream);
功能:清除流错误
参数:文件流
//综合函数使用示例。使用IO系统编程来代替cat指令
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
#include "string.h"
int main(int argc,char *argv[])
{
FILE *src_fp;
int read_ret;
if(argc<2)
{
printf("please input src file\n");
return -1;
}
src_fp=fopen(argv[1],"r");
if(src_fp==NULL)
{
printf("open src file %s failure\n",argv[1]);
return -2;
}
printf("open src file %s sucess\n",argv[1]);
while(1)
{
read_ret = fgetc(src_fp);
fputc(read_ret,stdout);
if(feof(src_fp))
{
printf("\nread file %s end\n",argv[1]);
break;
}
}
fclose(src_fp);
return 0;
}
全缓存函数fread和fwrite
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE *stream);
功能:全缓存的读写函数
第一个参数:ptr:写的内容
第二个参数:stream:写到哪里去
第三个参数:size:写的内容中,每一个单元所占的字节数
第四个参数:nmemb:写的内容中,有多少个单元数
总共写的字节数=sizenmemb
返回值:实际读写的单元数。取决于内核的缓存大小
实际上各个函数的时间效率不同,大致可以认为
fread(fwrite) 》 fgets(fputs) 》 fgetc(fputc)
Linux下静态库和动态库(共享库)的制作与使用
静态库,形如libxxx.a,在编译时就将库编译进可执行程序中。
优点:程序的运行环境中不需要外部的函数库
缺点:可执行程序大
动态库:又称共享库,libxxx.so,在运行时将库加载到可执行程序中。
优点:可执行程序小。
缺点:程序的运行环境中必须提供相应的库。
库函数目录: /lib 和 /usr /lib
静态库的制作:
- 生成目标文件:gcc -c file.c
- 静态函数库创建命令:ar
ar -cr libfile.a file.o
-c:create,创建
-r:replace 替换,表示当插入的模块file.o已经存在libfile.a中,则覆盖。反之ar显示一个错误消息。
实例:
情况1:如果从别处得到一个静态库 libunkonwn.a,想知道其中包含哪些模块。
命令:ar -t libunknown.a
- 静态库编译:gcc -o main main.c -L . / -lfile编译main.c就会把静态函数库整合进main。
其中:
-L指定静态函数库的位置供查找,注意L后面还有‘.’,表示静态函数库在本目录下查找。
-l则指定了静态函数库名,由于静态函数库的命名方式是lib***.a,其中的lib和.a可忽略。
动态库的制作:
- 生成目标文件:gcc -c file.c
- gcc -shared -fpic -o libfile.so file.o
-fpic:产生位置无关代码。
-shared:生成共享库
用上述命令生成libaddsub.so动态函数库。
gcc -o out main.c -L . / -lfile
此时还不能立即./out,因为在动态函数库使用时,会查找/usr/lib与/lib目录下的动态函数库,而此时我们生成的库不在里边。
方法1.
libaddsub.so放到/usr/lib或/lib中去。
方法2.假设libaddsub.so在/home/linux/addsub
export LD_LIBRARY_PATH=/home/linux/addsub:$LD_LIBRARY_PATH
方法3.
在/etc/ld.so.conf文件里加入我们生成的库的目录,然后/sbin/ldconfig
/etc/ld.so.conf是非常重要的目录,里面存放的是连接器和加载器搜索共享库时要检查的目录,默认是从/usr/lib /lib中读取的,所以要想顺利运行,可以把库的目录加入到这个文件中并执行/sbin/ldconfig。
目录IO函数(需要调用头文件sys/tyoes.h和dirent.h)
opendir打开目录,如果没有不能创建
DIR *opendir(const char *pathname);
参数:打开的目录以及路径
返回值:成功返回目录流指针,出错返回NULL。
mkdir创建目录
int mkdir(const char *path,mode_t mode);
path为欲创建的目录文件路径,
mode为该目录的访问权限。权限值和umask有关。
返回值:若目录创建成功返回0,否则返回-1.
readdir 读目录信息
struct dirent *readdir(DIR *dr);
参数:目录流指针
返回值:成功返回struct dirent指针,若在目录尾或者出错则返回NULL。
struct dirent定义在头文件dirent.h中。
此结构至少包含以下两个成员:
struct dirent
{
int_t d_ino;//inode号(每个文件唯一的ID号)
char d_name【NAME_MAX+1】;//文件名
}
rewinddir:重置读取目录的位置为开头
void rewinddir(DIR *dr);
参数:目录流指针
telldir:返回目录流位置
long telldir(DIR *dirp);
参数:目录流指针
返回值:目录流当前位置
seekdir:定位任何目录
void seekdir(DIR *dirp,long loc);
参数:目录流指针和偏移量。
closedir:关闭目录
int close(DIR *dr);
参数:目录流指针
返回值;成功返回0,出错返回-1;
最后对IO操作的实例题目: 单机模式下的文件下载。
-
输入服务器的地址:路径和目录名
-
列出服务器中有哪些文件:opendir、readdir
-
输入从服务器下载的文件名
-
文件下载。
#include “stdio.h”
#include “sys/types.h”
#include “dirent.h”
#include “string.h”
#include “unistd.h”
#include “fcntl.h”int main()
{
DIR *dp;
int fd,ret;
int src_fd,des_fd;
struct dirent *dir;
char server[128]={0};
char file[128]={0};
char buf[128]={0};
start:
printf(“输入服务器路径和目录名\n”);
scanf("%s",server);dp=opendir(server); if(dp==NULL) { printf("打开%s失败\n",server); goto start; } printf("打开%s成功\n",server); while(1) { dir=readdir(dp); if(dir==NULL){ break; }else{ printf("inode=%ld\t,文件名:%s\n",dir->d_ino,dir->d_name); } } printf("请输入下载文件名\n"); scanf("%s",file); src_fd=open(strcat(strcat(server,"/"),file),O_RDONLY); if(src_fd < 0) { printf("下载%s文件失败\n",file); return -1; }else{ printf("下载%s文件成功\n",file); } des_fd=open(file,O_CREAT|O_WRONLY,0777); if(des_fd < 0) { printf("创建%s文件失败\n",file); return -2; }else{ printf("创建%s文件成功\n",file); } while(1) { ret=read(src_fd,buf,128); if(ret < 128){ break; } write(des_fd,buf,ret); } write(des_fd,buf,ret); printf("拷贝成功"); close(src_fd); close(des_fd); closedir(dp); return 0; }