Linux 系统调用的本质
简单概念
- fd
![在这里插入图片描述]()
#include <unistd.h>
#include <string.h>
int main(int argc,char* argv[])
{
char buf[20]={0};
read(0,buf,15);
write(1,buf,strlen(buf));
return 0;
}
- 如果想查看某个系统编程的接口,比如想查看 open 函数的用法,可以这样操作:man 2 open。可以看到open函数在哪里定义的,以及open函数的使用方法。
- 首先看一个简单的demo1
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
int fd=0;
fd=open("hello.txt",O_RDWR|O_CREAT,0666);
if(fd == -1)
{
printf("open file failed!\n");
return -1;
}
char string[128]="Hello Word!\n";
write(fd,string,strlen(string));
fsync(fd); //1.为什么要用fsync?因为调用write(...)函数时候,并没有真正写入到磁盘上,可能还在缓冲区中,等缓冲区满了之后,再一起写到磁盘中。
//2.所以,fsync(...) 是强制让缓冲区中的内容写到磁盘中。
char* buf=(char*)malloc(20);
memset(buf,0,20);
lseek(fd,0,SEEK_SET); // 1.SEEK_SET : 代表文件开始 SEEK_CUR:文件当前位置 SEEK_END:文件末尾
// 2.因为上面调用 write(...)函数,使得文件指针偏移了,现在使用lseek(...)将文件指针还原。
read(fd,buf,strlen(string));
printf("%s",buf);
free(buf);
close(fd);
return 0;
}
3.自己动手实现一个cp 命令
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFERSIZE 4096
int main(int argc,char* argv[])
{
if(argc != 3)
{
printf("usage:\n copy src dst\n");
return 1;
}
int srcfd = open(argv[1],O_RDONLY);
if(srcfd == -1)
{
perror("open");
return 1;
}
int dstfd = open(argv[2],O_CREAT|O_WRONLY,0666);
if(dstfd == -1)
{
perror("open");
return 1;
}
int len=0;
char buffer[BUFFERSIZE] = {0};
while((len=read(srcfd,buffer,BUFFERSIZE)) > 0)
{
if(write(dstfd,buffer,len) != len)
{
perror("error");
return 2;
}
}
if(len<0)
{
perror("read error");
return 3;
}
close(srcfd);
close(dstfd);
return 0;
}
系统调用与C标准库函数
-
一般来说C语言中很多函数 fopen、fclose、fseek、fread、fwrite等待都是对Linux 系统调用的封装。
![在这里插入图片描述]()
![在这里插入图片描述]()
一般来说,Linux各种系统的调用流程如上图所示。 -
首先用户空间调用 read 函数,通过中断机制,进入到内核空间,内核空间有一个虚拟文件系统(VFS),虚拟文件系统(VFS)是对内核中各种各样的设备的抽象。
-
我们知道Linux中一切皆文件,对每个设备都抽象成一个文件。每个文件的 读/写 又是不一样的,因此需要一个公用的VFS虚拟文件系统对内核的文件进行抽象。
系统调用的基本流程:
- 软中断:X86下init0X80;ARM架构下SWI软中断指令
- 寄存器保存相关参数:参数、系统调用号
- 进入内核态,执行内核特权指令代码函数
- 返回值保存到寄存器
- 返回到用户态、系统调用结束
内嵌汇编剖析系统调用的本质
一个应用程序运行起来有三种加载方式
- 用户进程 -> 第三方库(或者叫做中间件) -> Linux系统调用 -> Linux 内核 -> 计算机硬件。
- 用户进程 -> Linux系统API -> Linux系统调用 -> Linux 内核 -> 计算机硬件。
- 用户进程 -> Linux系统调用 -> Linux 内核 -> 计算机硬件。
系统调用的本质:就是模式切换
- 系统模式切换依赖于CPU提供的工作方式
- 一般来说,大部分CPU至少具体两种工作方式
- 高特权级(Ring 0)(内核模式) :可以访问任意的数据,包括外围设备,比如网卡,硬盘
- 低特权级(Ring 1)(用户模式):只能受限的访问内存,并且不允许访问外围设备,可被打断。
看一下下面这个例子,与我们平常写的程序有几点不同:
1.没有 #include<stdio.h>,2.没有main()函数,3.内嵌汇编代码
void print(const char* s,int l);
void exit(int code);
void program()
{
print("Hello World!\n",13);
exit(0);
}
void print(const char* s,int l) //封装了系统调用
{
asm volatile (
"movl $4,%%eax\n" //指定编号为4的系统调用(sys_write)
"movl $1,%%ebx\n" //指定sys_write的输出目标,1为标准输出
"movl %0,%%ecx\n" //指定输出字符串地址
"movl %1,%%edx\n" //指定输出字符串长度
"int $0x80 \n" //执行系统调用,请求内核干事情,请求 编号为4的系统调用(sys_write)向屏幕输出
: //忽虐输出参数
: "r"(s),"r"(l)
: "eax","ebx","ecx","edx" //保留寄存器,不用于关联变量
);
}
void exit(int code) //封装了系统调用
{
asm volatile (
"movl $1, %%eax\n" //1号系统调用,结束当前进程
"movl $0, %%ebx\n"
"int $0x80 \n"
:
: "r"(code)
: "eax","ebx"
);
}
运行:gcc -m32 -e program -fno-builtin -nostartfiles program.c -o program.out && && ./program.out
note:如果有报错记得,切换到正确的gcc版本。
可以看到程序正确的运行出来了,这里我们根本没有包含#include<stdio.h> 还有C语言的运行时库 ,这里我们走的是:用户进程 -> Linux系统调用 -> Linux 内核 -> 计算机硬件,这一条路径,传统的是使用第三方库(glic.so) 。
strace证明使用了系统调用
- strace 系统调用探测器,strace用于监控进程与内核的交互(监控系统调用)
- strace用于追踪进程内部状态(定位运行时问题)
- strace按序输出进程运行过程系统调用名称,参数和返回值
strace -o ./program.log ./program.out
execve("./program.out", ["./program.out"], 0x7fff5fb7c860 /* 58 vars */) = 0
brk(NULL) = 0x57ba6000
arch_prctl(0x3001 /* ARCH_??? */, 0xffad6458) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7fbe000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
set_thread_area({entry_number=-1, base_addr=0xf7fbe9c0, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=12)
mprotect(0x5663a000, 4096, PROT_READ) = 0
write(1, "Hello World!\n", 13) = 13
exit(0) = ?
+++ exited with 0 +++
可以看到的确使用了系统调用。




浙公网安备 33010602011771号