关于harmonyos:鸿蒙OS的系统调用是如何实现的-解读鸿蒙源码

3次阅读

共计 20914 个字符,预计需要花费 53 分钟才能阅读完成。

本文将首先带您回顾“零碎调用”的概念以及它的作用,而后从经典的 Hello World 开始,逐行代码层层剖析——鸿蒙 OS 的零碎调用是如何实现的。

写在后面

9 月 10 号 华为开发者大会(HDC)上,华为向宽广开发者发表了鸿蒙 2.0 零碎开源,源码托管在国内源码托管平台“码云”上:https://openharmony.gitee.com/

我也第一工夫从码云下载了鸿蒙零碎的源代码,并进行了编译和剖析。当晚回看了 HDC 上的对于鸿蒙 OS 2.0 的主题演讲,集体最为好奇的是——这次开源的 liteos- a 内核。因为它反对了带 MMU(内存治理单元)的 ARM Cortex- A 设施;咱们晓得,在带有 MMU 的处理器上,能够实现虚拟内存,进而实现过程之间的隔离、内核态和用户态的隔离等等这些性能。

零碎调用简介

援用一张官网文档中的图片,看看 liteos- a 内核在整个零碎中的地位。

这次开源的鸿蒙零碎中同时蕴含了两个内核,别离是 liteos- a 和 liteos-m,其中的 liteos- m 和以前开源的 LiteOS 相当,而 liteos- a 是面向利用处理器的操作系统内核,提供了更为丰盛的内核性能。此前曾经开源的 LiteOS,只是一个实时操作系统(RTOS),它次要面向的是内存和闪存配置都比拟低的微控制器。

咱们先来简略回顾一下操作系统课程的一个知识点——零碎调用,以及为什么会有零碎调用?它的作用是什么?如果你对于这两个问题以及了然于心,能够间接跳过本段,看前面的源码剖析局部。

在微控制器这样的系统资源较少的硬件零碎(比方 STM32、MSP430、AVR、8051)上,通常间接裸跑程序(也就是不应用任何操作系统),或者应用像 FreeRTOS、Zephyr 这一类的实时操作系统(RTOS)。这些实时操作系统中,应用程序和内核程序间接运行在同一个物理内存空间(因为这些设施个别没有 MMU)上。而 RTOS 只提供了线程(或者叫工作),线程间同步、互斥等基础设施;应用程序能够间接调用内核函数(用户程序和内核程序只是逻辑上的划分,实质上并没有太大不同);一旦有一个线程产生异样,整个零碎就会重启。

而在 ARM Cortex-A、x86、x86-64 这样的系统资源丰盛的硬件零碎上,SoC 或 CPU 芯片外部个别集成了 MMU,而且 CPU 有特权级别状态(状态寄存器的某些位)。基于特权级别状态,能够实现局部硬件相干的操作只能在内核态进行,例如拜访外设等,用户态应用程序不能拜访硬件设施。在这样的零碎上,零碎调用是用户态应用程序调用内核性能的申请入口。艰深的说,零碎调用就是在有内核态和用户态隔离的操作系统上,用户态过程拜访内核态资源的一种形式。

从 Hello World 开始

接下来,咱们一起从鸿蒙零碎源码剖析它在 liteos- a 内核上是如何实现零碎调用的。鸿蒙 OS 应用了 musl libc,应用程序和零碎服务都通过 musl libc 封装的零碎调用 API 接口拜访内核相干性能。

上面,咱们就从经典的 helloworld 剖析整个零碎调用的流程。鸿蒙零碎目前官网反对了三个芯片平台,别离是 Hi3516DV300(双核 ARM Cortex A-7 @ 900M Hz),Hi3518EV300(单核 ARM Cortex A-7 @ 900MHz 内置 64MB DDR2 内存)和 Hi3861V100(单核 RISC-V @160M Hz 内置 SRAM 和 Flash)。其中 Hi3516 和 Hi3518 是带有 Cortex A7 内核的芯片,鸿蒙零碎在这两个平台应用的内核天然是 liteos-a。依据官网领导文档,咱们晓得这两个平台的第一个应用程序示例都是 helloworld,源码门路为:applications/sample/camera/app/src/helloworld.c,除去头部正文,代码内容为:

#include <stdio.h>#include "los_sample.h"int main(int argc, char **argv){printf("\n************************************************\n");
    printf("\n\t\tHello OHOS!\n");
    printf("\n************************************************\n\n");

    LOS_Sample(g_num);

    return 0;
}

musl libc 的 printf 函数实现剖析

文件门路:third_party/musl/src/stdio/printf.c:

int printf(const char *restrict fmt, ...){    int ret;
    va_list ap;
    va_start(ap, fmt);
    ret = vfprintf(stdout, fmt, ap);
    va_end(ap);    return ret;
}

咱们看到了,这里应用规范库的 stdout 作为第一个参数调用了 vfprintf,咱们持续向下剖析 third_party/musl/src/stdio/vfprintf.c 文件:

