0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于鴻蒙系統(tǒng)開源項(xiàng)目OpenHarmony源碼靜態(tài)分析

鴻蒙系統(tǒng)HarmonyOS ? 來源:oschina ? 作者:xu4v ? 2020-10-16 10:51 ? 次閱讀

本文將首先帶您回顧“系統(tǒng)調(diào)用”的概念以及它的作用,然后從經(jīng)典的Hello World開始,逐行代碼層層分析——鴻蒙OS的系統(tǒng)調(diào)用是如何實(shí)現(xiàn)的。

寫在前面

9月10號 華為開發(fā)者大會(HDC)上,華為向廣大開發(fā)者宣布了鴻蒙2.0系統(tǒng)開源,源碼托管在國內(nèi)源碼托管平臺“碼云”上:https://openharmony.gitee.com/

我也第一時(shí)間從碼云下載鴻蒙系統(tǒng)的源代碼,并進(jìn)行了編譯和分析。當(dāng)晚回看了HDC上的關(guān)于鴻蒙OS 2.0的主題演講,個(gè)人最為好奇的是——這次開源的liteos-a內(nèi)核。因?yàn)樗С至藥MU(內(nèi)存管理單元)的ARM Cortex-A設(shè)備;我們知道,在帶有MMU的處理器上,可以實(shí)現(xiàn)虛擬內(nèi)存,進(jìn)而實(shí)現(xiàn)進(jìn)程之間的隔離、內(nèi)核態(tài)和用戶態(tài)的隔離等等這些功能。

系統(tǒng)調(diào)用簡介

引用一張官方文檔中的圖片,看看liteos-a內(nèi)核在整個(gè)系統(tǒng)中的位置。

這次開源的鴻蒙系統(tǒng)中同時(shí)包含了兩個(gè)內(nèi)核,分別是liteos-a和liteos-m,其中的liteos-m和以前開源的LiteOS相當(dāng),而liteos-a是面向應(yīng)用處理器的操作系統(tǒng)內(nèi)核,提供了更為豐富的內(nèi)核功能。此前已經(jīng)開源的LiteOS,只是一個(gè)實(shí)時(shí)操作系統(tǒng)(RTOS),它主要面向的是內(nèi)存和閃存配置都比較低的微控制器。

我們先來簡單回顧一下操作系統(tǒng)課程的一個(gè)知識點(diǎn)——系統(tǒng)調(diào)用,以及為什么會有系統(tǒng)調(diào)用?它的作用是什么?如果你對于這兩個(gè)問題以及了然于心,可以直接跳過本段,看后面的源碼分析部分。

在微控制器這樣的系統(tǒng)資源較少的硬件系統(tǒng)(比如STM32、MSP430、AVR、8051)上,通常直接裸跑程序(也就是不使用任何操作系統(tǒng)),或者使用像FreeRTOS、Zephyr這一類的實(shí)時(shí)操作系統(tǒng)(RTOS)。這些實(shí)時(shí)操作系統(tǒng)中,應(yīng)用程序和內(nèi)核程序直接運(yùn)行在同一個(gè)物理內(nèi)存空間(因?yàn)檫@些設(shè)備一般沒有MMU)上。而RTOS只提供了線程(或者叫任務(wù)),線程間同步、互斥等基礎(chǔ)設(shè)施;應(yīng)用程序可以直接調(diào)用內(nèi)核函數(shù)(用戶程序和內(nèi)核程序只是邏輯上的劃分,本質(zhì)上并沒有太大不同);一旦有一個(gè)線程發(fā)生異常,整個(gè)系統(tǒng)就會重啟。

而在ARM Cortex-A、x86、x86-64這樣的系統(tǒng)資源豐富的硬件系統(tǒng)上,SoC或CPU芯片內(nèi)部一般集成了MMU,而且CPU有特權(quán)級別狀態(tài)(狀態(tài)寄存器的某些位)。基于特權(quán)級別狀態(tài),可以實(shí)現(xiàn)部分硬件相關(guān)的操作只能在內(nèi)核態(tài)進(jìn)行,例如訪問外設(shè)等,用戶態(tài)應(yīng)用程序不能訪問硬件設(shè)備。在這樣的系統(tǒng)上,系統(tǒng)調(diào)用是用戶態(tài)應(yīng)用程序調(diào)用內(nèi)核功能的請求入口。通俗的說,系統(tǒng)調(diào)用就是在有內(nèi)核態(tài)和用戶態(tài)隔離的操作系統(tǒng)上,用戶態(tài)進(jìn)程訪問內(nèi)核態(tài)資源的一種方式。

