공급업체 모듈 가이드라인

다음 가이드라인을 사용해 공급업체 모듈의 견고성과 안정성을 높이세요. 여러 가이드라인을 따르면 올바른 모듈 로드 순서와 드라이버가 기기를 프로브해야 하는 순서를 더 쉽게 결정할 수 있습니다.

모듈은 라이브러리드라이버일 수 있습니다.

  • 라이브러리 모듈은 다른 모듈에서 사용할 API를 제공하는 라이브러리입니다. 이러한 모듈은 일반적으로 하드웨어와 관련이 없습니다. 라이브러리 모듈의 예로는 AES 암호화 모듈, 모듈로 컴파일되는 remoteproc 프레임워크, logbuffer 모듈이 있습니다. module_init()의 모듈 코드는 데이터 구조를 설정하기 위해 실행되지만 외부 모듈에서 트리거하지 않는 한 다른 코드는 실행되지 않습니다.

  • 드라이버 모듈은 특정 유형의 기기를 프로브하거나 이러한 기기에 바인딩하는 드라이버입니다. 이러한 모듈은 하드웨어와 관련이 있습니다. 드라이버 모듈의 예로는 UART, PCIe, 동영상 인코더 하드웨어가 있습니다. 드라이버 모듈은 연결된 기기가 시스템에 있는 경우에만 활성화됩니다.

    • 기기가 없으면 실행되는 유일한 모듈 코드는 드라이버 코어 프레임워크에 드라이버를 등록하는 module_init() 코드입니다.

    • 기기가 있고 드라이버가 이 기기를 프로브하거나 기기에 성공적으로 바인딩하면 다른 모듈 코드가 실행될 수도 있습니다.

모듈 init/exit를 올바르게 사용

드라이버 모듈은 module_init()에서 드라이버를 등록하고 module_exit()에서 드라이버를 등록 취소해야 합니다. 이러한 제한사항을 적용하는 간단한 방법은 module_init() 또는 *_initcall(), module_exit() 매크로를 직접 사용하지 않는 래퍼 매크로를 사용하는 것입니다.

  • 로드 취소할 수 있는 모듈의 경우에는 module_subsystem_driver()를 사용합니다. 예를 들면 module_platform_driver(), module_i2c_driver(), module_pci_driver()입니다.

  • 로드 취소할 수 없는 모듈의 경우에는 builtin_subsystem_driver()를 사용합니다. 예를 들면 builtin_platform_driver(), builtin_i2c_driver(), builtin_pci_driver()입니다.

일부 드라이버 모듈은 module_init()module_exit()를 사용합니다. 드라이버를 두 개 이상 등록하기 때문입니다. module_init()module_exit()을 사용하여 여러 드라이버를 등록하는 드라이버 모듈의 경우 드라이버를 단일 드라이버로 결합해 보세요. 예를 들어 별도의 드라이버를 등록하는 대신 compatible 문자열이나 기기의 aux 데이터를 사용하여 구별할 수 있습니다. 또는 드라이버 모듈을 모듈 두 개로 분할할 수 있습니다.

init 및 exit 함수 예외

라이브러리 모듈은 드라이버를 등록하지 않으며 module_init()module_exit()에 관한 제한사항에서 제외됩니다. 데이터 구조나 작업 대기열, 커널 스레드를 설정하는 데 이러한 함수가 필요할 수 있기 때문입니다.

MODULE_DEVICE_TABLE 매크로 사용

드라이버 모듈에는 모듈을 로드하기 전에 사용자 공간에서 드라이버 모듈이 지원하는 기기를 확인하도록 하는 MODULE_DEVICE_TABLE 매크로가 포함되어 있어야 합니다. Android는 이 데이터를 사용하여 모듈 로드를 최적화할 수 있습니다(예: 시스템에 없는 기기의 모듈 로드 방지). 매크로 사용에 관한 예는 업스트림 코드를 참고하세요.

전방 선언 데이터 유형으로 인한 CRC 불일치 방지

전방 선언 데이터 유형을 확인하기 위해 헤더 파일을 포함하지 마세요. 헤더 파일(header-A.h)에 정의된 일부 구조체, 공용체, 기타 데이터 유형은 일반적으로 이러한 데이터 유형 포인터를 사용하는 다른 헤더 파일(header-B.h)에서 전방 선언될 수 있습니다. 이 코드 패턴은 커널이 의도적으로 데이터 구조를 header-B.h의 사용자 이외에는 비공개로 유지하려고 한다는 것을 의미합니다.