int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap)
{// 删减若干和参数 f 无关的代码行
    FLOCK(f);
    olderr = f->flags & F_ERR;    if (f->mode < 1) f->flags &= ~F_ERR;    if (!f->buf_size) {
        saved_buf = f->buf;
        f->buf = internal_buf;
        f->buf_size = sizeof internal_buf;
        f->wpos = f->wbase = f->wend = 0;
    }    if (!f->wend && __towrite(f)) ret = -1;    else ret = printf_core(f, fmt, &ap2, nl_arg, nl_type);    if (saved_buf) {f->write(f, 0, 0);        if (!f->wpos) ret = -1;
        f->buf = saved_buf;
        f->buf_size = 0;
        f->wpos = f->wbase = f->wend = 0;
    }    if (f->flags & F_ERR) ret = -1;
    f->flags |= olderr;
    FUNLOCK(f);
    va_end(ap2);    return ret;
}

这里,咱们持续关注三处带有参数 f 的调用:__towrite(f),printf_core(f, fmt, &ap2, nl_arg, nl_type),f->write(f, 0, 0);

其中,__towrite 的实现位于 third_party/musl/src/stdio/__towrite.c(可见和零碎调用无关):

int __towrite(FILE *f)
{f->mode |= f->mode-1;    if (f->flags & F_NOWR) {f->flags |= F_ERR;        return EOF;}    /* Clear read buffer (easier than summoning nasal demons) */
    f->rpos = f->rend = 0;    /* Activate write through the buffer. */
    f->wpos = f->wbase = f->buf;
    f->wend = f->buf + f->buf_size;    return 0;
}

从内容上看,__towrite 函数的作用是更新文件构造 FILE 的 wpos、wbase、wend 成员,以指向待写入理论文件的内存缓冲区域,同时将 rpos、rend 值为零。

printf_core 的实现也位于 src/stdio/vfprintf.c 文件:

static int printf_core(FILE *f, const char *fmt, va_list *ap, union arg *nl_arg, int *nl_type){
    // 删除了变量定义局部
    for (;;) {        /* This error is only specified for snprintf, but since it's
         * unspecified for other forms, do the same. Stop immediately
         * on overflow; otherwise %n could produce wrong results. */
        if (l > INT_MAX - cnt) goto overflow;        /* Update output count, end loop when fmt is exhausted */
        cnt += l;        if (!*s) break;        /* Handle literal text and %% format specifiers */
        for (a=s; *s && *s!='%'; s++);        for (z=s; s[0]=='%' && s[1]=='%'; z++, s+=2);        if (z-a > INT_MAX-cnt) goto overflow;
        l = z-a;        if (f) out(f, a, l);        if (l) continue;        if (isdigit(s[1]) && s[2]=='$') {
            l10n=1;
            argpos = s[1]-'0';
            s+=3;
        } else {
            argpos = -1;
            s++;
        }        /* Read modifier flags */
        for (fl=0; (unsigned)*s-''<32 && (FLAGMASK&(1U<<*s-' ')); s++)
            fl |= 1U<<*s-' ';        /* Read field width */
        if (*s=='*') {if (isdigit(s[1]) && s[2]=='$') {
                l10n=1;
                nl_type[s[1]-'0'] = INT;
                w = nl_arg[s[1]-'0'].i;
                s+=3;
            } else if (!l10n) {w = f ? va_arg(*ap, int) : 0;
                s++;
            } else goto inval;            if (w<0) fl|=LEFT_ADJ, w=-w;
        } else if ((w=getint(&s))<0) goto overflow;        /* Read precision */
        if (*s=='.' && s[1]=='*') {if (isdigit(s[2]) && s[3]=='$') {nl_type[s[2]-'0'] = INT;
                p = nl_arg[s[2]-'0'].i;
                s+=4;
            } else if (!l10n) {p = f ? va_arg(*ap, int) : 0;
                s+=2;
            } else goto inval;
            xp = (p>=0);
        } else if (*s=='.') {
            s++;
            p = getint(&s);
            xp = 1;
        } else {
            p = -1;
            xp = 0;
        }        /* Format specifier state machine */
        st=0;        do {if (OOB(*s)) goto inval;
            ps=st;
            st=states[st]S(*s++);
        } while (st-1<STOP);        if (!st) goto inval;        /* Check validity of argument type (nl/normal) */
        if (st==NOARG) {if (argpos>=0) goto inval;
        } else {if (argpos>=0) nl_type[argpos]=st, arg=nl_arg[argpos];            else if (f) pop_arg(&arg, st, ap);            else return 0;
        }        if (!f) continue;

        z = buf + sizeof(buf);
        prefix = "-+   0X0x";
        pl = 0;
        t = s[-1];        /* Transform ls,lc -> S,C */
        if (ps && (t&15)==3) t&=~32;        /* - and 0 flags are mutually exclusive */
        if (fl & LEFT_ADJ) fl &= ~ZERO_PAD;        switch(t) {case 'n':            switch(ps) {case BARE: *(int *)arg.p = cnt; break;            case LPRE: *(long *)arg.p = cnt; break;            case LLPRE: *(long long *)arg.p = cnt; break;            case HPRE: *(unsigned short *)arg.p = cnt; break;            case HHPRE: *(unsigned char *)arg.p = cnt; break;            case ZTPRE: *(size_t *)arg.p = cnt; break;            case JPRE: *(uintmax_t *)arg.p = cnt; break;
            }            continue;        case 'p':
            p = MAX(p, 2*sizeof(void*));
            t = 'x';
            fl |= ALT_FORM;        case 'x': case 'X':
            a = fmt_x(arg.i, z, t&32);            if (arg.i && (fl & ALT_FORM)) prefix+=(t>>4), pl=2;            if (0) {        case 'o':
            a = fmt_o(arg.i, z);            if ((fl&ALT_FORM) && p<z-a+1) p=z-a+1;
            } if (0) {        case 'd': case 'i':
            pl=1;            if (arg.i>INTMAX_MAX) {arg.i=-arg.i;} else if (fl & MARK_POS) {prefix++;} else if (fl & PAD_POS) {prefix+=2;} else pl=0;        case 'u':
            a = fmt_u(arg.i, z);
            }            if (xp && p<0) goto overflow;            if (xp) fl &= ~ZERO_PAD;            if (!arg.i && !p) {a=z;                break;}
            p = MAX(p, z-a + !arg.i);            break;        case 'c':
            *(a=z-(p=1))=arg.i;
            fl &= ~ZERO_PAD;            break;        case 'm':            if (1) a = strerror(errno); else
        case 's':
            a = arg.p ? arg.p : "(null)";
            z = a + strnlen(a, p<0 ? INT_MAX : p);            if (p<0 && *z) goto overflow;
            p = z-a;
            fl &= ~ZERO_PAD;            break;        case 'C':
            wc[0] = arg.i;
            wc[1] = 0;
            arg.p = wc;
            p = -1;        case 'S':
            ws = arg.p;            for (i=l=0; i<p && *ws && (l=wctomb(mb, *ws++))>=0 && l<=p-i; i+=l);            if (l<0) return -1;            if (i > INT_MAX) goto overflow;
            p = i;
            pad(f, ' ', w, p, fl);
            ws = arg.p;            for (i=0; i<0U+p && *ws && i+(l=wctomb(mb, *ws++))<=p; i+=l)
                out(f, mb, l);
            pad(f, ' ', w, p, fl^LEFT_ADJ);
            l = w>p ? w : p;            continue;        case 'e': case 'f': case 'g': case 'a':        case 'E': case 'F': case 'G': case 'A':            if (xp && p<0) goto overflow;
            l = fmt_fp(f, arg.f, w, p, fl, t);            if (l<0) goto overflow;            continue;
        }        if (p < z-a) p = z-a;        if (p > INT_MAX-pl) goto overflow;        if (w < pl+p) w = pl+p;        if (w > INT_MAX-cnt) goto overflow;

        pad(f, ' ', w, pl+p, fl);
        out(f, prefix, pl);
        pad(f, '0', w, pl+p, fl^ZERO_PAD);
        pad(f, '0', p, z-a, 0);
        out(f, a, z-a);
        pad(f, ' ', w, pl+p, fl^LEFT_ADJ);

        l = w;
    }    if (f) return cnt;    if (!l10n) return 0;    for (i=1; i<=NL_ARGMAX && nl_type[i]; i++)
        pop_arg(nl_arg+i, nl_type[i], ap);    for (; i<=NL_ARGMAX && !nl_type[i]; i++);    if (i<=NL_ARGMAX) goto inval;    return 1;
inval: // 删除了错误处理代码 overflow: // 删除了错误处理代码}

从正文和代码构造能够看出,这个函数实现了格式化字符串开展的次要流程,这里又调用了 out 和 pad 两个函数,从命名猜想应该别离是向内存缓冲区写入内容和填充内容的函数,它们的实现也位于 vfprintf.c 中:

static void out(FILE *f, const char *s, size_t l){if (!(f->flags & F_ERR)) __fwritex((void *)s, l, f);
}static void pad(FILE *f, char c, int w, int l, int fl){char pad[256];    if (fl & (LEFT_ADJ | ZERO_PAD) || l >= w) return;
    l = w - l;
    memset(pad, c, l>sizeof pad ? sizeof pad : l);    for (; l >= sizeof pad; l -= sizeof pad)        out(f, pad, sizeof pad);    out(f, pad, l);
}

它们又调用了__fwritex,它的实现位于 third_party/musl/src/stdio/fwrite.c:

size_t __fwritex(const unsigned char *restrict s, size_t l, FILE *restrict f)
{size_t i=0;    if (!f->wend && __towrite(f)) return 0;    if (l > f->wend - f->wpos) return f->write(f, s, l);    if (f->lbf >= 0) {/* Match /^(.*\n|)/ */
        for (i=l; i && s[i-1] != '\n'; i--);        if (i) {size_t n = f->write(f, s, i);            if (n < i) return n;
            s += i;
            l -= i;
        }
    }

    memcpy(f->wpos, s, l);
    f->wpos += l;    return l+i;
}

这里又呈现了 vfprintf 中呈现的 f ->write(f, s, i),上面咱们就剖析这个函数理论底是什么?

咱们先找到它的定义 prebuilts/lite/sysroot/usr/include/arm-liteos/bits/alltypes.h:

#if defined(__NEED_FILE) && !defined(__DEFINED_FILE)typedef struct _IO_FILE FILE;#define __DEFINED_FILE#endif

以及 third_party/musl/src/internal/stdio_impl.h:

struct _IO_FILE {unsigned flags;    unsigned char *rpos, *rend;    int (*close)(FILE *);    unsigned char *wend, *wpos;    unsigned char *mustbezero_1;    unsigned char *wbase;    size_t (*read)(FILE *, unsigned char *, size_t);    size_t (*write)(FILE *, const unsigned char *, size_t); // <-- 关注它
    off_t (*seek)(FILE *, off_t, int);    unsigned char *buf;    size_t buf_size;
    FILE *prev, *next;    int fd;    int pipe_pid;    long lockcount;    int mode;    volatile int lock;    int lbf;    void *cookie;    off_t off;    char *getln_buf;    void *mustbezero_2;    unsigned char *shend;    off_t shlim, shcnt;
    FILE *prev_locked, *next_locked;    struct __locale_struct *locale;};

咱们再持续寻找 stdout 的各个成员值是什么?

能够找到 third_party/musl/src/stdio/stdout.c 文件中的:

static unsigned char buf[BUFSIZ+UNGET];
hidden FILE __stdout_FILE = {
    .buf = buf+UNGET,
    .buf_size = sizeof buf-UNGET,
    .fd = 1, // fd 为 1 和少数 UNIX 零碎一样
    .flags = F_PERM | F_NORD,
    .lbf = '\n',
    .write = __stdout_write, // <-- write 成员在这里
    .seek = __stdio_seek,
    .close = __stdio_close,
    .lock = -1,
};
FILE *const stdout = &__stdout_FILE; // <-- stdout 在这里

third_party/musl/src/stdio/__stdout_write.c 文件中:

size_t __stdout_write(FILE *f, const unsigned char *buf, size_t len)
{    struct winsize wsz;
    f->write = __stdio_write;    if (!(f->flags & F_SVB) && __syscall(SYS_ioctl, f->fd, TIOCGWINSZ, &wsz))
        f->lbf = -1;    return __stdio_write(f, buf, len);
}

这段代码里调用了 SYS_ioctl 零碎调用,但主体流程是下方的函数__stdio_write,它的实现在 third_party/musl/src/stdio/__stdio_write.c 文件中:

size_t __stdio_write(FILE *f, const unsigned char *buf, size_t len)
{struct iovec iovs[2] = {{ .iov_base = f->wbase, .iov_len = f->wpos-f->wbase},
        {.iov_base = (void *)buf, .iov_len = len }
    };    struct iovec *iov = iovs;
    size_t rem = iov[0].iov_len + iov[1].iov_len;
    int iovcnt = 2;
    ssize_t cnt;    for (;;) {cnt = syscall(SYS_writev, f->fd, iov, iovcnt); // <-- 看这里!if (cnt == rem) {
            f->wend = f->buf + f->buf_size;
            f->wpos = f->wbase = f->buf;            return len;
        }        if (cnt < 0) {
            f->wpos = f->wbase = f->wend = 0;
            f->flags |= F_ERR;            return iovcnt == 2 ? 0 : len-iov[0].iov_len;
        }
        rem -= cnt;        if (cnt > iov[0].iov_len) {cnt -= iov[0].iov_len;
            iov++; iovcnt--;
        }
        iov[0].iov_base = (char *)iov[0].iov_base + cnt;
        iov[0].iov_len -= cnt;
    }
}

