linux内核分析第四周-使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用...

news/2024/7/9 16:50:38 标签: 操作系统, epoll

本周作业的主要内容就是采用gcc嵌入汇编的方式调用system call。
系统调用其实就是操作系统提供的服务。我们平时编写的程序,如果仅仅是数值计算,那么所有的过程都是在用户态完成的,但是我们想将变量打印在屏幕上,就必须调用printf,而printf这个函数内部就使用了write这个系统调用。
操作系统之所以以system call的方式提供服务,是因为如果程序员能够任意操作OS所有的资源,那么将无比危险,所以OS设计出了内核态和用户态。 我们平时编程都是在用户态下,如果我们想要调用系统资源,那么就必须采用系统调用,陷入内核态,才能达到目的。

下面我们采用几个例子,按照由浅入深的方式一一说明。

getpid 简单示例

getpid的函数很简单,就是获取当前进程的进程号。 使用C调用如下:

 #include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

int main(int argc, const char *argv[])

{

    pid_t tt;

    tt = getpid();

    printf("%u\n", tt);

    return 0;

}

使用内嵌汇编调用如下:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

int main(int argc, const char *argv[])

{

    pid_t tt;

    asm volatile(

            "mov $0x14, %%eax\n\t"

            "int $0x80\n\t"

            "mov %%eax, %0\n\t"

            :"=m" (tt)

            );

    printf("%u\n", tt);

    return 0;

}

内嵌汇编在语法上要求先声明输出参数,然后声明输出参数值。上述代码中getpid不需要参数,只需要一个输出值。 对于内嵌汇编调用system call:

.系统调用号放在eax中。
.系统调用的参数,按照顺序分别放在ebxecxedxesiediebp .返回值使用eax传递

上面的代码之所以使用中断,是因为中断(包括异常)是进入到内核态的唯一方式。 所以我们使用int 0x80触发中断,然后中断处理程序保存现场,我们的进程陷入内核态。

其实,使用系统调用还有一种方式:

#include <stdio.h>
#include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> int main(int argc, const char *argv[]) { pid_t tt; tt = syscall(SYS_getpid); printf("%u\n", tt); return 0; }

这种方式其实内部也是采用的内嵌汇编。
linux中有个函数叫做gettid,这个函数用来取出当前线程的pid(Linux中的线程是使用进程模拟实现的,所以每个线程都有一个全局唯一的pid),可以查到它的声明,但是使用时,编译报错,提示函数找不到,因为libc中没有提供这个函数。此时我们就可以借助这种方式,使用syscall(SYS_gettid)即可。

fork 使用

fork函数同样不需要参数,只有输出,这里给出两个版本:

#include <stdio.h>
#include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, const char *argv[]) { pid_t tt; tt = fork(); if (tt == 0) { printf("子进程\n"); } else { printf("父进程\n"); } printf("%u\n", tt); return 0; }

fork这个函数有个特点,就是调用一次返回两次,原因在于它复制出了一个子进程,执行同样地代码段。
区分子进程和父进程的手段就是检查返回值。

下面是使用汇编的版本

#include <stdio.h>
#include <stdlib.h> #include <string.h> #include <unistd.h> pid_t _fork() { pid_t tt; asm volatile( "mov $0x2, %%eax\n\t" "int $0x80\n\t" "mov %%eax, %0\n\t" :"=m" (tt) ); return tt; } int main(int argc, const char *argv[]) { pid_t tt; tt = _fork(); if (tt == 0) { printf("child\n"); } else { printf("parent\n"); } printf("%u\n", tt); return 0; }

read

上面的getpid和fork都不需要参数,下面我们看下read函数如何使用汇编调用。 read的函数声明如下:

ssize_t read(int fd, void *buf, size_t count);

read函数需要三个参数。 下面我们看下它的过程,这里为了减少篇幅,不再贴出完整的main函数。

