본문 바로가기

study

[nvme-cli] command 추가 방식

nvme-builtin.h 구조와 command 추가 방식

https://github.com/linux-nvme/nvme-cli/blob/master/nvme-builtin.h

 

COMMAND_LIST(
    ENTRY("list", "List all NVMe devices and namespaces on machine", list)
    ENTRY("list-subsys", "List nvme subsystems", list_subsys)
    // ... (중략)
);

 

  • COMMAND_LIST와 ENTRY 매크로를 사용해서 명령어(command) 정의
  • 각 ENTRY는 명령어 이름, 설명, 그리고 실제로 실행될 함수명을 인자로 받음
  • 이 구조는 새로운 명령어를 추가할 때 ENTRY를 하나 더 추가하면 되도록 설계되어 있음

cmd.h

https://github.com/linux-nvme/nvme-cli/blob/master/cmd.h
  • PLUGIN, COMMAND_LIST 등 매크로의 존재만 알리는 역할
#ifndef _CMD_H
#define _CMD_H

#undef PLUGIN
#define PLUGIN(n, c)

#undef COMMAND_LIST
#define COMMAND_LIST(args...)

#endif


 

  • PLUGIN, COMMAND_LIST 등의 매크로를 빈 정의로 선언
    • 즉, 이 파일을 include하면 PLUGIN, COMMAND_LIST가 아무 동작도 하지 않게 됨
    • 여러 번 include될 때, 매번 다른 방식으로 매크로를 정의해서 다양한 처리를 할 수 있도록 하기 위함

 

cmd_handler.h의 역할과 확장성 구현

https://github.com/linux-nvme/nvme-cli/blob/master/cmd_handler.h
  • 여러 단계(Stage)에서 PLUGIN, ENTRY, COMMAND_LIST 등의 매크로를 다르게 정의

1. 함수 프로토타입 정의

 

#undef NAME
#define NAME(n, d)

#undef ENTRY
#define ENTRY(n, h, f, ...) \
static int f(int argc, char **argv, struct command *command, struct plugin *plugin);

#undef COMMAND_LIST
#define COMMAND_LIST(args...) args

#undef PLUGIN
#define PLUGIN(name, cmds) cmds

#include CMD_INCLUDE(CMD_INC_FILE)
  • ENTRY 매크로를 함수 프로토타입 생성용으로 정의
    • 이렇게 하면 명령어별로 함수 프로토타입이 자동으로 생성됨
    • 예시: 
      ENTRY("list", "List all NVMe devices", list)
      → static int list(int argc, char **argv, struct command *command, struct plugin *plugin);

2. 명령어 구조체 정의

 

#undef NAME
#define NAME(n, d)

#undef ENTRY_W_ALIAS
#define ENTRY_W_ALIAS(n, h, f, a)	\
static struct command f ## _cmd = {	\
	.name = n, 			\
	.help = h, 			\
	.fn = f, 			\
	.alias = a, 			\
};

#undef ENTRY_WO_ALIAS
#define ENTRY_WO_ALIAS(n, h, f)		\
	ENTRY_W_ALIAS(n, h, f, NULL)

#undef ENTRY_SEL
#define ENTRY_SEL(n, h, f, a, CMD, ...) CMD

#undef ENTRY
#define ENTRY(...) 		\
	ENTRY_SEL(__VA_ARGS__, ENTRY_W_ALIAS, ENTRY_WO_ALIAS)(__VA_ARGS__)

#undef COMMAND_LIST
#define COMMAND_LIST(args...) args

#undef PLUGIN
#define PLUGIN(name, cmds) cmds

#include CMD_INCLUDE(CMD_INC_FILE)
  • ENTRY 매크로를 구조체 생성용으로 재정의
    • 각 명령어에 대해 struct command 구조체가 만들어짐
    • 예: static struct command list_cmd = { ... };
  • ENTRY_SEL 에서는 ENTRY_W_ALIAS 와 ENTRY_WO_ALIAS 중 하나의 매크로 함수를 반환
    • 여기서 (__VA_ARGS__) 는 매크로 함수에 또 인자를 전달하기 위해 작성됨
    • 만약 alias가 존재하는 경우 인자가 총 6개가 전달되고 ENTRY_W_ALIAS가 CMD가 됨
    • 반대로 alias가 존재하지 않는 경우 인자가 총 5개가 전달되고 ENTRY_WO_ALIAS가 CMD가 됨

 