header-B.h의 사용자는 이러한 전방 선언 데이터 구조의 내부 요소에 직접 액세스하기 위해 header-A.h를 포함해서는 안 됩니다. 다른 커널(예: GKI 커널)이 모듈을 로드하려고 할 때 CONFIG_MODVERSIONS CRC 불일치 문제(ABI 규정 준수 문제 발생)가 발생합니다.

예를 들어 struct fwnode_handleinclude/linux/fwnode.h에서 정의되지만 include/linux/device.h에서는 struct fwnode_handle;로 전방 선언됩니다. 커널이 struct fwnode_handle의 세부정보를 include/linux/device.h의 사용자로부터 비공개로 유지하려고 하기 때문입니다. 이 시나리오에서는 struct fwnode_handle의 멤버에 액세스하기 위해 모듈에 #include <linux/fwnode.h>를 추가하지 마세요. 이러한 헤더 파일을 포함해야 하는 모든 디자인은 잘못된 디자인 패턴을 나타냅니다.

코어 커널 구조에 직접 액세스하지 않음

코어 커널 데이터 구조에 직접 액세스하거나 이를 수정하면 메모리 누수, 비정상 종료, 향후 커널 버전과의 호환성 손상 등 원치 않는 동작이 발생할 수 있습니다. 데이터 구조는 다음 조건 중 하나를 충족하는 경우 코어 커널 데이터 구조입니다.

  • 데이터 구조가 KERNEL-DIR/include/에 정의되어 있습니다. struct device, struct dev_links_info를 예로 들 수 있습니다. include/linux/soc에 정의된 데이터 구조는 제외됩니다.

  • 데이터 구조가 모듈에 의해 할당되거나 초기화되지만 커널에서 내보낸 함수의 입력으로 간접적으로(구조체의 포인터를 통해) 또는 직접적으로 전달되어 커널에 표시됩니다. 예를 들어 cpufreq 드라이버 모듈은 struct cpufreq_driver를 초기화하고 cpufreq_register_driver()에 입력으로 전달합니다. 이 시점이 지나면 cpufreq 드라이버 모듈은 struct cpufreq_driver를 직접 수정하면 안 됩니다. cpufreq_register_driver() 호출로 인해 struct cpufreq_driver가 커널에 표시되기 때문입니다.

  • 데이터 구조가 모듈에 의해 초기화되지 않습니다. regulator_register()에서 반환된 struct regulator_dev를 예로 들 수 있습니다.

커널에서 내보낸 함수나 명시적으로 공급업체 후크에 입력으로 전달된 매개변수를 통해서만 코어 커널 데이터 구조에 액세스합니다. 코어 커널 데이터 구조의 일부를 수정하는 API나 공급업체 후크가 없다면 이는 의도된 것일 수 있으므로 모듈의 데이터 구조를 수정하면 안 됩니다. 예를 들어 struct device 또는 struct device.links 내부의 필드를 수정하지 마세요.

  • device.devres_head를 수정하려면 devm_clk_get() 또는 devm_regulator_get(), devm_kzalloc()과 같은 devm_*() 함수를 사용합니다.

  • struct device.links 내부 필드를 수정하려면 device_link_add() 또는 device_link_del()과 같은 기기 링크 API를 사용합니다.

호환되는 속성이 있는 devicetree 노드를 파싱하지 않음

기기 트리(DT) 노드에 compatible 속성이 있으면 struct device가 자동으로 할당되거나 of_platform_populate()가 상위 DT 노드에서 호출될 때(일반적으로 상위 기기의 기기 드라이버에서) 할당됩니다. 기본 예상(스케줄러에 관해 일찍 초기화된 일부 기기는 제외)은 compatible 속성이 있는 DT 노드에 struct device 및 일치하는 기기 드라이버가 있다는 것입니다. 다른 모든 예외는 미리 업스트림 코드에서 처리됩니다.

또한 fw_devlink(이전의 of_devlink)는 compatible 속성이 있는 DT 노드를 드라이버에서 프로브하는 할당된 struct device가 있는 기기로 간주합니다. DT 노드에 compatible 속성이 있지만 할당된 struct device가 프로브되지 않는 경우 fw_devlink는 소비자 기기가 프로브되는 것을 차단하거나 sync_state() 호출이 공급자 기기에 관해 호출되는 것을 차단할 수 있습니다.