至此,咱们看到了 printf 函数最终调用到了两个零碎调用 SYS_ioctl 和 SYS_write。

musl libc 的 syscall 函数实现剖析
在上一节中,咱们看到 printf 最终调用到了两个长得像零碎调用的函数 syscall 和__syscall。

零碎调用宏 syscall 的实现
在 musl 代码仓(third_party/musl)下搜寻:

$ find . -name '*.h' | xargs grep --color -n '\ssyscall('./kernel/include/unistd.h:198:long syscall(long, ...);
./src/internal/syscall.h:44:#define syscall(...) __syscall_ret(__syscall(__VA_ARGS__))./include/unistd.h:199:long syscall(long, ...);

能够找到 third_party/musl/src/internal/syscall.h:

#define __syscall(...) __SYSCALL_DISP(__syscall,__VA_ARGS__)#define syscall(...) __syscall_ret(__syscall(__VA_ARGS__))

这里能够看到它们两者都是宏,而 syscall 调用了__syscall,而__syscall 又调用了__SYSCALL_DISP,它的实现也在同一个文件中:

#define __SYSCALL_NARGS_X(a,b,c,d,e,f,g,h,n,...) n#define __SYSCALL_NARGS(...) __SYSCALL_NARGS_X(__VA_ARGS__,7,6,5,4,3,2,1,0,)#define __SYSCALL_CONCAT_X(a,b) a##b#define __SYSCALL_CONCAT(a,b) __SYSCALL_CONCAT_X(a,b)#define __SYSCALL_DISP(b,...) __SYSCALL_CONCAT(b,__SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)

