u-boot-2016.5 시작 프로세스
ENTRY(_start)
에서 알 수 있는 프로그램의 입구는start, SourceInsight에서 검색 가능한 프로그램의 입구 찾기start는 u-boot-2016.05\arch\arm\lib\vectors에 있습니다.S에서....
ENTRY(_start)
SECTIONS
{
...
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
CPUDIR/start.o (.text*)
*(.text*)
}
...
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
...
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
...
}
boot-2016.05\arch\arm\lib\vectors에 들어갑니다.S에서 볼 수 있는start가 시작되면 reset으로 이동합니다:
...
.globl _start
...
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
...
1、u-boot-2016.05\arch\arm\cpu\arm920t\start.S에서 reset의 주요 실행 프로세스: reset -> cpuinit_crit -> lowlevel_init -> _main
reset:
...
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
bl _main
...
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
...
bl lowlevel_init
...
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
2,blmain에서 u-boot-2016.05\arch\arm\lib\crt0으로 이동합니다.S에서 포털로부터main에서 주요 실행 프로세스를 시작합니다:boardinit_f -> relocate_code -> board_init_r
ENTRY(_main)
...
bl board_init_f_alloc_reserve
...
bl board_init_f_init_reserve
...
bl board_init_f
#if ! defined(CONFIG_SPL_BUILD)
...
b relocate_code
...
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
...
#if defined(CONFIG_SYS_THUMB_BUILD)
...
#else
ldr pc, =board_init_r
#endif
#endif
ENDPROC(_main)
이 부분에는 세 가지 설명이 있다. (1) u-boot-2016.05\common\boardf.c:board_init_f initcall 통과run_list(init sequence f) 함수는 전반부 보드 레벨 초기화를 위한 일련의 초기화 함수를 실행합니다.글로벌 구조체 gd는 u-boot-2016.05\arch\arm\include\asm\글로벌data.h에서 선언:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
⑵、u-boot-2016.05\arch\arm\lib\relocate.S:relocate_코드는 uboot 코드의 재배치를 실현합니다. 이 부분은 원본 코드가 간단명료하지 않다고 생각하면 스스로 고칠 수 있습니다.(3) uboot를 다시 지정하는 데는 두 가지 경로가 있다. 하나는 gd->flags를 0으로 설정하고 함수 서열을 초기화하는 initsequence_f의 jumpto_copy 함수에서relocate 로 이동code:
static int jump_to_copy(void)
{
if (gd->flags & GD_FLG_SKIP_RELOC)
return 0;
...
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
...
#else
relocate_code(gd->start_addr_sp, gd->new_gd, gd->relocaddr);
#endif
return 0;
}
다른 하나는 CONFIG 를 매크로 정의하지 않는 것이다SPL_그리고 u-boot-2016.05\arch\arm\lib\crt0.S에서 통과
#if ! defined(CONFIG_SPL_BUILD)
...
b relocate_code
...
#endif
relocate 로 이동code.상기 두 가지 방법 중 하나를 선택하면 다른 하나는 없애야 한다.3、이전 단계에서ldrpc를 통과,=boardinit_r명령 u-boot-2016.05\common\boardr.c:board_init_r 함수, 더 나아가 initcall 호출run_list(init sequence r) 함수는 후반부 보드 레벨 초기화를 위한 일련의 초기화 함수를 실행하고 initcallrun_list 함수에서run 입장main_loop이 다시 돌아오지 않습니다.
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
...
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
init_sequence_r는 함수 포인터 그룹으로 초기화 함수 포인터가 많이 저장되어 있으며 그 안에 두 가지 중요한 함수 포인터 initrannounce 및 runmain_loop:
init_fnc_t init_sequence_r[] = {
...
initr_announce,
...
run_main_loop,
};
initr_announce 함수 선언은 여기서부터 RAM으로 건너뜁니다.
static int initr_announce(void)
{
debug("Now running in RAM - U-Boot at: %08lx
", gd->relocaddr);
return 0;
}
마지막은 런main_loop,run 입장main_loop 후 다시 돌아오지 않습니다.
4、runmain_loop에서 u-boot-2016.05\common\main.c:main_loop 함수
static int run_main_loop(void)
{
...
for (;;)
main_loop();
return 0;
}
main 입장loop 이전에 초기화가 완료되었으니 명령을 처리할 준비를 하세요
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
...
/* get environment_variable: s = getenv("bootcmd"); -> bootcmd */
s = bootdelay_process();
...
autoboot_command(s);
...
}
main_loop 함수에는 두 가지 중요한 과정이 있다. (1) 먼저 bootdelay프로세스 함수에서 s = getenv ("bootcmd") 를 통해bootcmd 파라미터를 받아서bootcmd 파라미터를 되돌려줍니다.
const char *bootdelay_process(void)
{
char *s;
int bootdelay;
...
s = getenv("bootdelay");
...
debug("### main_loop entered: bootdelay=%d
", bootdelay);
...
s = getenv("bootcmd");
...
stored_bootdelay = bootdelay;
return s;
}
여기서 bootcmd 매개 변수는 다음과 같은 방법으로 지정됩니다. 먼저 u-boot-2016.05\include\envdefault.h중
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
u-boot-2016.05\include\configs\smdk2440.h에서 지정
#define CONFIG_BOOTCOMMAND "nand read 30000000 kernel;bootm 30000000"
⑵ 그리고 autobootcommand 함수,bootcmd 매개 변수를 입력하고run 에 들어갑니다command_list 함수, bootcmd 매개 변수를 계속 전송
void autoboot_command(const char *s)
{
...
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
...
run_command_list(s, -1, 0);
...
}
...
}
5、autoboot 에서command 함수 u-boot-2016.05\common\cli.c:run_command_list 함수 이후 board 호출run_command 함수로 명령 실행
int run_command_list(const char *cmd, int len, int flag)
{
int need_buff = 1;
char *buff = (char *)cmd; /* cast away const */
int rcode = 0;
if (len == -1) {
len = strlen(cmd);
#ifdef CONFIG_SYS_HUSH_PARSER
...
#else
/* the built-in parser will change our string if it sees
*/
need_buff = strchr(cmd, '
') != NULL;
#endif
}
if (need_buff) {
buff = malloc(len + 1);
if (!buff)
return 1;
memcpy(buff, cmd, len);
buff[len] = '\0';
}
#ifdef CONFIG_SYS_HUSH_PARSER
...
#ifdef CONFIG_CMDLINE
...
#else
rcode = board_run_command(buff);
#endif
#endif
...
}
그럼,boardrun_command는 어떻게 명령을 실행합니까?우선,boardrun_command 함수는bootcmd 매개 변수의bootm 명령을 통해 u-boot-2016.05\cmd\bootm를 찾습니다.c의
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"boot application image from memory", bootm_help_text
);
그리고 이 정보에 따라bootm 명령을 실행하는 처리 함수 포인터do 를 찾습니다bootm, Do 진입bootm 함수는 관련 코드를 실행하고 UBOOT_CMD는 u-boot-2016.05\include\command.h에서 정의된 내용:
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, \
_comp) \
_CMD_REMOVE(sub_ ## _name, _cmd)
#define _CMD_REMOVE(_name, _cmd) \
int __remove_ ## _name(void) \
{ \
if (0) \
_cmd(NULL, 0, 0, NULL); \
return 0; \
}
여기, boardrun_command 함수는bootm 명령의 매개 변수(내부 이미지가 있는 주소) 30000000을bootm 에 부여합니다headers_t구조체 변수 images의 경우 images의 첫 주소는 30000000이고 images는 u-boot-2016.05\cmd\bootm에 있다.c에서 정의한 내용:
bootm_headers_t images;
6、U 에 따라BOOT_CMD 정보가 u-boot-2016.05\cmd\bootm로 이동합니다.c:do_bootm 함수
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
BOOTM_STATE_LOADOS |
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
BOOTM_STATE_OS_CMDLINE |
#endif
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO, &images, 1);
}
여기서 BOOTMSTATE_START 、BOOTM_STATE_FINDOS 、BOOTM_STATE_FINDOTHER 、BOOTM_STATE_LOADOS 、BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 이것들은 u-boot-2016.05\include\image.h중bootmheaders 구조체에서 지정:
#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO (0x00000400)
7、do 에서bootm u-boot-2016.05\common\bootm.c:do_bootm_states 함수, Now run the OS!
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
images->state |= states;
/*
* Work through the states and see how far we get. We stop on
* any error.
*/
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}
/* Load the OS */
if (!ret && (states & BOOTM_STATE_LOADOS)) {
ulong load_end;
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
if (ret == 0)
lmb_reserve(&images->lmb, images->os.load,
(load_end - images->os.load));
else if (ret && ret != BOOTM_ERR_OVERLAP)
goto err;
else if (ret == BOOTM_ERR_OVERLAP)
ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
if (images->os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
}
/* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
if (!ret && (states & BOOTM_STATE_RAMDISK)) {
ulong rd_len = images->rd_end - images->rd_start;
ret = boot_ramdisk_high(&images->lmb, images->rd_start,
rd_len, &images->initrd_start, &images->initrd_end);
if (!ret) {
setenv_hex("initrd_start", images->initrd_start);
setenv_hex("initrd_end", images->initrd_end);
}
}
#endif
#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)
if (!ret && (states & BOOTM_STATE_FDT)) {
boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
&images->ft_len);
}
#endif
/* From now on, we need the OS boot function */
if (ret)
return ret;
boot_fn = bootm_os_get_boot_func(images->os.os);
need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
if (boot_fn == NULL && need_boot_fn) {
if (iflag)
enable_interrupts();
printf("ERROR: booting os '%s' (%d) is not supported
",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}
/* Call various other states that are not generally used */
if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_BD_T))
ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
if (!ret && (states & BOOTM_STATE_OS_PREP))
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
#ifdef CONFIG_TRACE
/* Pretend to run the OS, then run a user command */
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("fakegocmd");
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif
/* Check for unsupported subcommand. */
if (ret) {
puts("subcommand not supported
");
return ret;
}
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
/* Deal with any fallout */
err:
if (iflag)
enable_interrupts();
if (ret == BOOTM_ERR_UNIMPLEMENTED)
bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
else if (ret == BOOTM_ERR_RESET)
do_reset(cmdtp, flag, argc, argv);
return ret;
}
do_bootm_states 함수는 총 8개 부분으로 나뉜다. (1) Work through the states and see how far we get.We stop on any error. 여기서 주요 함수 bootmfind_os는 get kernel image header, start address and length, get image parameters 세 가지 기능을 실현한다.대략적인 과정은:bootmfind_os -> boot_get_kernel -> image_get_kernel .
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;
bool ep_found = false;
int ret;
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
...
/* get image parameters */
switch (genimg_get_format(os_hdr)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY: /* */
images.os.type = image_get_type(os_hdr);
images.os.comp = image_get_comp(os_hdr);
images.os.os = image_get_os(os_hdr);
images.os.end = image_get_image_end(os_hdr);
images.os.load = image_get_load(os_hdr);
images.os.arch = image_get_arch(os_hdr);
break;
#endif
#if IMAGE_ENABLE_FIT
case IMAGE_FORMAT_FIT:
...
#endif
#ifdef CONFIG_ANDROID_BOOT_IMAGE
case IMAGE_FORMAT_ANDROID:
...
#endif
default:
puts("ERROR: unknown image format type!
");
return 1;
}
...
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
...
boot 정보get_kernel 、image_get_kernel의 설명: bootget_kernel - find kernel image(returns: pointer to image header if valid image was found, plus kernel start address and length, otherwise NULL)
image_get_kernel - verify legacy format kernel image(returns: pointer to a legacy image header if valid image was found otherwise return NULL) ⑵, Load the OS 37108;, Relocate the ramdisk ⑳, From now on, we need the OS boot function은
boot_fn = bootm_os_get_boot_func(images->os.os);
boot 처리 함수 포인터를 받아서 bootfn. ①、매개 변수에 대한 이미지s->os.os, 다음 정의에서 알 수 있듯이 이것은 시스템 내 핵의 유형이고 (2)에 값이 부여됩니다. 만약 시스템 유형이 linux라면images->os입니다.os=5. typedef struct bootm_headers {
...
#ifndef USE_HOSTCC /*USE_HOSTCC */
image_info_t os; /* os image info */
ulong ep; /* entry point of OS */
ulong rd_start, rd_end;/* ramdisk start/end */
char *ft_addr; /* flat dev tree address */
ulong ft_len; /* length of flat device tree */
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif
...
} bootm_headers_t;
bootm_headers_t images;
typedef struct image_info {
ulong start, end; /* start/end of blob */
ulong image_start, image_len; /* start of image within blob, len of image */
ulong load; /* load addr for the image */
uint8_t comp, type, os; /* compression, type of image, os type */
uint8_t arch; /* CPU architecture */
} image_info_t;
이미지 가져오기os.os 값:
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;
...
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
...
/* get image parameters */
switch (genimg_get_format(os_hdr)) {
#if defined(CONFIG_IMAGE_FORMAT_LEGACY)
case IMAGE_FORMAT_LEGACY: /* */
...
images.os.os = image_get_os(os_hdr);
...
break;
#endif
②、bootm_os_get_boot_func에서 함수 포인터 배열 boot 사용os, 이 수조는 전송된 이미지를 이용한다.os.os=5의 boot 처리 함수 포인터dobootm_boot 에 linux 반환fn .
boot_fn = bootm_os_get_boot_func(images->os.os);
boot_os_fn *bootm_os_get_boot_func(int os)
{
...
return boot_os[os];
}
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
...
};
운영 체제 코드는 u-boot-2016.05\include\image.h에서 보기
/*
* Operating System Codes
*/
#define IH_OS_INVALID 0 /* Invalid OS */
#define IH_OS_OPENBSD 1 /* OpenBSD */
#define IH_OS_NETBSD 2 /* NetBSD */
#define IH_OS_FREEBSD 3 /* FreeBSD */
#define IH_OS_4_4BSD 4 /* 4.4BSD */
#define IH_OS_LINUX 5 /* Linux */
...
⑸、Call various other states that are not generally used ⑹、Check for unsupported subcommand ⑺、Now run the OS! We hope this doesn’t return
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn);
Dobootm_states u-boot-2016.05\common\bootm 진입os.c:boot_selected_os 함수, 실행
boot_fn(state, argc, argv, images);
int boot_selected_os(int argc, char * const argv[], int state,
bootm_headers_t *images, boot_os_fn *boot_fn)
{
...
boot_fn(state, argc, argv, images);
...
}
⑻, Deal withany fallout 8, 실행
boot_fn(state, argc, argv, images)
, 왜냐하면bootfn=do_bootm_루스, 그래서 실행do_bootm_linux(state, argc, argv, images)
에 해당, 프로그램은 u-boot-2016.05\arch\arm\lib\bootm로 넘어갑니다.c: /* Main Entry point for arm bootm implementation*/
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
...
boot_jump_linux(images, flag);
...
}
do_bootm_linux -> boot_jump_linux -> kernel_entry(0, machid, r2);
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
...
unsigned long machid = gd->bd->bi_arch_number;
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(int, int, uint))images->ep; /* ep:entry point of OS*/
s = getenv("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!
");
return;
}
printf("Using machid 0x%lx from environment
", machid);
}
...
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
if (!fake) {
...
kernel_entry(0, machid, r2);
}
#endif
}
run the OS! 설명:
kernel_entry = (void (*)(int, int, uint))images->ep;
의 images->ep가 u-boot-2016.05\common\bootm.c:bootm_find_os 함수에 값이 부여되었습니다.static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
...
if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
...
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
일본어를 Sublime Text3로 입력하면 Tab 키를 누르면 사라집니다.Sublime Text3의 일본어 입력법으로 탭을 누르면 입력한 문자가 사라집니다. Sublime Text를 좋아해서 사용했는데 탭을 누르자 한자가 사라져 쓰기 싫어졌다.그리고 낡은 Jedit를 가동해 보며 하루하루...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.