1、 什么是系统调用
操作系统通过系统调用为运行于其上的进程提供服务。当用户态进程发起一个系统调用,CPU将切换到内核态并开始执行一个内核函数。内核函数负责响应应用程序的要求,例如操作文件、进行网络通讯或者申请内存资源等。在Linux中系统调用是有Linux内核提供的各种功能服务,为了便于调用Linux提供了一个底层C语言库libc(glibc是GUN版本的libc,其他类似库还有uclibc、klibc),目前glibc是linux标准函数库,这些都对系统系统接口打包成了标准C函数,这些函数一般就成为系统调用。系统调用可以通过syscall()函数发起,或者调用每个对应的一个C函数,这些函数定义在
内核实现了很多的系统调用函数, 这些函数会有自己的名字, 以及编号. 用户要调用系统调用, 首先需要使用 int 0x80 触发软中断. 这个指令会在0x80代表十进制的128, 所以这个指令会找终端向量表的128项, 找到以后, 跳转到相应的函数, 这个处理函数就是system_call. 这个中断向量表的设置, 是在操作系统初始化的时候, 通过trap_init()函数设置的. 在进入中断处理函数system_call以后, 首先要进行一般的中断处理流程, 即保护现场. 这个体现在指令SAVE_ALL(494行)上. 然后有一个重要的函数调用 call *sys_call_table(,%eax,4). 这个表示查找系统调用函数表(), 然后调用相应的系统调用函数. 对于32位的系统, 函数位置存了4个Bytes, eax中是我们传入的系统调用号, 所以4*eax,就可以找到对应的系统调用函数, 执行函数. 之后还需要进行返回值的保存等工作.
2、 gdb跟踪相关系统调用
1) 因为上一次实验是在shiyanlou环境下完成,所以此次实验需要重新下载下载linux-5.0.1的内核并编译内核,并制作根文件系统。
2)此处先不进行理论上的分析,不妨首先通过gdb跟踪其在内核中的系统调用,在Menuos中输入replyhi命令,观察其系统调用
接下来输入hello命令,观察系统系统调用
3、socket相关系统调用的内核处理函数深入分析
1)通过socket和hello/hi的跟踪分析,可以看到一共进行了14次系统调用,其中初始化占了3次,replyhi的socket创建,bind,listen,accept;hello的socket创建等共10次,完成这些操作后,replyhi重新调用accept。
2)通过以上结果并结合内核函数源码进一步分析,在menu目录下找到test.c源代码,如下位置
int Replyhi() { char szBuf[MAX_BUF_LEN] = "\0"; char szReplyMsg[MAX_BUF_LEN] = "hi\0"; int sockfd = -1; struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; socklen_t addr_len = sizeof(struct sockaddr); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); serveraddr.sin_addr.s_addr = inet_addr(IP_ADDR); memset(&serveraddr.sin_zero, 0, 8); sockfd = socket(PF_INET,SOCK_STREAM,0); int ret = bind( sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)); if(ret == -1) { fprintf(stderr,"Bind Error,%s:%d\n", __FILE__,__LINE__); close(sockfd); return -1; } listen(sockfd,MAX_CONNECT_QUEUE); while(1) { int newfd = accept( sockfd, (struct sockaddr *)&clientaddr, &addr_len); if(newfd == -1) { fprintf(stderr,"Accept Error,%s:%d\n", __FILE__,__LINE__); } ret = recv(newfd,szBuf,MAX_BUF_LEN,0); if(ret > 0) { printf("recv \"%s\" from %s:%d\n", szBuf, (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); \ } ret = send(newfd,szReplyMsg,strlen(szReplyMsg),0); if(ret > 0) { printf("rely \"hi\" to %s:%d\n", (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); \ } close(newfd); } close(sockfd); return 0; } int StartReplyhi(int argc, char *argv[]) { int pid; /* fork another process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed!"); exit(-1); } else if (pid == 0) { /* child process */ Replyhi(); printf("Reply hi TCP Service Started!\n"); } else { /* parent process */ printf("Please input hello...\n"); } } int Hello(int argc, char *argv[]) { char szBuf[MAX_BUF_LEN] = "\0"; char szMsg[MAX_BUF_LEN] = "hello\0"; int sockfd = -1; struct sockaddr_in serveraddr; struct sockaddr_in clientaddr; socklen_t addr_len = sizeof(struct sockaddr); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); serveraddr.sin_addr.s_addr = inet_addr(IP_ADDR); memset(&serveraddr.sin_zero, 0, 8); sockfd = socket(PF_INET,SOCK_STREAM,0); int ret = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)); if(ret == -1) { fprintf(stderr,"Connect Error,%s:%d\n", __FILE__,__LINE__); return -1; } ret = send(sockfd,szMsg,strlen(szMsg),0); if(ret > 0) { printf("send \"hello\" to %s:%d\n", (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); \ } ret = recv(sockfd,szBuf,MAX_BUF_LEN,0); if(ret > 0) { printf("recv \"%s\" from %s:%d\n", szBuf, (char*)inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); \ } close(sockfd); return 0; } int main() { PrintMenuOS(); SetPrompt("MenuOS>>"); MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL); MenuConfig("quit","Quit from MenuOS",Quit); MenuConfig("time","Show System Time",Time); MenuConfig("time-asm","Show System Time(asm)",TimeAsm); MenuConfig("replyhi", "Reply hi TCP Service", StartReplyhi); MenuConfig("hello", "Hello TCP Client", Hello); ExecuteMenu(); }
先看main()函数,replyhi和hello命令分别调用StartReplyhi()和hello()函数。接着分析StartReplyhi()函数,当pid=0时,调用Replyhi(),进入Replyhi中,可以看到其中用到了socket(),bind(),listen(),accept(),recv(),send()接口。同理进入Hello()函数,可以看到调用了socket(),connect(),send(),recv()等接口。这个分析结果与也与之前的实验结果相吻合。