ssize_t _read(int fd, void *buf, size_t count) { int ret; asm volatile( "mov %3, %%edx\n\t" // count->edx "mov %2, %%ecx\n\t" // buf->ecx "mov %1, %%ebx\n\t" // fd->ebx "mov $0x3, %%eax\n\t" "int $0x80\n\t" "mov %%eax, %0\n\t" :"=m"(ret) :"b"(fd), "c"(buf), "d"(count) ); return ret; }

上面提过,参数保存在ebx、ecx等寄存器中,这里的三个参数就是放在这三个寄存器中。最后一行的

:"b"(fd), "c"(buf), "d"(count)

就是声明,fd使用的是ebx,buf使用ecx传递,count使用edx传递。

使用timerfd的定时器

下面是一个较复杂的示例,它使用了timerfd系列的定时器,timefd是Linux2.6之后加入的新的系统调用。它将定时器的触发采用fd可读这个事件来表现。所以对于timerfd,我们可以使用epoll,将它和socket的fd一同监视。

C源码如下:

#include <stdio.h>
#include <string.h> #include <stdlib.h> #include <sys/timerfd.h> #define ERR_EXIT(m) \ do { \ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int main(int argc, const char *argv[]) { //创建定时器的fd int timerfd = timerfd_create(CLOCK_REALTIME, 0); if(timerfd == -1) ERR_EXIT("timerfd_create"); //开启定时器,并设置时间 struct itimerspec howlong; memset(&howlong, 0, sizeof howlong); howlong.it_value.tv_sec = 4; //初始时间 howlong.it_interval.tv_sec = 1; //间隔时间 if(timerfd_settime(timerfd, 0, &howlong, NULL) == -1) ERR_EXIT("timerfd_settime"); int ret; char buf[1024]; while((ret = read(timerfd, buf, sizeof buf)) > 0) { printf("foobar ....\n"); } close(timerfd); return 0; }

这段代码主要是注册了一个定时器的fd,然后设置初始化时间和事件触发间隔,然后每隔几秒钟,该定时器fd变为可读。
这段代码的运行效果是:先运行4s,然后每隔1s打印出一行"foobar ..."

下面是使用汇编的版本:

#include <stdio.h>
#include <string.h> #include <stdlib.h> #include <sys/timerfd.h> #define ERR_EXIT(m) \ do { \ perror(m);\ exit(EXIT_FAILURE);\ }while(0) int _timerfd_create(int clockid, int flags); int _timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value); ssize_t _read(int fd, void *buf, size_t count); ssize_t _write(int fd, const void *buf, size_t count); int _close(int fd); int main(int argc, const char *argv[]) { //创建定时器的fd int timerfd = _timerfd_create(CLOCK_REALTIME, 0); if(timerfd == -1) ERR_EXIT("timerfd_create"); //开启定时器,并设置时间 struct itimerspec howlong; memset(&howlong, 0, sizeof howlong); howlong.it_value.tv_sec = 2; //初始时间 howlong.it_interval.tv_sec = 1; //间隔时间 if(_timerfd_settime(timerfd, 0, &howlong, NULL) == -1) ERR_EXIT("timerfd_settime"); int ret; char buf[1024]; while((ret = _read(

转载于:https://www.cnblogs.com/baka/p/5294327.html


http://www.niftyadmin.cn/n/1065128.html

相关文章

Java之重载与覆盖

有的时候&#xff0c;类的同一种功能有多种实现方式&#xff0c;到底采用哪种实现方式&#xff0c;取决于调用者给定的参数。例如我们最常用的System.out.println()能够打印出任何数据类型的数据&#xff0c;它有多种实现方式。运行时&#xff0c;Java虚拟机先判断给定参数的类…

第三周学习进度条

星期一 星期二 星期三 星期四 星期五 星期六 所花时间(包括上课) 8:00-10:00 15:00-15:20 14:30-16:30 16:20-19:40 15:00-17:00 15:00-17:30 9:00-11:40 代码量(行) 0 46 …

敏捷开发般若敏捷系列之三:什么是敏捷(下)(无住,不住于空,破空执,非法,非非法)...

这是敏捷开发般若敏捷系列的第三篇。&#xff08;之一&#xff0c;之二&#xff0c;之三&#xff0c;之四&#xff0c;之五&#xff0c;之六&#xff0c;之七&#xff0c;之八&#xff0c;之九&#xff09;破除法执之后&#xff0c;很容易落入空执&#xff0c;就是认为不存在绝…

判断是不是数字 和String串的长度

String Value“asdasd”; //判断String串的长度 int length System.Text.Encoding.Default.GetBytes(Value).Length; //判断是不是数字 long 为18&#xff08;19&#xff09;位以内有效数字 long outnum; if (!long.TryParse(Value, out outnum)) { continue; }

UVALive - 3882 —— And Then There Was One

题目&#xff1a;http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id11350 约瑟夫环问题 这道题问的是最后死的是那个数字&#xff0c;所以可以不需要模拟&#xff0c;直接通过递推来推出最后幸存的那个数字&#xff1a; 声明&#xff1a;这里我们将所有人的编号都…

layui select选项 某一项不显示 css设置

dd[lay-value‘3’] { display:none; }//3就是某一项

webview加载html

WebView web(WebView) findViewById(R.id.webview); Intent intentgetIntent(); String urlintent.getStringExtra("url");WebSettings wsweb.getSettings(); ws.setDefaultTextEncodingName("gbk"); //设置js代码有效 ws.setJavaScriptEnabled(true); //加…

12864随想

虽然只是简单的驱动&#xff0c;但是下午的调试让自己体会很多。 1.白忙了几个小时去调试&#xff0c;结果是没有tcl文件中有个引脚名改了后没有再运行&#xff0c;自认为综合编译时是会自动把tcl文件编译进去&#xff0c; 最后确实不晓得原因&#xff0c;通过看重要警告&#…