咱们以__stdio_write 中调用 syscall 处进行剖析,即尝试开展 syscall(SYS_writev, f->fd, iov, iovcnt);

syscall(SYS_writev, f->fd, iov, iovcnt);
=> __syscall_ret(__syscall(SYS_writev, f->fd, iov, iovcnt)) // 开展 syscall=> __syscall_ret(__SYSCALL_DISP(__syscall, SYS_writev, f->fd, iov, iovcnt)); // 开展__syscall

先疏忽最外层的 __syscall_ret,开展__SYSCALL_DISP 局部:

__SYSCALL_DISP(__syscall, SYS_writev, f->fd, iov, iovcnt)
=> __SYSCALL_CONCAT(__syscall, __SYSCALL_NARGS(SYS_writev, f->fd, iov, iovcnt))(SYS_writev, f->fd, iov, iovcnt) // 开展 __SYSCALL_DISP

疏忽外层的__SYSCALL_CONCAT,开展__SYSCALL_NARGS_X 局部:

__SYSCALL_NARGS(SYS_writev, f->fd, iov, iovcnt)
=> __SYSCALL_NARGS_X(SYS_writev, f->fd, iov, iovcnt,7,6,5,4,3,2,1,0,) // 开展 __SYSCALL_NARGS=> 3 // 开展 __SYSCALL_NARGS_X// SYS_writev, f->fd, iov, iovcnt 和宏参数 a,b,c,d 对应 // 7,6,5,4 和宏参数 e,f,g,h 对应 // 3 和宏参数 n 对应 // 宏表达式的值为 n 也就是 3,

回到 __SYSCALL_CONCAT 开展流程,

__SYSCALL_CONCAT(__syscall, __SYSCALL_NARGS(SYS_writev, f->fd, iov, iovcnt))
=> __SYSCALL_CONCAT(__syscall, 3)
=> __SYSCALL_CONCAT_X(__syscall, 3)
=> __syscall3

再回到__SYSCALL_DISP(__syscall, SYS_writev, f->fd, iov, iovcnt)开展流程,后果应该是:

__SYSCALL_DISP(__syscall, SYS_writev, f->fd, iov, iovcnt)
=> __syscall3(SYS_writev, f->fd, iov, iovcnt)

零碎调用函数__syscall3 的实现

这些__syscall[1-7]的零碎调用包装宏定义如下:

#ifndef __scc#define __scc(X) ((long) (X)) // 转为 long 类型 typedef long syscall_arg_t;#endif#define __syscall1(n,a) __syscall1(n,__scc(a))#define __syscall2(n,a,b) __syscall2(n,__scc(a),__scc(b))#define __syscall3(n,a,b,c) __syscall3(n,__scc(a),__scc(b),__scc(c)) // <- 看这里 #define __syscall4(n,a,b,c,d) __syscall4(n,__scc(a),__scc(b),__scc(c),__scc(d))#define __syscall5(n,a,b,c,d,e) __syscall5(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e))#define __syscall6(n,a,b,c,d,e,f) __syscall6(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e),__scc(f))#define __syscall7(n,a,b,c,d,e,f,g) __syscall7(n,__scc(a),__scc(b),__scc(c),__scc(d),__scc(e),__scc(f),__scc(g))

持续搜寻发现有多出匹配,咱们关注 arch/arm 目录下的文件,因为 ARM Cortext A7 是 Armv7- A 指令集的 32 位 CPU(如果是 Armv8- A 指令集的 64 位 CPU 则对应 arch/aarch64 下的文件):

static inline long __syscall3(long n, long a, long b, long c)
{register long r7 __ASM____R7__ = n;    register long r0 __asm__("r0") = a;    register long r1 __asm__("r1") = b;    register long r2 __asm__("r2") = c;
    __asm_syscall(R7_OPERAND, "0"(r0), "r"(r1), "r"(r2));
}

这段代码中还有三个宏,__ASM____R7__、__asm_syscall 和 R7_OPERAND:

#ifdef __thumb__#define __ASM____R7__#define __asm_syscall(...) do { \
    __asm__ __volatile__ ( "mov %1,r7 ; mov r7,%2 ; svc 0 ; mov r7,%1" \
    : "=r"(r0), "=&r"((int){0}) : __VA_ARGS__ : "memory"); \
    return r0; \
    } while (0)#else // __thumb__#define __ASM____R7__ __asm__("r7")#define __asm_syscall(...) do { \
    __asm__ __volatile__ ( "svc 0" \
    : "=r"(r0) : __VA_ARGS__ : "memory"); \
    return r0; \
    } while (0)#endif // __thumb__#ifdef __thumb2__#define R7_OPERAND "rI"(r7)#else#define R7_OPERAND "r"(r7)#endif

它们有两个实现版,别离对应于编译器 THUMB 选项的开启和敞开。这两种选项条件下的代码流程基本一致,以下仅以未开启 THUMB 选项为例进行剖析。这两个宏开展后的__syscall3 函数内容为:

static inline long __syscall3(long n, long a, long b, long c)
{register long r7 __asm__("r7") = n; // 零碎调用号
    register long r0 __asm__("r0") = a; // 参数 0
    register long r1 __asm__("r1") = b; // 参数 1
    register long r2 __asm__("r2") = c; // 参数 2
    do { \
        __asm__ __volatile__ ( "svc 0" \
        : "=r"(r0) : "r"(r7), "0"(r0), "r"(r1), "r"(r2) : "memory"); \
        return r0; \
    } while (0);
}

这里最初的一个内嵌汇编比较复杂,它合乎如下格局(具体细节能够查阅 gcc 内嵌汇编文档的扩大汇编阐明):

asm asm-qualifiers ( AssemblerTemplate 
                 : OutputOperands 
                 [ : InputOperands
                 [: Clobbers] ])

汇编模板为:”svc 0″, 输入参数局部为:”=r”(r0),输入寄存器为 r0 输出参数局部为:”r”(r7), “0”(r0), “r”(r1), “r”(r2),输出寄存器为 r7,r0,r1,r2,(“0″ 的含意是,这个输出寄存器必须和输入寄存器第 0 个地位一样) Clobber 局部为:”memory”

这里咱们只须要记住:零碎调用号寄存在 r7 寄存器,参数寄存在 r0,r1,r2,返回值最终会寄存在 r0 中;

SVC 指令,ARM Cortex A7 手册 的解释为:

The SVC instruction causes a Supervisor Call exception. This provides a mechanism for unprivileged software to make a call to the operating system, or other system component that is accessible only at PL1.

翻译过去就是说

SVC 指令会触发一个“特权调用”异样。这为非特权软件调用操作系统或其余只能在 PL1 级别拜访的零碎组件提供了一种机制。

具体的指令阐明在
到这里,咱们剖析了鸿蒙零碎上应用程序如何进入内核态,次要剖析的是 musl libc 的实现。

liteos- a 内核的零碎调用实现剖析

既然 SVC 可能触发一个异样,那么咱们就要看看 liteos- a 内核是如何解决这个异样的。

ARM Cortex A7 中断向量表

在 ARM 架构参考手册中,能够找到中断向量表的阐明:

能够看到 SVC 中断向量的便宜地址是 0x08,咱们能够在 kernel/liteos_a/arch/arm/arm/src/startup 目录的 reset_vector_mp.S 文件和 reset_vector_up.S 文件中找到相干汇编代码:

__exception_handlers:
    /*
    *Assumption:  ROM code has these vectors at the hardware reset address.
    *A simple jump removes any address-space dependencies [i.e. safer]
    */
    b   reset_vector
    b   _osExceptUndefInstrHdl
    b   _osExceptSwiHdl
    b   _osExceptPrefetchAbortHdl
    b   _osExceptDataAbortHdl
    b   _osExceptAddrAbortHdl
    b   OsIrqHandler
    b   _osExceptFiqHdl

PS: kernel/liteos_a/arch/arm/arm/src/startup 目录有两个文件 reset_vector_mp.S 文件和 reset_vector_up.S 文件别离对应多核和单核编译选项:

ifeq ($(LOSCFG_KERNEL_SMP), y)
LOCAL_SRCS += src/startup/reset_vector_mp.SelseLOCAL_SRCS += src/startup/reset_vector_up.Sendif

SVC 中断处理函数
下面的汇编代码中能够看到,_osExceptSwiHdl 函数就是 SVC 异样处理函数,具体实现在 kernel/liteos_a/arch/arm/arm/src/los_hw_exc.S 文件中:

@ Description: Software interrupt exception handler_osExceptSwiHdl:
    SUB     SP, SP, #(4 * 16)     @ 栈增长
    STMIA   SP, {R0-R12}          @ 保留 R0-R12 寄存器到栈上
    MRS     R3, SPSR              @ 挪动 SPSR 寄存器的值到 R3
    MOV     R4, LR

    AND     R1, R3, #CPSR_MASK_MODE                          @ Interrupted mode
    CMP     R1, #CPSR_USER_MODE                              @ User mode
    BNE     OsKernelSVCHandler                               @ Branch if not user mode

    @ we enter from user mode, we need get the values of  USER mode r13(sp) and r14(lr).
    @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list).
    MOV     R0, SP
    STMFD   SP!, {R3}                                        @ Save the CPSR
    ADD     R3, SP, #(4 * 17)                                @ Offset to pc/cpsr storage
    STMFD   R3!, {R4}                                        @ Save the CPSR and r15(pc)
    STMFD   R3, {R13, R14}^                                  @ Save user mode r13(sp) and r14(lr)
    SUB     SP, SP, #4
    PUSH_FPU_REGS R1

    MOV     FP, #0                                           @ Init frame pointer
    CPSIE   I               @ Interrupt Enable
    BLX     OsArmA32SyscallHandle
    CPSID   I                        @ Interrupt Disable

    POP_FPU_REGS R1
    ADD     SP, SP,#4
    LDMFD   SP!, {R3}                                        @ Fetch the return SPSR
    MSR     SPSR_cxsf, R3                                    @ Set the return mode SPSR

    @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr).
    @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list)

    LDMFD   SP!, {R0-R12}
    LDMFD   SP, {R13, R14}^                                  @ Restore user mode R13/R14
    ADD     SP, SP, #(2 * 4)
    LDMFD   SP!, {PC}^                                       @ Return to user

这段代码的正文较为分明,能够看到,内核模式会持续调用 OsKernelSVCHandler,用户模式会持续调用 OsArmA32SyscallHandle 函数;

OsArmA32SyscallHandle 函数
咱们这里剖析的流程是从用户模式进入的,所以调用的是 OsArmA32SyscallHandle,它的实现位于 kernel/liteos_a/syscall/los_syscall.c 文件:

/* The SYSCALL ID is in R7 on entry.  Parameters follow in R0..R6 */LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs)
{
    UINT32 ret;
    UINT8 nArgs;
    UINTPTR handle;
    UINT32 cmd = regs[REG_R7];

    if (cmd >= SYS_CALL_NUM) {PRINT_ERR("Syscall ID: error %d !!!\n", cmd);
        return regs;
    }

    if (cmd == __NR_sigreturn) {OsRestorSignalContext(regs);
        return regs;
    }

    handle = g_syscallHandle[cmd]; // 失去理论零碎调用处理函数
    nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */
    nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);
    if ((handle == 0) || (nArgs > ARG_NUM_7)) {PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs);
        regs[REG_R0] = -ENOSYS;
        return regs;
    }

    switch (nArgs) { // 以下各个 case 是理论函数调用
        case ARG_NUM_0:
        case ARG_NUM_1:
            ret = (*(SyscallFun1)handle)(regs[REG_R0]);
            break;
        case ARG_NUM_2:
        case ARG_NUM_3:
            ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);
            break;
        case ARG_NUM_4:
        case ARG_NUM_5:
            ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
                                         regs[REG_R4]);
            break;
        default:
            ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
                                         regs[REG_R4], regs[REG_R5], regs[REG_R6]);
    }

    regs[REG_R0] = ret; // 返回值填入 R0

    OsSaveSignalContext(regs);

    /* Return the last value of curent_regs.  This supports context switches on return from the exception.
     * That capability is only used with theSYS_context_switch system call.
     */
    return regs;
}

这个函数中用到了个全局数组 g_syscallHandle 和 g_syscallNArgs,它们的定义以及初始化函数也在同一个文件中:

static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0};static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};void SyscallHandleInit(void)
{#define SYSCALL_HAND_DEF(id, fun, rType, nArg)  \
    if ((id) < SYS_CALL_NUM) {                  \
        g_syscallHandle[(id)] = (UINTPTR)(fun); \
        g_syscallNArgs[(id) / NARG_PER_BYTE] |= \
            ((id) & 1) ? (nArg) << NARG_BITS : (nArg); \
    }

    #include "syscall_lookup.h"#undef SYSCALL_HAND_DEF}

其中 SYSCALL_HAND_DEF 宏的对齐格局我做了一点调整。

从 g_syscallNArgs 成员赋值以及定义的中央,能看出它的每个 UINT8 成员被用来寄存两个零碎调用的参数个数,从而实现更少的内存占用;

syscall_lookup.h 文件和 los_syscall.c 位于同一目录,它记录了零碎调用函数对照表,咱们仅节取一部分:

SYSCALL_HAND_DEF(__NR_read, SysRead, ssize_t, ARG_NUM_3)
SYSCALL_HAND_DEF(__NR_write, SysWrite, ssize_t, ARG_NUM_3) // <-- 咱们要跟踪的 write 在这里 SYSCALL_HAND_DEF(__NR_open, SysOpen, int, ARG_NUM_7)
SYSCALL_HAND_DEF(__NR_close, SysClose, int, ARG_NUM_1)
SYSCALL_HAND_DEF(__NR_creat, SysCreat, int, ARG_NUM_2)
SYSCALL_HAND_DEF(__NR_unlink, SysUnlink, int, ARG_NUM_1)#ifdef LOSCFG_KERNEL_DYNLOADSYSCALL_HAND_DEF(__NR_execve, SysExecve, int, ARG_NUM_3)#endif

看到这里,write 零碎调用的内核函数终于找到了——SysWrite。
到此,咱们曾经晓得了 liteos- a 的零碎调用机制是如何实现的。

liteos- a 内核 SysWrite 的实现

SysWrite 函数的实现位于 kernel/liteos_a/syscall/fs_syscall.c 文件:

ssize_t SysWrite(int fd, const void *buf, size_t nbytes)
{
    int ret;

    if (nbytes == 0) {return 0;}

    if (!LOS_IsUserAddressRange((vaddr_t)(UINTPTR)buf, nbytes)) {return -EFAULT;}

    /* Process fd convert to system global fd */
    fd = GetAssociatedSystemFd(fd);

    ret = write(fd, buf, nbytes); // <--??似曾相识??if (ret < 0) {return -get_errno();
    }
    return ret;
}

