티스토리 뷰
앞의 글에서 설명한 CLI 구현 방식에는 조금 불편한 내용이 있다.
새로운 명령을 추가하거나 삭제하기 위해서는 cli_command[] 배열을 수정해야 된다는 것이다. 이것이 크게 문제 되지 않기는 하겠지만 때로는 상당히 번거로운 일이 되기도 한다.
이번 글에서는 좀 더 쉬운 방법을 통해서 CLI 처리 부분에 대한 소스 코드 수정없이 새로운 명령을 추가 하는 방법에 대해서 설명 하도록 하겠다.
이 방법은 링커 프로그램을 이용하는 방법이다.
따라서 이 기능을 위해서 링커 스크립트 파일을 수정하도록 하겠다.
1 2 3 4 5 6 7 8 9 10 | .text : { KEEP(*(.vectors.table)) KEEP(*(.vectors.code)) *(.text .text.*) __cli_start = .; KEEP(*(SORT(.cli.*))) __cli_end = .; *(.rodata .rodata.*) } > rom |
6~8번 줄에 .cli.*에 해당되는 섹션으로 선언된 것들을 .text 섹션에 포함되도록 한다.
그리고 command.h 파일에 다음과 같은 내용을 추가한다.
1 2 3 4 5 6 7 8 9 10 11 | struct cli_cmd { char *cmd_name; char *cmd_help; void (*cli_func)( int , char **); }; typedef struct cli_cmd cli_cmd_t; #define CLI_CMD(name, help, cmd) \ cli_cmd_t __cli_cmd_##name __attribute__ ((section ( ".cli." #name))) = \ {#name, help, cmd} |
9~11번 줄은 CLI_CMD라는 매크로를 만들어 주는데 주의해서 보아야 할 내용은 CLI_CMD 매크로를 사용하면 .cli. 섹션으로 선언된다는 것이다.
예를 들어,
1 2 3 4 5 | static void do_help( int argc, char **argv) { } CLI_CMD(help, "Display CLI command usage" , do_help); |
위와 같이 만들어 프로젝트를 빌드하고 난 후, map 파일을 열어보면 다음과 같은 결과를 볼 수 있다.
1 2 3 4 5 | 0x08002744 __cli_start = . *(SORT(.cli.*)) .cli.help 0x08002744 0xc cli.o 0x08002744 __cli_cmd_help 0x08002750 __cli_end = . |
0x08002744 번지에 __cli_cmd_help라는 이름의 배열이 하나 생성되어 있고 .cli.help라는 섹션안에 포함되어 있는 것을 볼 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | extern cli_cmd_t __cli_start, __cli_end; void run_cli( void ) { cli_cmd_t *cli_cmd = &__cli_start; int num_command = &__cli_end - &__cli_start; int argc; char **argv; int i; printf ( "[%s]$ " , BD_PROMPT); parse_command(wait_command(), &argc, ( void *)&argv); if (!argc) return ; for (i=0; i<num_command; i++, cli_cmd++) { if ( strncmp (cli_cmd->cmd_name, argv[0], strlen (argv[0])) == 0) { cli_cmd->cli_func(argc, argv); break ; } } if (num_command <= i) { printf ( "You entered wrong command: \"%s\"\n" , argv[0]); do_help(0, 0); } } |
1번 줄은 링커에서 생성한 심볼을 C 코드에서 참조하기 위해 선언된 코드이다.
6번 줄은 .cli 섹션안에 몇개의 테이블이 있는지 계산하는 코드이다.
프로그램을 빌드한 후 보드에서 실행하면 다음과 같은 결과를 볼 수 있다.
현재까지 등록된 명령어는 help 하나 밖에 없기 때문에 help 명령만 보여 진다.
여기까지만 코드를 만들어 놓으면 더이상 CLI를 처리하기 위한 코드는 수정할 필요가 없다. 새로운 명령을 추가하기 위해서는 CLI_CMD 매크로만 이용하면 된다.
toys.c 파일과 media.c 파일에 각각 다음과 같은 코드를 추가한 후 결과가 어떻게 나오는지 확인해 본다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | toys.c static void do_toys( int argc, char **argv) { puts ( "Toys & Hobbies\n" ); } CLI_CMD(toys, "Toys & Hobbies" , do_toys); media.c static void do_medias( int argc, char **argv) { puts ( "DVDs & Movies\n" ); } CLI_CMD(medias, "DVDs & Movies" , do_medias); |
단지 CLI_CMD 매크로만 이용해서 새로운 명령어가 추가 되었고, 각 명령어를 실행하였을때 정상적으로 명령이 실행되는것을 볼 수 있다.
map 파일을 열어 보면 새로운 명령어가 추가 되었음을 볼 수 있다.
1 2 3 4 5 6 7 8 9 | 0x0800275c __cli_start = . *(SORT(.cli.*)) .cli.help 0x0800275c 0xc cli.o 0x0800275c __cli_cmd_help .cli.medias 0x08002768 0xc media.o 0x08002768 __cli_cmd_medias .cli.toys 0x08002774 0xc toys.o 0x08002774 __cli_cmd_toys 0x08002780 __cli_end = . |
이제 마지막으로 books 명령어를 추가해 보도록 하겠다. books.c 파일에 다음과 같은 코드를 추가한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | struct cli_cmd book_cmd[] = { { "art" , "Arts & Photography" , do_art}, { "comic" , "Comics & Graphic Novels" , do_comics}, { "history" , "History" , do_history}, { "sf" , "Science Fiction" , do_sf}, }; static int books_items = array_num(book_cmd); static void do_art( int argc, char **argv) { puts ( "Arts & Photography\n" ); } static void do_comics( int argc, char **argv) { puts ( "Comics & Graphic Novels\n" ); } static void do_history( int argc, char **argv) { puts ( "History\n" ); } static void do_sf( int argc, char **argv) { puts ( "Science Fiction\n" ); } static void book_help( void ) { int i; printf ( "You can select one of below item\n" ); for (i=0; i<books_items; i++) printf ( "%-10s : %s\n" , book_cmd[i].cmd_name, book_cmd[i].cmd_help); } static void do_books( int argc, char **argv) { int i; if (argc == 1) { book_help(); return ; } for (i=0; i<books_items; i++) { if ( strncmp (book_cmd[i].cmd_name, argv[1], strlen (argv[1])) == 0) { book_cmd[i].cli_func(argc, argv); break ; } } } CLI_CMD(books, "Books" , do_books); |
앞의 글에서 사용하였던 코드이다.
프로젝트를 다시 빌드하여 실행해 본다.
의도했던대로 명령어가 제대로 실행되는 것을 확인할 수 있다.
'심화' 카테고리의 다른 글
User Interface - Command Line Interface(CLI) (0) | 2017.07.09 |
---|---|
User Interface - 메뉴 방식 (0) | 2017.07.08 |
User Interface - argc, argv (0) | 2017.07.07 |
User Interface - UART 문자열 입력 (0) | 2017.06.29 |
User Interface - UART 특수키 처리 (0) | 2017.06.24 |