3. 플러그인에 대한 Command List 생성

 

#undef NAME
#define NAME(n, d)

#undef ENTRY
#define ENTRY(n, h, f, ...) &f ## _cmd,

#undef COMMAND_LIST
#define COMMAND_LIST(args...)	\
static struct command *commands[] = {	\
	args				\
	NULL,				\
};

#undef PLUGIN
#define PLUGIN(name, cmds) cmds

#include CMD_INCLUDE(CMD_INC_FILE)
  • 각 명령어 구조체의 포인터를 배열로 만듦
  • 이 배열이 실제로 명령어 목록이 됨

 

4. 플러그인 정의 및 등록

 

#undef NAME
#define NAME(n, d) .name = n, .desc = d,

#undef COMMAND_LIST
#define COMMAND_LIST(args...)

#undef PLUGIN
#define PLUGIN(name, cmds)				\
static struct plugin plugin = {				\
	name						\
	.commands = commands				\
}; 							\
							\
static void init(void) __attribute__((constructor)); 	\
static void init(void)					\
{							\
	register_extension(&plugin);			\
}

#include CMD_INCLUDE(CMD_INC_FILE)
 
  • PLUGIN 매크로를 통해 plugin 구조체를 만들고, 명령어 배열을 연결
  • 그리고 register_extension 함수를 통해 플러그인을 등록

 

define_cmd.h

https://github.com/linux-nvme/nvme-cli/blob/master/define_cmd.h
#ifdef CREATE_CMD
#undef CREATE_CMD


#define __stringify_1(x...) #x
#define __stringify(x...)  __stringify_1(x)
#define __CMD_INCLUDE(cmd) __stringify(cmd.h)
#define CMD_INCLUDE(cmd) __CMD_INCLUDE(cmd)

#define CMD_HEADER_MULTI_READ

#include CMD_INCLUDE(CMD_INC_FILE)

#include "cmd_handler.h"

#undef CMD_HEADER_MULTI_READ

#define CREATE_CMD
#endif
  • CMD_INC_FILE에 정의된 이름(예: nvme-builtin)을 이용해 해당 헤더 파일을 include
    • 예: #define CMD_INC_FILE nvme-builtin → #include "nvme-builtin.h"가 됨

 

C 전처리기 동작 방식

  • C 전처리기 (컴파일 전 단계)에서는 매크로가 어떻게 정의되어 있느냐에 따라 include된 파일의 코드가 실제로 어떻게 변환될 지 결정함
  • 즉, 매크로 정의 → 헤더 include → 코드 생성

 

예시

  #undef CMD_INC_FILE
  #define CMD_INC_FILE my-nvme

  #if !defined(MY_NVME) || defined(CMD_HEADER_MULTI_READ)
  #define MY_NVME

  #include "cmd.h"

  PLUGIN(NAME("my", "My vendor extension"),
      COMMAND_LIST(
          ENTRY("hello", "Say hello", my_hello)
          ENTRY("bye", "Say bye", my_bye)
      )
  );

  #endif

  #include "define_cmd.h"
// Stage 1: 함수 프로토타입
static int my_hello(int argc, char **argv, struct command *command, struct plugin *plugin);
static int my_bye(int argc, char **argv, struct command *command, struct plugin *plugin);

// Stage 2: command 구조체
static struct command my_hello_cmd = {
    .name = "hello",
    .help = "Say hello",
    .fn = my_hello,
    .alias = NULL,
};
static struct command my_bye_cmd = {
    .name = "bye",
    .help = "Say bye",
    .fn = my_bye,
    .alias = NULL,
};

// Stage 3: command 포인터 배열
static struct command *commands[] = {
    &my_hello_cmd,
    &my_bye_cmd,
    NULL,
};

// Stage 4: plugin 구조체 및 등록
static struct plugin plugin = {
    .name = "my",
    .desc = "My vendor extension",
    .commands = commands
};

static void init(void) __attribute__((constructor));
static void init(void)
{
    register_extension(&plugin);
}