드라이버가 of_find_*() 함수(예: of_find_node_by_name() 또는 of_find_compatible_node())를 사용하여 compatible 속성이 있는 DT 노드를 직접 찾아서 이 DT 노드를 파싱하는 경우 기기를 프로브하거나 compatible 속성을 삭제할 수 있는 기기 드라이버를 작성하여 모듈을 수정합니다(업스트림되지 않은 경우에만 가능). 대안을 논의하려면 Android 커널팀(kernel-team@android.com)에 문의하여 사용 사례를 정당화할 수 있도록 준비하세요.

DT phandle을 사용하여 공급자 찾기

가능한 경우 DT에서 phandle(DT 노드 참조/포인터)을 사용하여 공급자를 참조하세요. 표준 DT 바인딩 및 phandle을 사용하여 공급자를 참조하면 fw_devlink(이전의 of_devlink)가 런타임에 DT를 파싱하여 기기 간 종속 항목을 자동으로 확인할 수 있습니다. 그러면 커널은 올바른 순서로 기기를 자동으로 프로브하므로 모듈 로드 순서나 MODULE_SOFTDEP()가 필요하지 않습니다.

기존 시나리오(ARM 커널에서 DT 지원되지 않음)

이전에는 DT 지원이 ARM 커널에 추가되기 전에 터치 기기와 같은 소비자가 전역적으로 고유한 문자열을 사용하여 레귤레이터와 같은 공급자를 조회했습니다. 예를 들어 ACME PMIC 드라이버는 여러 레귤레이터(예: acme-pmic-ldo1~acme-pmic-ldo10)를 등록하거나 알릴 수 있었으며 터치 드라이버는 regulator_get(dev, "acme-pmic-ldo10")을 사용하여 레귤레이터를 조회할 수 있었습니다. 하지만 다른 보드에서 LDO8은 터치 기기를 공급할 수 있으므로 동일한 터치 드라이버가 터치 기기가 사용되는 각 보드의 레귤레이터에 관한 올바른 조회 문자열을 결정해야 하는 번거로운 시스템이 만들어집니다.

현재 시나리오(ARM 커널에서 DT 지원됨)

DT 지원이 ARM 커널에 추가된 후 소비자는 phandle을 사용하여 공급자의 기기 트리 노드를 참조함으로써 DT에서 공급자를 식별할 수 있습니다. 소비자는 리소스를 제공하는 주체 대신 리소스의 용도에 따라 리소스의 이름을 지정할 수도 있습니다. 예를 들어 이전 예의 터치 드라이버는 regulator_get(dev, "core")regulator_get(dev, "sensor")을 사용하여 터치 기기의 코어와 센서에 전원을 공급하는 공급자를 가져올 수 있습니다. 이러한 기기의 연결된 DT는 다음 코드 샘플과 유사합니다.

touch-device {
    compatible = "fizz,touch";
    ...
    core-supply = <&acme_pmic_ldo4>;
    sensor-supply = <&acme_pmic_ldo10>;
};

acme-pmic {
    compatible = "acme,super-pmic";
    ...
    acme_pmic_ldo4: ldo4 {
        ...
    };
    ...
    acme_pmic_ldo10: ldo10 {
        ...
    };
};

최악의 시나리오

이전 커널에서 포팅된 일부 드라이버에는 기존 스키마의 최악의 부분을 가져와서 작업을 더 쉽게 하기 위한 최신 스키마에 이를 강제하는 DT의 기존 동작이 포함되어 있습니다. 이러한 드라이버에서 소비자 드라이버는 기기별 DT 속성을 사용하여 조회에 사용할 문자열을 읽고 공급자는 다른 공급자별 속성을 사용하여 공급자 리소스 등록에 사용할 이름을 정의합니다. 그러면 소비자와 공급자는 공급자를 조회하는 데 문자열을 사용하는 동일한 이전 스키마를 계속 사용합니다. 이 최악의 시나리오는 다음과 같습니다.

  • 터치 드라이버가 다음 코드와 유사한 코드를 사용합니다.

    str = of_property_read(np, "fizz,core-regulator");
    core_reg = regulator_get(dev, str);
    str = of_property_read(np, "fizz,sensor-regulator");
    sensor_reg = regulator_get(dev, str);
    
  • DT가 다음과 유사한 코드를 사용합니다.

    touch-device {
      compatible = "fizz,touch";
      ...
      fizz,core-regulator = "acme-pmic-ldo4";
      fizz,sensor-regulator = "acme-pmic-ldo4";
    };
    acme-pmic {
      compatible = "acme,super-pmic";
      ...
      ldo4 {
        regulator-name = "acme-pmic-ldo4"
        ...
      };
      ...
      acme_pmic_ldo10: ldo10 {
        ...
        regulator-name = "acme-pmic-ldo10"
      };
    };
    