從Hello World開始

接下來,我們一起從鴻蒙系統(tǒng)源碼分析它在liteos-a內(nèi)核上是如何實(shí)現(xiàn)系統(tǒng)調(diào)用的。鴻蒙OS使用了musl libc,應(yīng)用程序和系統(tǒng)服務(wù)都通過musl libc封裝的系統(tǒng)調(diào)用API接口訪問內(nèi)核相關(guān)功能。

下面,我們就從經(jīng)典的helloworld分析整個(gè)系統(tǒng)調(diào)用的流程。鴻蒙系統(tǒng)目前官方支持了三個(gè)芯片平臺,分別是Hi3516DV300(雙核ARM Cortex A-7 @ 900M Hz),Hi3518EV300(單核ARM Cortex A-7 @ 900MHz 內(nèi)置64MB DDR2內(nèi)存)和Hi3861V100(單核RISC-V @160M Hz 內(nèi)置 SRAM 和 Flash)。其中Hi3516和Hi3518是帶有Cortex A7內(nèi)核的芯片,鴻蒙系統(tǒng)在這兩個(gè)平臺使用的內(nèi)核自然是liteos-a。根據(jù)官方指導(dǎo)文檔,我們知道這兩個(gè)平臺的第一個(gè)應(yīng)用程序示例都是helloworld,源碼路徑為:applications/sample/camera/app/src/helloworld.c,除去頭部注釋,代碼內(nèi)容為:

#include 
#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函數(shù)實(shí)現(xiàn)分析

文件路徑: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;
}

我們看到了,這里使用標(biāo)準(zhǔn)庫的stdout作為第一個(gè)參數(shù)調(diào)用了vfprintf,我們繼續(xù)向下分析third_party/musl/src/stdio/vfprintf.c文件:

int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap)

{
// 刪減若干和參數(shù) f 無關(guān)的代碼行
	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;
}

這里,我們繼續(xù)關(guān)注三處帶有參數(shù)f的調(diào)用:__towrite(f),printf_core(f, fmt, &ap2, nl_arg, nl_type),f->write(f, 0, 0);

其中,__towrite的實(shí)現(xiàn)位于third_party/musl/src/stdio/__towrite.c(可見和系統(tǒng)調(diào)用無關(guān)):

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;
}

從內(nèi)容上看,__towrite函數(shù)的作用是更新文件結(jié)構(gòu)FILE的wpos、wbase、wend成員,以指向待寫入實(shí)際文件的內(nèi)存緩沖區(qū)域,同時(shí)將rpos、rend值為零。

printf_core的實(shí)現(xiàn)也位于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=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) && pINTMAX_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

=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: // 刪除了錯(cuò)誤處理代碼 overflow: // 刪除了錯(cuò)誤處理代碼 }

從注釋和代碼結(jié)構(gòu)可以看出,這個(gè)函數(shù)實(shí)現(xiàn)了格式化字符串展開的主要流程,這里又調(diào)用了out和pad兩個(gè)函數(shù),從命名猜測應(yīng)該分別是向內(nèi)存緩沖區(qū)寫入內(nèi)容和填充內(nèi)容的函數(shù),它們的實(shí)現(xiàn)也位于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);
}

它們又調(diào)用了__fwritex,它的實(shí)現(xiàn)位于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;
}

這里又出現(xiàn)了vfprintf中出現(xiàn)的f->write(f, s, i),下面我們就分析這個(gè)函數(shù)實(shí)際底是什么?

我們先找到它的定義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); // <--關(guān)注它
	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;
};

我們再繼續(xù)尋找stdout的各個(gè)成員值是什么?

可以找到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 和多數(shù)UNIX系統(tǒng)一樣
	.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);
}

這段代碼里調(diào)用了SYS_ioctl系統(tǒng)調(diào)用,但主體流程是下方的函數(shù)__stdio_write,它的實(shí)現(xiàn)在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函數(shù)最終調(diào)用到了兩個(gè)系統(tǒng)調(diào)用SYS_ioctl和SYS_write。

musl libc的syscall函數(shù)實(shí)現(xiàn)分析

在上一節(jié)中,我們看到printf最終調(diào)用到了兩個(gè)長得像系統(tǒng)調(diào)用的函數(shù)syscall和__syscall。

