1. 练习open/read/write/close等文件相关系统调用接口,纵向对比fd与FILE结构体
2. 对之前编写的自主shell进行修改,使其支持输入/输出/追加重定向
3. 编写简单的add/sub/mul/div函数,并打包成动/静态库,并分别使用。
1. 练习open/read/write/close等文件相关系统调用接口,纵向对比fd与FILE结构体
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<errno.h>
#include<string.h>
int main()
{
umask(0);//mode_t umask(mode_t mask);
//将当前进程的文件创建权限掩码修改成为mask
//open接口:int open(const char *pathname, int flags, mode_t mode);
int fd = open("./open.txt", O_RDWR|O_APPEND|O_CREAT|O_TRUNC,0664);
if(fd < 0)
{
perror("open error");
return -1;
}
//write接口:ssize_t write(int fd, const void *buf, size_t count);
// fd: open打开文件所返回的文件描述符
// buf: 要写入的数据
// count: 要写入的字节数
// 返回值:实际写入的字节数 失败:-1
char* buf = "I love you\n";
ssize_t ret = write(fd, buf, strlen(buf));
if(ret < 0)
{
perror("write error");
return -1;
}
//lseek接口:off_t lseek(int fd, off_t offset, int whence);
//跳转fd文件的读写位置到指定处
// whence: SEEK_SET SEEK_CUR SEEK_END
// offset: 偏移量
lseek(fd, 0 ,SEEK_SET);
//read接口:ssize_t read(int fd, void *buf, size_t count);
//从fd文件中读取count长度的数据,放到buf中
//返回值:返回实际读取到的字节数 失败:-1
char buf1[1024] = {0};
ret = read(fd, buf1, 1023);
if(ret < 0)
{
perror("read error");
return -1;
}
printf("ret = %d-[%s]\n",ret , buf);
//close接口:int close(int fd);
close(fd);//open 必须和 close 搭配使用
}
FILE
- 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上访问文件都是通过fd访问的
- 所以C库当中的FILE结构体内部,必定封装了fd
FILE 与 fd 的区别:
fd是一个表示数组下标的整数,在打开文件的过程中,起到一个索引的作用,进程通过PCB中的文件描述符找到所指向的文件指针file*。
open:文件描述符的操作(如:open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表,此表中的文件描述符来引用。
fopen:流(如:fopen)返回的是一个文件指针(即指向FILE结构体的指针),FILE结构是包含有文件描述符的,fopen可以看做是open(fd直接操作的系统调用)的封装,它的优点是带有I/O缓存。
2. 对之前编写的自主shell进行修改,使其支持输入/输出/追加重定向
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
int main()
{
while(1) {
printf("[san@localhost]$ ");
fflush(stdout);
char tmp[1024] = {0};
scanf("%[^\n]%*c", tmp);
printf("[%s]\n", tmp);
char *ptr = tmp;
int argc = 0;
char *argv[32] = {NULL};
while(*ptr != '\0') {
if (!isspace(*ptr)) {
argv[argc] = ptr;
argc++;
while(!isspace(*ptr) && *ptr != '\0') ptr++;
*ptr = '\0';
ptr++;
continue;
}
ptr++;
}
//argv[argc] = NULL;
if (!strcmp(argv[0], "cd")) {
chdir(argv[1]);
continue;
}
int pid = fork();
if(pid<0)
{
printf("fork error!");
exit(0);
}
else if (pid == 0) {
int i = 0;
int flag=0;
for(;argv[i]!=NULL;++i){
if(strcmp(">",argv[i])==0){
flag=1;
break;
}
}
int copyfd;
argv[i]=NULL;
if(flag)
{
if(argv[i+1]==NULL){
printf("command error\n");
exit(1);
}
close(1);
int fd=open(argv[i+1],O_WRONLY | O_CREAT,0777);
copyfd=dup2(1,fd);//把标准输出重新定向到一个文件中
}
execvp(argv[0],argv);
if(flag){
close(1);
dup2(copyfd,1);
}
exit(0);
}
wait(NULL);
}
return 0;
}
输出结果:
3. 编写简单的add/sub/mul/div函数,并打包成动/静态库,并分别使用。
- 静态库(.a):在windows下后缀为(.lid),程序在编译链接的时候把库的代码链接到可执行文件里。程序运行的时候将不再需要静态库。
- 动态库(.so):在windows下后缀为(.dll),程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
- 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
- 在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程叫动态链接。
- 动态库可以在多个程序间共享,所以动态链接使用可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一部分动态库被要到该库的所有进程共用,节省了内存和磁盘空间。
静态库
编写简单的add/sub/mul/div函数,并打包成静态库,并使用
第一步:分别创建.h.c文件
首先创建 add.h /sub.h /mul.h /div.h
内容如下:
然后创建 add.c/sub.c /mul.c /div.c
内容如下:
第二步:创建好.h 和 .c 文件之后gcc -c add.c sub.c mul.c div.c 编译生成.o文件
第三步:将编译生成的文件用以下命令生成静态库: ar -cr libsmath.a ./*.o
注意:这里的库名是叫smath,要生成静态库,需要给前面加上lib,后面加上.a。
ar是gnu归档工具,cr表示 create and replace
./*.o 表示的是当前目录下的所有的.o后缀文件
第四步:创建一个测试目录,将上一级的静态库和.h都拷贝过来
第五步:创建一个test.c文件来实现函数功能
.
创建的test.c文件内容如下:
第六步:生成可执行程序test: gcc test.c -o test -L. -lsmath
命令的意思是在当前目录下查找库。-L. 指定库路径 -l 指定库名。
注意,在-l时要同时去掉前缀和后缀。
测试目标文件生成后,静态库删掉,程序照样运行。
动态库
编写简单的add/sub/mul/div函数,并打包成动态库,并使用
第一步:首先将上面创建好的代码拷贝到新目录下
第二步:通过gcc -c编译生成.o文件:命令[ gcc -fPIC -c ./*.c ] 不过这里要加上-fPIC:产生位置无关码,使它加载到哪里都能运行
第三步:生成动态库:gcc -shared ./*.o -o libdlib.so
shared:表示生成共享库格式,库(dlib)的前面加上lib后缀要加上.so
*.o 表示所有.o文件
第四步:创建一个测试文件,把头文件(.h)和动态库(.so)拷贝进去
第五步:把之前的test.c拷到当前目录
第六步:生成可执行程序: gcc test.c -o test -L. -ldlib
中间不小心敲错,直接马掉
第七步:运行可执行程序
这里需要引入一个环境变量LD_LIBRARY_PATH,它是系统查找动态库的路径