프레임워크 API 오류를 수정하지 않음

regulator, clocks, irq, gpio, phys, extcon과 같은 프레임워크 API는 오류 반환 값으로 -EPROBE_DEFER를 반환하여 기기가 프로브를 시도하지만 지금은 할 수 없으며 커널이 나중에 프로브를 다시 시도해야 함을 나타냅니다. 이러한 경우에 기기의 .probe() 함수가 예상대로 실패하도록 하려면 오류 값을 교체하거나 재매핑하지 마세요. 오류 값을 교체하거나 재매핑하면 -EPROBE_DEFER가 삭제되어 기기가 프로브되지 않을 수 있습니다.

devm_*() API 변형 사용

기기가 devm_*() API를 사용하여 리소스를 획득하면 기기가 프로브에 실패하거나 프로브에 성공하고 나중에 바인딩 해제되는 경우 리소스는 커널에서 자동으로 해제합니다. 이 기능을 사용하면 probe() 함수의 오류 처리 코드가 더 깔끔해집니다. devm_*()에서 획득한 리소스를 해제하는 데 goto 점프가 필요하지 않고 드라이버 바인딩 해제 작업이 간소화되기 때문입니다.

기기 드라이버 바인딩 해제 처리

기기 드라이버 바인딩을 의도적으로 해제해야 하고 바인딩 해제를 정의되지 않은 것으로 두면 안 됩니다. 정의되지 않은 것이 허용되지 않은 것을 의미하지는 않기 때문입니다. 기기 드라이버 바인딩 해제를 완전히 구현해야 합니다. 또는 기기 드라이버 바인딩 해제를 명시적으로 사용 중지해야 합니다.

기기 드라이버 바인딩 해제 구현

기기 드라이버 바인딩 해제를 완전히 구현하려고 할 때 메모리 또는 리소스 누수 및 보안 문제를 방지하려면 기기 드라이버를 깔끔하게 바인딩 해제합니다. 드라이버의 probe() 함수를 호출하여 기기를 드라이버에 바인딩하고 드라이버의 remove() 함수를 호출하여 기기를 바인딩 해제할 수 있습니다. remove() 함수가 없어도 커널은 계속 기기를 바인딩 해제할 수 있습니다. 드라이버 코어는 기기에서 바인딩 해제될 때 드라이버에 정리 작업이 필요하지 않다고 가정합니다. 기기에서 바인딩 해제되는 드라이버는 다음 두 사항이 모두 참일 때 명시적 정리 작업을 실행할 필요가 없습니다.

  • 드라이버의 probe() 함수는 devm_*() API를 통해 모든 리소스를 획득합니다.

  • 하드웨어 기기는 종료 또는 정지 시퀀스가 필요하지 않습니다.

이 상황에서 드라이버 코어는 devm_*() API를 통해 획득한 모든 리소스를 해제합니다. 앞의 두 사항 중 하나가 참이 아닌 경우 드라이버는 기기에서 바인딩 해제될 때 정리 작업(리소스 해제 및 하드웨어 종료나 정지)을 실행해야 합니다. 기기가 드라이버 모듈을 깔끔하게 바인딩 해제할 수 있도록 하려면 다음 옵션 중 하나를 사용하세요.

  • 하드웨어에 종료 또는 정지 시퀀스가 필요하지 않으면 기기 devm_*() API를 통해 리소스를 획득하도록 기기 모듈을 변경합니다.

  • probe() 함수와 동일한 구조체에서 remove() 드라이버 작업을 구현하고 remove() 함수를 사용하여 정리 단계를 실행합니다.

명시적으로 기기 드라이버 바인딩 해제 사용 중지(권장하지 않음)