系統(tǒng)調(diào)用宏syscall的實(shí)現(xiàn)

在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調(diào)用了__syscall,而__syscall又調(diào)用了__SYSCALL_DISP,它的實(shí)現(xiàn)也在同一個(gè)文件中:

#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中調(diào)用syscall處進(jìn)行分析,即嘗試展開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 和宏參數(shù) a,b,c,d 對應(yīng)
// 7,6,5,4 和宏參數(shù) e,f,g,h 對應(yīng)
// 3 和宏參數(shù) n 對應(yīng)
// 宏表達(dá)式的值為 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)展開流程,結(jié)果應(yīng)該是:

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

系統(tǒng)調(diào)用函數(shù)__syscall3的實(shí)現(xiàn)

這些__syscall[1-7]的系統(tǒng)調(diào)用包裝宏定義如下:

#ifndef __scc
#define __scc(X) ((long) (X)) // 轉(zhuǎn)為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))

繼續(xù)搜索發(fā)現(xiàn)有多出匹配,我們關(guān)注arch/arm目錄下的文件,因?yàn)锳RM Cortext A7是Armv7-A指令集的32位CPU(如果是Armv8-A指令集的64位CPU則對應(yīng)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));
}

這段代碼中還有三個(gè)宏,__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

它們有兩個(gè)實(shí)現(xiàn)版,分別對應(yīng)于編譯器THUMB選項(xiàng)的開啟和關(guān)閉。這兩種選項(xiàng)條件下的代碼流程基本一致,以下僅以未開啟THUMB選項(xiàng)為例進(jìn)行分析。這兩個(gè)宏展開后的__syscall3函數(shù)內(nèi)容為:

static inline long __syscall3(long n, long a, long b, long c)
{
	register long r7 __asm__("r7") = n; // 系統(tǒng)調(diào)用號
	register long r0 __asm__("r0") = a; // 參數(shù)0
	register long r1 __asm__("r1") = b; // 參數(shù)1
	register long r2 __asm__("r2") = c; // 參數(shù)2
	do { \
        __asm__ __volatile__ ( "svc 0" \
        : "=r"(r0) : "r"(r7), "0"(r0), "r"(r1), "r"(r2) : "memory"); \
        return r0; \
	} while (0);
}

這里最后的一個(gè)內(nèi)嵌匯編比較復(fù)雜,它符合如下格式(具體細(xì)節(jié)可以查閱gcc內(nèi)嵌匯編文檔的擴(kuò)展匯編說明):

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

匯編模板為:"svc 0", 輸出參數(shù)部分為:"=r"(r0),輸出寄存器為r0輸入?yún)?shù)部分為:"r"(r7), "0"(r0), "r"(r1), "r"(r2),輸入寄存器為r7,r0,r1,r2,("0"的含義是,這個(gè)輸入寄存器必須和輸出寄存器第0個(gè)位置一樣) Clobber部分為:"memory"

這里我們只需要記?。合到y(tǒng)調(diào)用號存放在r7寄存器,參數(shù)存放在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指令會觸發(fā)一個(gè)“特權(quán)調(diào)用”異常。這為非特權(quán)軟件調(diào)用操作系統(tǒng)或其他只能在PL1級別訪問的系統(tǒng)組件提供了一種機(jī)制。

詳細(xì)的指令說明在

到這里,我們分析了鴻蒙系統(tǒng)上應(yīng)用程序如何進(jìn)入內(nèi)核態(tài),主要分析的是musl libc的實(shí)現(xiàn)。

liteos-a內(nèi)核的系統(tǒng)調(diào)用實(shí)現(xiàn)分析

既然SVC能夠觸發(fā)一個(gè)異常,那么我們就要看看liteos-a內(nèi)核是如何處理這個(gè)異常的。

ARM Cortex A7中斷向量表

在ARM架構(gòu)參考手冊中,可以找到中斷向量表的說明:

可以看到SVC中斷向量的便宜地址是0x08,我們可以在kernel/liteos_a/arch/arm/arm/src/startup目錄的reset_vector_mp.S文件和reset_vector_up.S文件中找到相關(guān)匯編代碼:

__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目錄有兩個(gè)文件reset_vector_mp.S文件和reset_vector_up.S文件分別對應(yīng)多核和單核編譯選項(xiàng):