它又调用了 write?然而这一次是内核空间的 write,不再是 musl libc,通过一番搜寻,咱们能够找到另一个文件 third_party/NuttX/fs/vfs/fs_write.c 中的 write:

ssize_t write(int fd, FAR const void *buf, size_t nbytes) {#if CONFIG_NFILE_DESCRIPTORS > 0
  FAR struct file *filep;
  if ((unsigned int)fd >= CONFIG_NFILE_DESCRIPTORS)#endif
  {/* Write to a socket descriptor is equivalent to send with flags == 0 */#if defined(LOSCFG_NET_LWIP_SACK)
      FAR const void *bufbak = buf;
      ssize_t ret;
      if (LOS_IsUserAddress((VADDR_T)(uintptr_t)buf)) {if (buf != NULL && nbytes > 0) {buf = malloc(nbytes);
              if (buf == NULL) {/* 省略 错误处理 代码 */}
              if (LOS_ArchCopyFromUser((void*)buf, bufbak, nbytes) != 0) {/* 省略 */}
          }
      }
      ret = send(fd, buf, nbytes, 0); // 这个分支是解决 socket fd 的
      if (buf != bufbak) {free((void*)buf);
      }
      return ret;#else
      set_errno(EBADF);
      return VFS_ERROR;#endif
  }#if CONFIG_NFILE_DESCRIPTORS > 0
  /* The descriptor is in the right range to be a file descriptor... write
   * to the file.
   */
  if (fd <= STDERR_FILENO && fd >= STDIN_FILENO) {/* fd : [0,2] */
      fd = ConsoleUpdateFd();
      if (fd < 0) {set_errno(EBADF);
          return VFS_ERROR;
      }
  }

  int ret = fs_getfilep(fd, &filep);
  if (ret < 0) {
      /* The errno value has already been set */
      return VFS_ERROR;
  }

  if (filep->f_oflags & O_DIRECTORY) {set_errno(EBADF);
      return VFS_ERROR;
  }

  if (filep->f_oflags & O_APPEND) {if (file_seek64(filep, 0, SEEK_END) == -1) {return VFS_ERROR;}
  }

  /* Perform the write operation using the file descriptor as an index */
  return file_write(filep, buf, nbytes);#endif}

找到这段代码,咱们晓得了:

liteos- a 的 vfs 是在 NuttX 根底上实现的,NuttX 是一个开源 RTOS 我的项目;

liteos- a 的 TCP/IP 协定栈是基于 lwip 的,lwip 也是一个开源我的项目;

这段代码中的 write 分为两个分支,socket fd 调用 lwip 的 send,另一个分支调用 file_write;

至于,file_write 如何调用到存储设备驱动程序,则是更底层的实现了,本文不在持续剖析。

补充阐明

本文内容均是基于鸿蒙零碎开源我的项目 OpenHarmony 源码动态剖析所整顿,没有进行理论的运行环境调试,理论执行过程可能有所差别,心愿发现错误的读者及时斧正。文中所有门路均为整个 openharmony 源码树上的相对路径(而非 liteos 源码相对路径)。

参考链接

ARM Architecture Reference Manual ® ARMv7-A and ARMv7-R edition:https://developer.arm.com/doc…

gcc 内嵌汇编文档的扩大汇编阐明:https://gcc.gnu.org/onlinedoc…

鸿蒙官网文档“内核子系统”:https://gitee.com/openharmony/docs/blob/master/readme/%E5%86%85%E6%A0%B8%E5%AD%90%E7%B3%BB%E7%BB%9FREADME.md

鸿蒙官网文档“OpenHarmony 轻内核”:https://gitee.com/openharmony/docs/blob/master/kernel/Readme-CN.md

NuttX:https://nuttx.apache.org/

Lwip:https://savannah.nongnu.org/p…

本文参加了「解读鸿蒙源码」技术征文,欢送正在浏览的你也退出。


原文链接:https://developer.huawei.com/consumer/cn/forum/topic/0201398672740480099?fid=0101303901040230869

原作者:思维

正文完
 0