기기 드라이버 바인딩 해제를 명시적으로 사용 중지하려는 경우에는 바인딩 해제 모듈 로드 취소를 허용하지 않아야 합니다.

  • 바인딩 해제를 허용하지 않으려면 드라이버의 struct device_driver에서 suppress_bind_attrs 플래그를 true로 설정합니다. 이 설정으로 bindunbind 파일이 드라이버의 sysfs 디렉터리에 표시되지 않습니다. unbind 파일은 사용자 공간이 기기에서 드라이버의 바인딩 해제를 트리거하도록 허용하는 파일입니다.

  • 모듈 로드 취소를 허용하지 않으려면 모듈의 lsmod[permanent]가 있는지 확인합니다. module_exit() 또는 module_XXX_driver()를 사용하지 않으면 모듈은 [permanent]로 표시됩니다.

probe 함수 내에서 펌웨어를 로드하지 않음

드라이버는 .probe() 함수 내에서 펌웨어를 로드하지 않아야 합니다. 플래시 또는 영구 저장소 기반 파일 시스템이 마운트되기 전에 드라이버가 프로브하면 펌웨어에 액세스하지 못할 수 있기 때문입니다. 이러한 경우 request_firmware*() API가 오랫동안 차단되었다가 실패할 수 있으며 이로 인해 부팅 프로세스가 불필요하게 느려질 수 있습니다. 대신 클라이언트가 기기 사용을 시작할 때까지 펌웨어 로드를 지연하세요. 예를 들어 디스플레이 드라이버는 디스플레이 기기가 열릴 때 펌웨어를 로드할 수 있습니다.

.probe()를 사용하여 펌웨어를 로드하는 것이 괜찮은 경우도 있습니다(예: 펌웨어가 작동해야 하지만 기기는 사용자 공간에 노출되지 않는 클록 드라이버에서). 다른 적절한 사용 사례도 있을 수 있습니다.

비동기 프로빙 구현

향후 출시에서 Android에 추가될 수 있는 향후 개선사항(예: 부팅 시간 단축을 위한 병렬 모듈 로드 또는 기기 프로빙)을 활용하려면 비동기 프로빙을 지원하고 사용하세요. 비동기 프로빙을 사용하지 않는 드라이버 모듈은 이러한 최적화의 효율성을 낮출 수 있습니다.

비동기 프로빙을 지원하고 선호하는 것으로 드라이버를 표시하려면 드라이버의 struct device_driver 멤버에서 probe_type 필드를 설정하세요. 다음 예는 플랫폼 드라이버에 사용 설정된 이러한 지원을 보여줍니다.

static struct platform_driver acme_driver = {
        .probe          = acme_probe,
        ...
        .driver         = {
                .name   = "acme",
                ...
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};

드라이버가 비동기 프로빙과 호환되도록 하는 데는 특별한 코드가 필요하지 않습니다. 하지만 비동기 프로빙 지원을 추가할 때는 다음 사항에 유의하세요.

  • 이전에 프로브된 종속 항목에 관해 가정하지 마세요. 대부분의 프레임워크 호출을 직접 또는 간접적으로 확인하고 하나 이상의 공급자가 아직 준비되지 않았다면 -EPROBE_DEFER를 반환합니다.

  • 상위 기기의 probe 함수에 하위 기기를 추가하는 경우 하위 기기가 즉시 프로브된다고 가정하지 마세요.

  • 프로브에 실패하면 적절한 오류 처리 및 정리 작업을 실행합니다(devm_*() API 변형 사용 참고).

MODULE_SOFTDEP를 사용하여 기기 프로브의 순서를 지정하지 않음

MODULE_SOFTDEP() 함수는 기기 프로브의 순서를 보장하는 신뢰할 수 있는 솔루션이 아니며 다음과 같은 이유로 사용하면 안 됩니다.

  • 지연된 프로브. 모듈이 로드될 때 기기 프로브는 공급자 중 하나가 준비되지 않았기 때문에 지연될 수 있습니다. 이로 인해 모듈 로드 순서와 기기 프로브 순서가 일치하지 않을 수 있습니다.

  • 한 가지 드라이버, 여러 기기. 드라이버 모듈은 특정 기기 유형을 관리할 수 있습니다. 시스템에 기기 유형 인스턴스가 두 개 이상 포함되어 있고 이러한 기기에는 각각 다른 프로브 순서 요구사항이 있다면 모듈 로드 순서를 사용하여 이러한 요구사항을 준수할 수 없습니다.