ifeq ($(LOSCFG_KERNEL_SMP), y)
LOCAL_SRCS += src/startup/reset_vector_mp.S
else
LOCAL_SRCS += src/startup/reset_vector_up.S
endif

SVC中斷處理函數(shù)

上面的匯編代碼中可以看到,_osExceptSwiHdl函數(shù)就是SVC異常處理函數(shù),具體實(shí)現(xiàn)在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              @ 移動(dòng)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

這段代碼的注釋較為清楚,可以看到,內(nèi)核模式會繼續(xù)調(diào)用OsKernelSVCHandler,用戶模式會繼續(xù)調(diào)用OsArmA32SyscallHandle函數(shù);

OsArmA32SyscallHandle函數(shù)

我們這里分析的流程是從用戶模式進(jìn)入的,所以調(diào)用的是OsArmA32SyscallHandle,它的實(shí)現(xiàn)位于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]; // 得到實(shí)際系統(tǒng)調(diào)用處理函數(shù)
    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) { // 以下各個(gè)case是實(shí)際函數(shù)調(diào)用
        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è)函數(shù)中用到了個(gè)全局?jǐn)?shù)組g_syscallHandle和g_syscallNArgs,它們的定義以及初始化函數(shù)也在同一個(gè)文件中:

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宏的對齊格式我做了一點(diǎn)調(diào)整。

從g_syscallNArgs成員賦值以及定義的地方,能看出它的每個(gè)UINT8成員被用來存放兩個(gè)系統(tǒng)調(diào)用的參數(shù)個(gè)數(shù),從而實(shí)現(xiàn)更少的內(nèi)存占用;

syscall_lookup.h文件和los_syscall.c位于同一目錄,它記錄了系統(tǒng)調(diào)用函數(shù)對照表,我們僅節(jié)取一部分:

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_DYNLOAD
SYSCALL_HAND_DEF(__NR_execve, SysExecve, int, ARG_NUM_3)
#endif

看到這里,write系統(tǒng)調(diào)用的內(nèi)核函數(shù)終于找到了——SysWrite。

到此,我們已經(jīng)知道了liteos-a的系統(tǒng)調(diào)用機(jī)制是如何實(shí)現(xiàn)的。

liteos-a內(nèi)核SysWrite的實(shí)現(xiàn)

SysWrite函數(shù)的實(shí)現(xiàn)位于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;
}

它又調(diào)用了write?但是這一次是內(nèi)核空間的write,不再是 musl libc,經(jīng)過一番搜索,我們可以找到另一個(gè)文件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) { /* 省略 錯(cuò)誤處理 代碼 */ }
              if (LOS_ArchCopyFromUser((void*)buf, bufbak, nbytes) != 0) {/* 省略 */}
          }
      }
      ret = send(fd, buf, nbytes, 0); // 這個(gè)分支是處理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基礎(chǔ)上實(shí)現(xiàn)的,NuttX是一個(gè)開源RTOS項(xiàng)目;

liteos-a的TCP/IP協(xié)議棧是基于lwip的,lwip也是一個(gè)開源項(xiàng)目;

這段代碼中的write分為兩個(gè)分支,socket fd調(diào)用lwip的send,另一個(gè)分支調(diào)用file_write;

至于,file_write如何調(diào)用到存儲設(shè)備驅(qū)動(dòng)程序,則是更底層的實(shí)現(xiàn)了,本文不在繼續(xù)分析。

補(bǔ)充說明

本文內(nèi)容均是基于鴻蒙系統(tǒng)開源項(xiàng)目OpenHarmony源碼靜態(tài)分析所整理,沒有進(jìn)行實(shí)際的運(yùn)行環(huán)境調(diào)試,實(shí)際執(zhí)行過程可能有所差異,希望發(fā)現(xiàn)錯(cuò)誤的讀者及時(shí)指正。文中所有路徑均為整個(gè)openharmony源碼樹上的相對路徑(而非liteos源碼相對路徑)
編輯:hfy

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • RTOS
    +關(guān)注

    關(guān)注

    20

    文章

    804

    瀏覽量

    119116
  • 鴻蒙系統(tǒng)
    +關(guān)注

    關(guān)注

    183

    文章

    2627

    瀏覽量

    65785
  • OpenHarmony
    +關(guān)注

    關(guān)注

    25

    文章

    3548

    瀏覽量

    15740
收藏 人收藏

    評論

    相關(guān)推薦

    開放原子開源生態(tài)大會OpenHarmony生態(tài)主題演講報(bào)名開啟

    開源賦能產(chǎn)業(yè),生態(tài)共筑未來,OpenAtom OpenHarmony(簡稱“OpenHarmony”)項(xiàng)目群工作委員會將于9月26日上午舉辦Open
    發(fā)表于 09-19 22:02

    【免費(fèi)分享】OpenHarmony鴻蒙物聯(lián)網(wǎng)開發(fā)板資料包一網(wǎng)打盡,附教程/視頻/項(xiàng)目/源碼...

    ?想要深入學(xué)習(xí)鴻蒙設(shè)備開發(fā)及鴻蒙物聯(lián)網(wǎng)開發(fā)嗎?現(xiàn)在機(jī)會來了!我們?yōu)槌鯇W(xué)者們準(zhǔn)備了一份全面的資料包,包括原理圖、教程、視頻、項(xiàng)目、源碼等,所有資料全部免費(fèi)領(lǐng)取,課程視頻可試看(購買后看完
    的頭像 發(fā)表于 09-14 14:09 ?157次閱讀
    【免費(fèi)分享】<b class='flag-5'>OpenHarmony</b><b class='flag-5'>鴻蒙</b>物聯(lián)網(wǎng)開發(fā)板資料包一網(wǎng)打盡,附教程/視頻/<b class='flag-5'>項(xiàng)目</b>/<b class='flag-5'>源碼</b>...

    開源鴻蒙】使用QEMU運(yùn)行OpenHarmony輕量系統(tǒng)

    本文將會介紹如何從源碼安裝QEMU 6.2.0,以及如何使用QEMU運(yùn)行OpenHarmony輕量系統(tǒng)。通過本文,你將會對QEMU和OpenHarmony輕量
    的頭像 發(fā)表于 09-14 08:51 ?209次閱讀
    【<b class='flag-5'>開源</b><b class='flag-5'>鴻蒙</b>】使用QEMU運(yùn)行<b class='flag-5'>OpenHarmony</b>輕量<b class='flag-5'>系統(tǒng)</b>

    鴻蒙OpenHarmony南向/北向快速開發(fā)教程-迅為RK3568開發(fā)板

    4.1學(xué)習(xí)之旅了嗎?快來加入我們,一起探索鴻蒙4.1系統(tǒng)的無限魅力吧! 【北京迅為】OpenHarmony學(xué)習(xí)開發(fā)系列教程(第1期 北向基礎(chǔ)篇一) P0_先導(dǎo)課 P1_OpenHarmony
    發(fā)表于 07-23 10:44

    迅龍軟件:作為OpenHarmony項(xiàng)目捐贈人,基于開源鴻蒙的OrangePi OS(OH)正在通過XTS認(rèn)證

    作為開放原子開源基金會的白銀捐贈人、OpenHarmony項(xiàng)目群C類捐贈人,迅龍軟件受邀參加今年5月在深圳舉行的以“鴻心聚力,智引未來”為主題的OpenHarmony開發(fā)者大會2024
    的頭像 發(fā)表于 07-23 09:19 ?401次閱讀
    迅龍軟件:作為<b class='flag-5'>OpenHarmony</b><b class='flag-5'>項(xiàng)目</b>捐贈人,基于<b class='flag-5'>開源</b><b class='flag-5'>鴻蒙</b>的OrangePi OS(OH)正在通過XTS認(rèn)證

    開源鴻蒙 編譯OpenHarmony輕量系統(tǒng)QEMU RISC-V版本

    本文將介紹如何為QEMU RISC-V虛擬平臺構(gòu)建OpenHarmony輕量系統(tǒng)。得益于QEMU的CPU指令集模擬執(zhí)行能力,該方法可以在沒有開發(fā)板的情況下調(diào)試和運(yùn)行OpenHarmony系統(tǒng)
    的頭像 發(fā)表于 07-15 10:36 ?812次閱讀
    <b class='flag-5'>開源</b><b class='flag-5'>鴻蒙</b> 編譯<b class='flag-5'>OpenHarmony</b>輕量<b class='flag-5'>系統(tǒng)</b>QEMU RISC-V版本

    如何在OpenHarmony設(shè)置靜態(tài)IP?

    介紹本文適用于所有RK3566/RK3568/RK3588平臺產(chǎn)品在OpenHarmony系統(tǒng)上設(shè)置靜態(tài)IP。本文以PurplePiOH開發(fā)板為例,在OpenHarmony
    的頭像 發(fā)表于 05-12 08:32 ?448次閱讀
    如何在<b class='flag-5'>OpenHarmony</b>設(shè)置<b class='flag-5'>靜態(tài)</b>IP?

    開源鴻蒙】下載OpenHarmony 4.1 Release源代碼

    本文介紹了如何下載開源鴻蒙OpenHarmony)操作系統(tǒng) 4.1 Release版本的源代碼,該方法同樣可以用于下載OpenHarmony
    的頭像 發(fā)表于 04-27 23:16 ?592次閱讀
    【<b class='flag-5'>開源</b><b class='flag-5'>鴻蒙</b>】下載<b class='flag-5'>OpenHarmony</b> 4.1 Release源代碼

    鴻蒙OpenHarmony【標(biāo)準(zhǔn)系統(tǒng)編寫“Hello World”程序】 (基于RK3568開發(fā)板)

    源碼],創(chuàng)建RK3568開發(fā)板的源碼工程。 示例目錄 拉取openharmony項(xiàng)目代碼,在代碼根目錄創(chuàng)建sample子系統(tǒng)文件夾,在子
    的頭像 發(fā)表于 04-24 17:32 ?575次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>OpenHarmony</b>【標(biāo)準(zhǔn)<b class='flag-5'>系統(tǒng)</b>編寫“Hello World”程序】 (基于RK3568開發(fā)板)

    鴻蒙OpenHarmony【創(chuàng)建工程并獲取源碼

    在通過DevEco Device Tool創(chuàng)建OpenHarmony工程時(shí),可自動(dòng)下載相應(yīng)版本的OpenHarmony源碼。
    的頭像 發(fā)表于 04-19 21:40 ?268次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>OpenHarmony</b>【創(chuàng)建工程并獲取<b class='flag-5'>源碼</b>】

    鴻蒙OS和開源鴻蒙什么關(guān)系?

    開源鴻蒙(Open Harmony) 鴻蒙系統(tǒng)愿來的設(shè)計(jì)初衷,就是讓所有設(shè)備都可以運(yùn)行一個(gè)系統(tǒng),但是每個(gè)設(shè)備的運(yùn)算能力和功能都不同,所以內(nèi)核
    的頭像 發(fā)表于 01-30 15:44 ?825次閱讀
    <b class='flag-5'>鴻蒙</b>OS和<b class='flag-5'>開源</b><b class='flag-5'>鴻蒙</b>什么關(guān)系?

    【年度精選】2023年度top5榜單——鴻蒙開發(fā)經(jīng)驗(yàn)

    推薦理由: 本帖將指導(dǎo)你完成OpenHarmony開源鴻蒙無人項(xiàng)目的開發(fā),讓你了解如何使用鴻蒙系統(tǒng)
    發(fā)表于 01-10 17:19

    報(bào)名啟動(dòng)|OpenHarmony源碼轉(zhuǎn)換器—多線程特性轉(zhuǎn)換賽題

    點(diǎn)擊藍(lán)字 ╳ 關(guān)注我們 開源項(xiàng)目 OpenHarmony 是每個(gè)人的 OpenHarmony 原文標(biāo)題:報(bào)名啟動(dòng)|OpenHarmony
    的頭像 發(fā)表于 12-29 16:15 ?550次閱讀
    報(bào)名啟動(dòng)|<b class='flag-5'>OpenHarmony</b><b class='flag-5'>源碼</b>轉(zhuǎn)換器—多線程特性轉(zhuǎn)換賽題

    復(fù)旦、浙大、南開等17所高校成立開源鴻蒙OpenHarmony技術(shù)俱樂部

    據(jù)介紹,OpenAtom OpenHarmony 是由開放原子開源基金會孵化及運(yùn)營的開源項(xiàng)目,目標(biāo)是面向全場景、全連接、全智能時(shí)代,基于開源
    的頭像 發(fā)表于 11-08 17:09 ?674次閱讀

    鴻蒙操作系統(tǒng)的前世今生

    部分,OpenHarmonyOS、包括HMS在內(nèi)的閉源應(yīng)用與服務(wù),以及其他開放源代碼。 其中OpenHarmonyOS 是鴻蒙操作系統(tǒng)開源
    發(fā)表于 10-08 19:55