  • 비동기 프로빙. 비동기 프로빙을 실행하는 드라이버 모듈은 모듈이 로드될 때 즉시 기기를 프로브하지 않습니다. 대신 병렬 스레드가 기기 프로빙을 처리하므로 모듈 로드 순서와 기기 프로브 순서 간에 불일치가 발생할 수 있습니다. 예를 들어 I2C 기본 드라이버 모듈이 비동기 프로빙을 실행하고 터치 드라이버 모듈이 I2C 버스에 있는 PMIC에 종속되는 경우 터치 드라이버와 PMIC 드라이버가 올바른 순서로 로드되더라도 터치 드라이버의 프로브는 PMIC 드라이버 프로브보다 먼저 시도될 수 있습니다.

MODULE_SOFTDEP() 함수를 사용하는 드라이버 모듈이 있는 경우 이 함수를 사용하지 않도록 모듈을 수정합니다. 도움이 되도록 Android팀에서는 커널이 MODULE_SOFTDEP()를 사용하지 않고 순서 문제를 처리할 수 있도록 하는 변경사항을 업스트림했습니다. 특히 fw_devlink를 사용하여 프로브 순서를 보장하고 기기의 모든 소비자가 프로브한 후 sync_state() 콜백을 사용하여 필요한 작업을 실행할 수 있습니다.

구성에 #ifdef 대신 #if IS_ENABLED() 사용

#ifdef CONFIG_XXX 대신 #if IS_ENABLED(CONFIG_XXX)를 사용하여 이후에 구성이 tristate 구성으로 변경되는 경우 #if 블록 내의 코드가 계속 컴파일되도록 합니다. 차이는 다음과 같습니다.

  • #if IS_ENABLED(CONFIG_XXX)CONFIG_XXX가 모듈(=m) 또는 내장(=y)으로 설정되는 경우 true로 평가됩니다.

  • #ifdef CONFIG_XXXCONFIG_XXX가 내장(=y)으로 설정되는 경우 true로 평가되지만 CONFIG_XXX가 모듈(=m)로 설정되는 경우에는 true로 평가되지 않습니다. 구성을 모듈로 설정하거나 사용 중지할 때 확실히 동일한 작업을 하려는 경우에만 이를 사용하세요.

조건부 컴파일에 올바른 매크로 사용

CONFIG_XXX가 모듈(=m)로 설정되면 빌드 시스템이 CONFIG_XXX_MODULE을 자동으로 정의합니다. 드라이버가 CONFIG_XXX에 의해 제어되고 드라이버가 모듈로 컴파일되는지 확인하려면 다음 가이드라인을 따르세요.

  • 드라이버의 C 파일(또는 헤더 파일이 아닌 모든 소스 파일)에서 #ifdef CONFIG_XXX_MODULE을 사용하지 마세요. 불필요하게 제한적이고 구성의 이름이 CONFIG_XYZ로 바뀌면 중단되기 때문입니다. 모듈에 컴파일되는 헤더 이외의 소스 파일의 경우 빌드 시스템은 해당 파일 범위의 MODULE을 자동으로 정의합니다. 따라서 C 파일(또는 헤더 이외의 소스 파일)이 모듈의 일부로 컴파일되는지 확인하려면 CONFIG_ 접두어 없이 #ifdef MODULE을 사용합니다.

  • 헤더 파일에서는 동일한 검사가 더 까다롭습니다. 헤더 파일이 바이너리에 직접 컴파일되지 않고 C 파일(또는 다른 소스 파일)의 일부로 컴파일되기 때문입니다. 헤더 파일에는 다음 규칙을 사용하세요.

    • #ifdef MODULE을 사용하는 헤더 파일의 경우 결과는 이를 사용하는 소스 파일에 따라 변경됩니다. 즉, 같은 빌드의 동일한 헤더 파일은 다양한 소스 파일에 관해 컴파일된 코드의 여러 부분을 보유할 수 있습니다(모듈 대 내장 또는 사용 중지). 이는 내장 코드의 경우 한 방식을 확장하고 모듈의 경우 다른 방식으로 확장해야 하는 매크로를 정의하려는 경우에 유용할 수 있습니다.

    • 특정 CONFIG_XXX가 모듈로 설정될 때 코드 조각으로 컴파일해야 하는 헤더 파일의 경우(이를 포함하는 소스 파일이 모듈인지 여부와 관계없이) #ifdef CONFIG_XXX_MODULE을 사용해야 합니다.