selinux_enforcing_boot is configured within enforcing_setup().
 
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
static int selinux_enforcing_boot;
 
static int __init enforcing_setup(char *str)
{
unsigned long enforcing;
if (!kstrtoul(str, 0, &enforcing))
selinux_enforcing_boot = enforcing ? 1 : 0;
return 1;
}
__setup("enforcing=", enforcing_setup);
#else
#define selinux_enforcing_boot 1
#endif
 
If you would like to know how selinux_enforcing_boot is updated, please refer to below commit;
 
Now, let's find selinux_enabled is set as 1 or 0.
 
#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
int selinux_enabled = CONFIG_SECURITY_SELINUX_BOOTPARAM_VALUE;
 
static int __init selinux_enabled_setup(char *str)
{
unsigned long enabled;
if (!kstrtoul(str, 0, &enabled))
selinux_enabled = enabled ? 1 : 0;
return 1;
}
__setup("selinux=", selinux_enabled_setup);
#else
int selinux_enabled = 1;
#endif
 
If the boot argument includes "selinux=", selinux_enabled is set to 1 inside selinux_enabled_setup().
 
But above code snippset is based on 4.19 kernel version. 
 
With v5.12 version, selinux_enabled is changed as selinux_enabled_boot.
 
int selinux_enabled_boot __initdata = 1;
#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
static int __init selinux_enabled_setup(char *str)
{
unsigned long enabled;
if (!kstrtoul(str, 0, &enabled))
selinux_enabled_boot = enabled ? 1 : 0;
return 1;
}
__setup("selinux=", selinux_enabled_setup);
#endif
 
Above change seems to come from below commit;
 
 
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 40ec866e48daa..659c4a81e8976 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -109,7 +109,7 @@ struct selinux_state selinux_state;
 static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0);
 
 #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
-static int selinux_enforcing_boot;
+static int selinux_enforcing_boot __initdata;
 
 static int __init enforcing_setup(char *str)
 {
@@ -123,13 +123,13 @@ __setup("enforcing=", enforcing_setup);
 #define selinux_enforcing_boot 1
 #endif
 
-int selinux_enabled __lsm_ro_after_init = 1;
+int selinux_enabled_boot __initdata = 1;
 #ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
 static int __init selinux_enabled_setup(char *str)
 {
  unsigned long enabled;
  if (!kstrtoul(str, 0, &enabled))
- selinux_enabled = enabled ? 1 : 0;
+ selinux_enabled_boot = enabled ? 1 : 0;
  return 1;
 }
 __setup("selinux=", selinux_enabled_setup);
@@ -7202,7 +7202,7 @@ void selinux_complete_init(void)
 DEFINE_LSM(selinux) = {
  .name = "selinux",
  .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
- .enabled = &selinux_enabled,
+ .enabled = &selinux_enabled_boot,
  .blobs = &selinux_blob_sizes,
  .init = selinux_init,
 };
@@ -7271,7 +7271,7 @@ static int __init selinux_nf_ip_init(void)
 {
  int err;
 
- if (!selinux_enabled)
+ if (!selinux_enabled_boot)
  return 0;
 
  pr_debug("SELinux:  Registering netfilter hooks\n");
@@ -7318,8 +7318,6 @@ int selinux_disable(struct selinux_state *state)
 
  pr_info("SELinux:  Disabled at runtime.\n");
 
- selinux_enabled = 0;
-
  security_delete_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks));
 
  /* Try to destroy the avc node cache */
diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c
index de92365e4324b..f68a7617cfb95 100644
--- a/security/selinux/ibpkey.c
+++ b/security/selinux/ibpkey.c
@@ -222,7 +222,7 @@ static __init int sel_ib_pkey_init(void)
 {
  int iter;
 
- if (!selinux_enabled)
+ if (!selinux_enabled_boot)
  return 0;
 
  for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) {
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 8c0dbbd076c6f..af623f03922ce 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -69,7 +69,7 @@
 
 struct netlbl_lsm_secattr;
 
-extern int selinux_enabled;
+extern int selinux_enabled_boot;
 
 /* Policy capabilities */
 enum {
@@ -99,7 +99,9 @@ struct selinux_avc;
 struct selinux_ss;
 
 struct selinux_state {
+#ifdef CONFIG_SECURITY_SELINUX_DISABLE
  bool disabled;
+#endif
 #ifdef CONFIG_SECURITY_SELINUX_DEVELOP
  bool enforcing;
 #endif
diff --git a/security/selinux/netif.c b/security/selinux/netif.c
index e40fecd737524..15b8c1bcd7d0c 100644
--- a/security/selinux/netif.c
+++ b/security/selinux/netif.c
@@ -266,7 +266,7 @@ static __init int sel_netif_init(void)
 {
  int i;
 
- if (!selinux_enabled)
+ if (!selinux_enabled_boot)
  return 0;
 
  for (i = 0; i < SEL_NETIF_HASH_SIZE; i++)
diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c
index 9ab84efa46c7e..dff587d1e1641 100644
--- a/security/selinux/netnode.c
+++ b/security/selinux/netnode.c
@@ -291,7 +291,7 @@ static __init int sel_netnode_init(void)
 {
  int iter;
 
- if (!selinux_enabled)
+ if (!selinux_enabled_boot)
  return 0;
 
  for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) {
diff --git a/security/selinux/netport.c b/security/selinux/netport.c
index 3f8b2c0458c88..de727f7489b76 100644
--- a/security/selinux/netport.c
+++ b/security/selinux/netport.c
@@ -225,7 +225,7 @@ static __init int sel_netport_init(void)
 {
  int iter;
 
- if (!selinux_enabled)
+ if (!selinux_enabled_boot)
  return 0;
 
  for (iter = 0; iter < SEL_NETPORT_HASH_SIZE; iter++) {
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index dd7bb1f1dc99a..278417e67b4c6 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -168,11 +168,10 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
  goto out;
  audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
  "enforcing=%d old_enforcing=%d auid=%u ses=%u"
- " enabled=%d old-enabled=%d lsm=selinux res=1",
+ " enabled=1 old-enabled=1 lsm=selinux res=1",
  new_value, old_value,
  from_kuid(&init_user_ns, audit_get_loginuid(current)),
- audit_get_sessionid(current),
- selinux_enabled, selinux_enabled);
+ audit_get_sessionid(current));
  enforcing_set(state, new_value);
  if (new_value)
  avc_ss_reset(state->avc, 0);
@@ -304,10 +303,10 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf,
  goto out;
  audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
  "enforcing=%d old_enforcing=%d auid=%u ses=%u"
- " enabled=%d old-enabled=%d lsm=selinux res=1",
+ " enabled=0 old-enabled=1 lsm=selinux res=1",
  enforcing, enforcing,
  from_kuid(&init_user_ns, audit_get_loginuid(current)),
- audit_get_sessionid(current), 0, 1);
+ audit_get_sessionid(current));
  }
 
  length = count;
@@ -2105,7 +2104,7 @@ static int __init init_sel_fs(void)
    sizeof(NULL_FILE_NAME)-1);
  int err;
 
- if (!selinux_enabled)
+ if (!selinux_enabled_boot)
  return 0;
 
  err = sysfs_create_mount_point(fs_kobj, "selinux");
 
This patch contains very informative code, which make me know where selinux_enabled is configured.
Asan은 Address sanitizer의 약자입니다. Address Sanitizer는 컴파일러를 수정해  프로그램이 동작하는 도중에 일어나는 잘못된 메모리 (주소) 액세스를 검출하기 위한 도구입니다. 
 
Git Hub
 
Asan에 대한 상세한 내용은 아래 깃허브에서 확인할 수 있습니다.
 
 
Asan의 장점과 단점
 
사실 Asan과 비슷한 용도로 사용됐던 Valgrind라는 툴이 있습니다. 하지만 Valgrind는 치명적인 단점이 있습니다. 프로그램의 실행 속도가 매우 느려진다는 점인데, Valgrind이 모든 명령어를 에뮬레이션해 동작하기 때문입니다. 
 
그렇다면 Asan은 Valgrind보다 속도가 빠를까요? 네, 빠릅니다. 
 
Asan은 컴파일러의 도움을 받아, 이런 목적을 위한 코드를 해당 프로그램에 직접 삽입해 실행하기 때문입니다. 대신 프로그램을 재빌드해서 실행해야 하는 불편함이 있긴 합니다.
 
Asan의 핵심 동작
 
그렇다면, Asan의 핵심 동작은 무엇일까요?
 
정상적으로 접근 가능한 메모리 영역과 그렇지 않은 영역을 구분해, 올바르지 않은 영역을 접근하면(예: 메모리 관련 오류 발생, Fault) 이를 곧바로 탐지해서 알려주는 것입니다. 이를 위해 Asan은 컴파일러의 도움을 받게 됩니다. 정리하면, Asan은 메모리 주소에 엑세스할 때, 검사하는 작업을 수행하는 것입니다. 
 
엑세스하려는 주소가 올바른지 아닌지를 Asan은 어떻게 판단할까요?
 
shadow memory라는 추가적인 메모리를 사용해 validity를 검사합니다. 대부분 메모리 관련 오류는 주어진 영역을 벗어나서 다른 영역을 침범하는 경우(Out-of-bound)에 발생합니다. 그래서 메모리 영역 (변수) 사이 마다, red-zone이라는 영역을 추가해, 연속된 메모리 영역에 잘못된 접근해, 메모리 다른 영역이 침범당하는 경우를 찾아낼 수 있습니다. 즉, red-zone이 오염됐는지를 체크하는 것입니다.
 
Asan으로 잡아낼 수 있는 메모리 오류
 
Asan을 사용하면 모든 경우의 메모리 오류를 잡을 수 있을까요? 
그렇지는 않습니다. Asan을 사용해 검출할 수 있는 메모리 오류는 다음과 같습니다.
 
   * global/stack/heap 영역의 버퍼 오버플로우
   * heap use-after-free
   * stack use-after-return/scope
 
heap 영역은 libasan.so 라고 부르는 라이브러리를 통해 관리됩니다. 또한 global 영역과 stack 영역에 대해서는, 컴파일 시에 추가적인 작업이 필요한데, 이를 제어하기 위해서 '-asan-global' 및 '-asan-stack' 이라는 옵션을 제공합니다. 또한 실행 시에 제어가 가능한 기능들은 ASAN_OPTIONS라는 환경 변수를 통해 동적을 지정할 수 있습니다.
 
리눅스 디바이스 드라이버를 플렛폼 디바이스 규칙에 맞게 작성하면 몇 가지 편리한 점이 있습니다.
그 중 하나가 셸 커맨드로 probe/release 함수를 호출할 수 있다는 점입니다.
 
모듈 타입 디바이스 드라이버를 검증할 때 활용하면 좋을 꿀팁이니, 많은 개발자 분들이 활용했으면 좋겠습니다.
 
셸 명령어로 디렉터리 확인하기
 
먼저 /sys/bus/platform/drivers/gpio-keys 디렉터리로 이동합시다.
 
pi:/sys/bus/platform/drivers # cd gpio-keys/
pi:/sys/bus/platform/drivers/gpio-keys # ls
bind  soc:gpio_keys  uevent  unbind
 
확인하니 bind와 unbind 파일이 보입니다.
 
pi:/sys/bus/platform/drivers/gpio-keys # ls -l
total 0
--w------- 1 root root 4096 2018-06-11 03:48 bind
lrwxrwxrwx 1 root root    0 2018-06-11 03:48 soc:gpio_keys -> ../../../../devices/soc/soc:gpio_keys
--w------- 1 root root 4096 2018-06-11 03:48 uevent
--w------- 1 root root 4096 2018-06-11 03:48 unbind
 
아래와 같은 셸 커맨드를 입력합시다.
 
pi:/sys/bus/platform/drivers/gpio-keys # echo "soc:gpio_keys" > bind
pi:/sys/bus/platform/drivers/gpio-keys # echo "soc:gpio_keys" > unbind
 
관련 코드 분석
 
여기서 'echo "soc:gpio_keys" > bind' 혹은 'echo "soc:gpio_keys" > unbind' 명령어를 입력하면 어떤 함수가 호출될까요? 각각 gpio_keys_probe()/gpio_keys_remove() 함수가 호출됩니다.
 
다음은 위에서 언급된 함수가 선언된 코드입니다.
 
static struct platform_driver gpio_keys_device_driver = {
.probe = gpio_keys_probe,
.remove = gpio_keys_remove,
.driver = {
.name = "gpio-keys",
.owner = THIS_MODULE,
.pm = &gpio_keys_pm_ops,
.of_match_table = of_match_ptr(gpio_keys_of_match),
}
};
 
커널 크래시 문제 확인
 
그런데 다음과 같은 명령어를 입력했더니,
 
pi:/sys/bus/platform/drivers/gpio-keys # echo "soc:gpio_keys" > bind
pi:/sys/bus/platform/drivers/gpio-keys # echo "soc:gpio_keys" > unbind
pi:/sys/bus/platform/drivers/gpio-keys # echo "soc:gpio_keys" > bind
 
데이터 어보트가 발생하네요.
 
000|do_DataAbort(?)
-001|__dabt_svc(asm)
 -->|exception
-002|sysfs_remove_file_ns(kobj = 0xFFFFFFF7, attr = 0xC19BEA30, ns = 0x0)
-003|switch_dev_unregister(sdev = 0xC19A1964)
-004|gpio_keys_setup_key(inline)
-004|gpio_keys_probe(?)
-005|platform_drv_probe(_dev = 0xEB490A50)
-006|really_probe(inline)
-006|driver_probe_device(drv = 0xC19A191C, dev = 0xEB490A50)
-007|bind_store(drv = 0xC19A191C, ?, count = 14)
-008|drv_attr_store(?, ?, ?, ?)
-009|sysfs_kf_write(?, buf = 0xC19BEA30, ?, pos = 0)
-010|kernfs_fop_write(?, ?, count = 14, ppos = 0xE1E75F80)
-011|vfs_write(file = 0x0, buf = 0x0, ?, pos = 0xE1E75F80)
-012|SYSC_write(inline)
-012|sys_write(?, buf = -1471503740, count = 14)
-013|ret_fast_syscall(asm)
 
빨리 문제를 해결해야 겠습니다.
소형 임베디드 장비를 제외하고는 대부분 시스템은 멀티 CPU 코어 환경에서 개발됩니다. 
멀티 프로세스(Multiprocess) 기반으로 작성된 데몬을 실행하면 여러 CPU 코어에 적당히 나뉘어 실행되는 것을 쉽게 볼 수 있습니다. CPU0에서 실행됐다가 CPU2에서 실행되는 것이죠.
 
그런데 가끔은 특정 작업을 수행하는 프로세스가 특정 CPU 코어에서 실행되도록 설정해야 할 때가 있습니다. 여러가지 이유가 있지만 요약하면 다음과 같습니다.
 
    * 실행하는 프로세스와 관련된 인터럽트가 특정 CPU 코어에서만 트리거됨(IRQ Affinity라고 하죠.)
    * 프로세스가 여러 CPU 코어에 옮겨 다니면서 실행할 때 요구되는 오버헤드(마이그레이션)을 최소화하자
 
참고로 커널이 프로세스를 어떤 CPU 코어에 할당하는지는, 커널 내부의 자체적인 스케쥴링 메카니즘에 따라 결정됩니다. 
 
간단한 예제 코드로 실습하기
 
이어서 다음 코드를 보면서 설명을 이어나겠습니다.
 
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main(void) 
{
unsigned int i = 0;
pid_t pid;
 
if ((pid = fork()) == 0 ) {
for ( i = 0; i < UINT_MAX; i++) {
printf("I am running\n");
}
}
else {
int status;
waitpid(pid, &status, 0);
}
 
return EXIT_SUCCESS;
}
 
위 소스는 1개의 자식 프로세스를 생성하고 더하기 연산을 반복적으로 수행하여 CPU를 100% 사용하게 만드는 예제입니다. 
 
위 코드를 작성한 후 raspbian_test.c 이란 이름으로 저장합니다. 이어서 다음 구문을 입력해 Makefile을 생성합니다.
 
#Makefile
raspbian_proc: raspbian_test.c
gcc -o raspbian_proc raspbian_test.c
 
Makefile을 만든 다음에 make를 입력하면 raspbian_test.c을 손쉽게 컴파일할 수 있습니다.
아래는 make 명령어를 입력한 후의 터미널 출력 결과입니다.
 
root@raspberrypi:/home/pi/work/test_affinity# make
gcc -o raspbian_proc raspbian_test.c
 
이어서 ./raspbian_proc 명령어를 입력해 작성한 코드를 실행합니다.
 
root@raspberrypi:/home/pi/work/test_affinity# ./raspbian_proc 
I am running
I am running
I am running
 
예제 코드 실행 결과 분석하기
 
터미널을 하나 더 열어 top을 실행시킨 후 '1'번 키를 눌러 CPU 별로 사용량을 지켜볼 수 있게 준비를 합니다. 
 
root@raspberrypi:/home/pi# top
top - 10:27:42 up 51 min,  5 users,  load average: 2.20, 0.72, 0.26
Tasks: 146 total,   6 running, 140 sleeping,   0 stopped,   0 zombie
%Cpu0  : 12.9 us, 49.4 sy,  0.0 ni, 33.2 id,  0.0 wa,  0.0 hi,  4.4 si,  0.0 st
%Cpu1  : 16.4 us, 55.5 sy,  0.0 ni, 26.7 id,  0.0 wa,  0.0 hi,  1.4 si,  0.0 st
%Cpu2  :  7.1 us, 59.1 sy,  0.0 ni, 33.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  4.5 us, 36.0 sy,  0.0 ni, 57.4 id,  0.0 wa,  0.0 hi,  2.1 si,  0.0 st
MiB Mem :   1939.5 total,   1494.6 free,     93.7 used,    351.1 buff/cache
MiB Swap:    100.0 total,    100.0 free,      0.0 used.   1662.1 avail Mem
 
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  132 root      20   0   45984  17828  16852 S   99.3   0.9   0:33.90 raspbian_proc
 
그리고, 소스를 컴파일 해서 여러번 실행시켜 보면 사용량이 100%에 달하는 CPU가 고정적이지 않고 변하는 것을 확인 할 수 있습니다. 
 
이번에는 ftrace 메시지를 통해 위 코드가 실행될 때 CPU 코어의 번호를 확인해 보겠습니다. 아래 ftrace 메시지를 볼까요? 
 
raspbian_proc-1106  [002] d...  4086.493934: sched_switch: prev_comm=raspbian_proc prev_pid=1 106 prev_prio=120 prev_state=R+ ==> next_comm=kworker/u8:2 next_pid=1019 next_prio=120
...
raspbian_proc-1106  [001] d...  4086.583273: sched_switch: prev_comm=raspbian_proc prev_pid=1 106 prev_prio=120 prev_state=R+ ==> next_comm=kworker/u8:0 next_pid=1084 next_prio=120
 
4086.493934 초에는 2번째 CPU 코어에서 실행하다가, 4086.583273초에는 1번째 CPU 코어에서 해당 코드가 실행 중이란 사실을 알 수 있습니다. 
 
sched_setaffinity() 함수를 사용해 CPU 코어를 지정하는 예시 코드
 
이번에는 해당 프로세스를 특정 CPU 코어에서 실행하는 예제 코드를 소개합니다.
 
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <getopt.h>
#include <sched.h>
 
void print_help(char *cmd) {
printf("Usage: %s -n <cpu 개수> -c < 선호CPU>\n\n", cmd);
printf(" CPU 개수 : CPU 코어 개수\n");
printf(" 선호 CPU : CPU 코어 번호 (0 부터 시작)\n\n");
printf(" 예 : 쿼드코어 CPU에서 3번째 코어를 사용하는 경우\n");
printf(" $ %s -n 4 -c 2\n", cmd);
}
 
int main(int argc, char *argv[]) {
unsigned int i = 0;
pid_t pid;
int max_cpu = -1;
int cpu = -1;
int opt;
 
while ( (opt = getopt(argc, argv, "n:c:")) != -1 ) {
switch ( opt ) {
case 'c' :
cpu = atoi(optarg);
printf("cpu: %d \n", cpu);
break;
 
case 'n' :
max_cpu = atoi(optarg);
printf("max_cpu: %d \n", max_cpu);
break;
 
case '?' :
default :
print_help(argv[0]);
exit(EXIT_FAILURE);
break;
}
}
 
printf("@: %d \n", __LINE__);
 
if ( max_cpu < 1 || cpu < 0 || cpu >= max_cpu ) {
print_help(argv[0]);
printf("@: %d \n", __LINE__);
exit(EXIT_FAILURE);
}
 
if ( (pid = fork()) == 0 ) {
cpu_set_t mask;
printf("@: %d \n", __LINE__);
 
CPU_ZERO(&mask);
CPU_SET(cpu, &mask);
 
pid = getpid();
 
if ( sched_setaffinity(pid, sizeof(mask), &mask) ) {
fprintf(stderr, "%d 번 CPU를 선호하도록 설정하지 못했습니다.\n", cpu);
exit(EXIT_FAILURE);
}
else {
printf("%d 번 CPU를 선호하도록 설정했습니다.\n", cpu);
}
 
for ( i = 0; i < UINT_MAX; i++) {
}
}  else {
int status;
waitpid(pid, &status, 0);
}
 
return EXIT_SUCCESS;
}
 
위 소스 코드는 sched.h에서 제공하는 sched_setaffinity() 함수를 사용하여 특정한 CPU에서 프로세스가 실행되도록 한 것입니다.
 
sched_setaffinity() 함수는 3개의 매개변수를 받는데 첫번째는 프로세스 ID(pid)입니다. pid 대신 0을 넘기면 자동으로 현재 동작중인 프로세스로 설정됩니다. 두 번째는 cpusetsize 입니다. 보통은 sizeof(cpu_set_t)로 설정하면 됩니다. 세번째는 mask 포인터입니다. mask 포인터는 아래의 매크로 함수들을 사용해서 편리하게 설정 할 수 있습니다.
 
위 코드를 컴파일한 다음에 다음 명령어를 입력해 실행해 볼까요?
 
root@raspberrypi:/home/pi/work/test_affinity# ./raspbian_proc -n 4 -c 0
max_cpu: 4
cpu: 0
@: 49
@: 59
0 번 CPU를 선호하도록 설정했습니다.
 
전체 CPU 코어의 갯수는 4, 지정하려는 CPU 번호는 0으로 설정해 실행했습니다.
 
로그 분석
 
아래는 위 코드를 실행한 후 확인한 ftrace 메시지입니다.
 
raspbian_proc-1177  [000] d...  4556.349390: sched_switch: prev_comm=raspbian_proc prev_pid=1177 prev_prio=120 prev_state=R ==> next_comm=irq/36-mmc1 next_pid=83 next_prio=49
raspbian_proc-1177  [000] d...  4556.454209: sched_switch: prev_comm=raspbian_proc prev_pid=1177 prev_prio=120 prev_state=R ==> next_comm=kworker/0:0 next_pid=1142 next_prio=120
kworker/0:0-1142  [000] d...  4556.454225: sched_switch: prev_comm=kworker/0:0 prev_pid=1142 prev_prio=120 prev_state=D ==> next_comm=raspbian_proc next_pid=1177 next_prio=120
 
보다시피, 0번째 CPU에서 위 코드가 실행했다는 사실을 알 수 있습니다.
 
cpu는 CPU의 번호로 0번부터 시작합니다. 쿼드코어 CPU를 탑재한 라즈베리 파이에서는 0~3번 사이의 값이 됩니다. 또한 mask 값을 여러개의 CPU로 지정하는 것도 가능합니다.
 
참고로 아래는 CPU 코어를 설정하는데 필요한 함수 목록입니다.
 
 void CPU_CLR(int cpu, cpu_set_t *set);
 int CPU_ISSET(int cpu, cpu_set_t *set);
 void CPU_SET(int cpu, cpu_set_t *set);
 void CPU_ZERO(cpu_set_t *set);
 
정리
 
멀티프로세스나 멀티쓰레드를 여러개의 CPU나 코어에 적절히 배치하여 효과적으로 사용하는 것은 매우 어려운 기술입니다. sched_setaffinity 함수를 통해 수동으로 배치했다고 해서 그것이 반드시 커널의 스케줄러에 의해 선택되는 동작보다 효율적이라는 보장은 없습니다.
 
다만 몇가지 특징적인 프로세스들을 적절히 배치하여 CPU 자원을 어느 정도 보장 해주는데 도움이 될 수 있습니다.
 
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자
 
 
 
 
 
다음 포스트에서는 sched_setaffinity() 함수를 유저 프로세스에서 실행하면 커널에서 어떤 방식으로 처리되는지 소개합니다.
defconfig 파일에서 아래 컨피그를 추가하면, 
 
CONFIG_IRQSOFF_TRACER=y
CONFIG_SCHED_TRACER=y
CONFIG_STACK_TRACER=y
 
.config에서 아래 내용을 확인할 수 있다.
 
CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_IRQSOFF_TRACER=y
# CONFIG_PREEMPT_TRACER is not set
CONFIG_SCHED_TRACER=y
LK(Little Kernel)에서 전처리 파일을 추출하기 위해서 아래 패치 코드를 반영하면 됩니다.
 
diff --git a/engine.mk b/engine.mk
index 74f289c..51ff0f1 100644
--- a/engine.mk
+++ b/engine.mk
@@ -204,7 +204,7 @@ endif
 
 # default to no ccache
 CCACHE ?=
-CC := $(CCACHE) $(TOOLCHAIN_PREFIX)${TOOLCHAIN}
+CC := $(CCACHE) $(TOOLCHAIN_PREFIX)${TOOLCHAIN} --save-temps
 LD := $(TOOLCHAIN_PREFIX)ld
 OBJDUMP := $(TOOLCHAIN_PREFIX)objdump
 OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy
 
리눅스 시스템을 개발할 때 가장 많이 활용하는 로그는 무엇일까요?
 
    * 커널 로그죠.
 
그런데 가끔 실전 개발에서 커널 로그 레벨을 조정해야 할 때가 있어요.
예를 들면 개발 버전이 아니라 릴리즈 버전을 배포할 때 변경하죠.
 
이번 시간에는 커널 로그 레벨을 3으로 내리는 방법에 대해 소개합니다.
 
코드 수정
 
커널 로그 레벨을 3으로 내리려면 다음과 같이 코드를 수정해야 합니다.
 
1. 커널 코드(컨피그)
 
다음과 같은 커널 컨피그를 추가해야 합니다.
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=3
 
2. 디바이스 트리 혹은 board make file
 
다음 커널 커맨드 라인이 추가 되도록 부트로더 코드를 수정해야 합니다.
rcupdate.rcu_expedited=2 rcu_nocbs=0-7 loglevel=3
 
커널 이미지를 로딩하는 부트로더는 LK(Little Kernel)와 uboot이 있는데요.
각 부트 로더에서 제공하는 함수를 사용해 커맨드 라인이 추가되도록 코드를 수정해야 합니다.
 
코드 수정 후 확인
 
이번에는 커널 로그 레벨이 제대로 설정됐는지 확인하기 위한 패치 코드를 소개합니다.
 
패치 코드는 아래와 같습니다.
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 766be12..54412a8 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -113,6 +113,17 @@ enum devkmsg_log_masks {
 
 static unsigned int __read_mostly devkmsg_log = DEVKMSG_LOG_MASK_DEFAULT;
 
+
+void trace_console_printk(char *type)
+{
+       pr_emerg("[%s] ============emerg= %d %d %d %d===================== \n",
+                                               type, console_printk[0], console_printk[1], console_printk[2], console_printk[3]);
+
+}
+
 
diff --git a/kernel/printk/printk_safe.c b/kernel/printk/printk_safe.c
index 0913b4d..f77947d 100644
--- a/kernel/printk/printk_safe.c
+++ b/kernel/printk/printk_safe.c
@@ -15,6 +15,7 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <linux/kernel.h>
 #include <linux/preempt.h>
 #include <linux/spinlock.h>
 #include <linux/debug_locks.h>
@@ -398,6 +399,22 @@ __printf(1, 0) int vprintk_func(const char *fmt, va_list args)
        return vprintk_default(fmt, args);
 }
 
+#define CONSOLE_TRACE_PERIOD           msecs_to_jiffies(HZ*20)
+#define INIT_CONSOLE_TRACE_PERIOD      msecs_to_jiffies(HZ*20)
+#include <linux/workqueue.h>
+
+static struct delayed_work console_trace_delayed_work;
+
+extern void trace_console_printk(char *type);
+
+static void console_trace_delayed_work_func(struct work_struct *work)
+{
+       trace_console_printk("delayed_work");
+
+       queue_delayed_work(system_highpri_wq, &console_trace_delayed_work,
+                                                       CONSOLE_TRACE_PERIOD);
+}
+
 void __init printk_safe_init(void)
 {
        int cpu;
@@ -414,6 +431,10 @@ void __init printk_safe_init(void)
 #endif
        }
 
+       INIT_DELAYED_WORK(&console_trace_delayed_work, console_trace_delayed_work_func);
+
+       queue_delayed_work(system_highpri_wq, &console_trace_delayed_work,
+                                               INIT_CONSOLE_TRACE_PERIOD);
 
딜레이 워크를 통해 2초 간격의 주기로 시간 정보를 출력하는 패치 코드입니다.
 
위 패치 코드에서 눈여겨 볼 점이 있는데, 커널 로그를 출력할 때 printk보단 pr_emerg이 좋다는 점입니다.
그 이유는 다음과 같습니다.;
 
    * 커널 로그 레벨이 낮아도 pr_emerg 로그를 반드시 커널이 출력해준다.
 
커널 로그 확인
 
패치 코드를 반영한 후 커널 로그로 console_printk 출력 결과를 확인하면 다음과 같습니다.
 
[    8.080092 / 01-01 00:00:08.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
[   10.080068 / 01-01 00:00:10.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
[   12.080069 / 01-01 00:00:12.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
[   14.080064 / 01-01 00:00:14.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
[   16.080067 / 01-01 00:00:16.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
[   18.080093 / 01-01 00:00:18.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
[   20.080084 / 01-01 00:00:20.079][0] [delayed_work] =========emerg= 3 3 1 7=====================
리눅스 커널에서는 CPU Frequency를 트레이싱할 수 있는 ftrace event를 제공합니다.
그 정체는 cpu_frequency 이벤트입니다. 먼저 ftrace 이벤트를 정의하는 코드를 보겠습니다.
 
다음은 cpu_frequency 이벤트의 선언부입니다.
 
01 DEFINE_EVENT(cpu, cpu_frequency,
02
03 TP_PROTO(unsigned int frequency, unsigned int cpu_id),
04
05 TP_ARGS(frequency, cpu_id)
06 );
 
01번째 줄과 같이 cpu_frequency 이벤트는 cpu란 이벤트 클래스를 오버로딩한다는 사실을 알 수 있습니다. 
 
cpu 이벤트 클래스의 선언부는 다음과 같습니다.
 
DECLARE_EVENT_CLASS(cpu,
 
TP_PROTO(unsigned int state, unsigned int cpu_id),
 
TP_ARGS(state, cpu_id),
 
TP_STRUCT__entry(
__field( u32, state )
__field( u32, cpu_id )
),
 
TP_fast_assign(
__entry->state = state;
__entry->cpu_id = cpu_id;
),
 
TP_printk("state=%lu cpu_id=%lu", (unsigned long)__entry->state,
  (unsigned long)__entry->cpu_id)
);
 
ftrace의 이벤트 클래스는 ftrace 이벤트의 공통 skeleton 형식으로 구성돼 있습니다. 
'state=%lu'는 주파수, 'cpu_id=%lu'는 CPU 코어의 번호를 출력합니다.
 
이번에는 'cpu_frequency' 이벤트를 출력하는 커널 코드를 분석하겠습니다.
 
다음은 cpufreq_notify_transition() 함수의 선언부입니다.
 
1 static void cpufreq_notify_transition(struct cpufreq_policy *policy,
2                       struct cpufreq_freqs *freqs,
3                       unsigned int state)
4 {
5     BUG_ON(irqs_disabled());
7     if (cpufreq_disabled())
8         return;
10    freqs->flags = cpufreq_driver->flags;
11    pr_debug("notification %u of frequency transition to %u kHz\n",
12         state, freqs->new);
13
14    switch (state) {
15 ...
16    case CPUFREQ_POSTCHANGE:
17        adjust_jiffies(CPUFREQ_POSTCHANGE, freqs);
18        pr_debug("FREQ: %u - CPUs: %*pbl\n", freqs->new,
19             cpumask_pr_args(policy->cpus));
20
21        for_each_cpu(freqs->cpu, policy->cpus) {
22            trace_cpu_frequency(freqs->new, freqs->cpu);
23            srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
24                         CPUFREQ_POSTCHANGE, freqs);
25        }
26
27        cpufreq_stats_record_transition(policy, freqs->new);
28        policy->cur = freqs->new;
29    }
30 }
 
위 코드에서 cpu_frequency이란 ftrace 이벤트는 22번째 줄이 실행될 때 출력합니다.
 
22            trace_cpu_frequency(freqs->new, freqs->cpu);
 
이어서 23~24번째 줄을 주의깊게 눈여겨 볼 필요가 있습니다.
 
23            srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
24                         CPUFREQ_POSTCHANGE, freqs);
 
cpufreq_transition_notifier_list 이란 notifier call을 수행합니다. 
notifier call은 어떤 함수가 동작이 완료된 후 호출되도록 등록하는 인터페이스인데요.
 
cpufreq_transition_notifier_list 이란 notifier call에 등록하기 위해서는 cpufreq_register_notifier() 함수를 호출해야 합니다.
 
여기서 한 가지 의문이 생깁니다. 
 
    * 'cpufreq_transition_notifier_list' notifier-call에 등록된 notifier의 정체는 무엇인가?
 
이 의문을 해소하려면 cpufreq_transition_notifier_list 변수를 직접 확인하면 됩니다.
 
다음은 cpufreq_transition_notifier_list입니다.
 
  (static struct srcu_notifier_head) cpufreq_transition_notifier_list = (
    (struct mutex) mutex = ((atomic_t) count = ((int) counter = 1 = 0x1 = '....'),  
    (struct srcu_struct) srcu = ((long unsigned int) completed = 1 = 0x1 = '....',  
    (struct notifier_block *) head = 0xC17129C4 = cpufreq_notifier -> (
      (notifier_fn_t) notifier_call = 0xC0110CF4 = cpufreq_callback -> ,
      (struct notifier_block *) next = 0xC17FAE28 = notifier_trans_block -> (
        (notifier_fn_t) notifier_call = 0xC0BDA378 = cpufreq_stat_notifier_trans -> ,
        (struct notifier_block *) next = 0xC1768DA0 = ppm_cpu_freq_notifier -> (
          (notifier_fn_t) notifier_call = 0xC059CD20 = ppm_cpu_freq_callback -> ,
          (struct notifier_block *) next = 0xC1F2B110 = freq_transition -> (
            (notifier_fn_t) notifier_call = 0xC05C0F90 = cpufreq_transition_handler -> ,
            (struct notifier_block *) next = 0x0 =  -> NULL,
 
위에서 본 디버깅 정보를 정리하면 다음과 같습니다. 
notifier | notifier-call 콜백 함수
cpufreq_notifier:  cpufreq_callback()
notifier_trans_block: cpufreq_stat_notifier_trans()
ppm_cpu_freq_notifier: ppm_cpu_freq_callback()
freq_transition: cpufreq_transition_handler()
 
아까 봤던 cpufreq_notify_transition() 함수로 되돌아 가서 분석을 해보면...
 
1 static void cpufreq_notify_transition(struct cpufreq_policy *policy,
2                       struct cpufreq_freqs *freqs,
3                       unsigned int state)
4 {
...
14    switch (state) {
15 ...
16    case CPUFREQ_POSTCHANGE:
17        adjust_jiffies(CPUFREQ_POSTCHANGE, freqs);
18        pr_debug("FREQ: %u - CPUs: %*pbl\n", freqs->new,
19             cpumask_pr_args(policy->cpus));
20
21        for_each_cpu(freqs->cpu, policy->cpus) {
22            trace_cpu_frequency(freqs->new, freqs->cpu);
23            srcu_notifier_call_chain(&cpufreq_transition_notifier_list,
24                         CPUFREQ_POSTCHANGE, freqs);
25        }
 
23번째 줄이 실행되면 다음 목록의 함수들이 차례로 호출되는 것입니다.
 
    * cpufreq_callback() 
    * cpufreq_stat_notifier_trans() 
    * ppm_cpu_freq_callback()
    * cpufreq_transition_handler() 
 
(where)
notifier | notifier-call 콜백 함수
cpufreq_notifier:  cpufreq_callback()
notifier_trans_block: cpufreq_stat_notifier_trans()
ppm_cpu_freq_notifier: ppm_cpu_freq_callback()
freq_transition: cpufreq_transition_handler()
 
 
English version
 
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다. 



 
리눅스 커널에서 CPU Frequency는 성능을 측정할 때 중요한 척도 중 하나입니다.
이번 시간에는 리눅스 커널에서 CPU Frequency와 관련된 자료 구조를 소개합니다.
 
cpufreq_cpu_data 선언부
 
cpufreq_cpu_data는 커널에서 CPU 주파수를 저장하는 중요한 변수입니다.
 
먼저 cpufreq_cpu_data 변수의 선언부를 봅시다.
 
static DEFINE_PER_CPU(struct cpufreq_policy *, cpufreq_cpu_data);
 
보시다시피 DEFINE_PER_CPU 키워드와 함께 cpufreq_policy 구조체로 선언된 변수임을 알 수 있습니다.
즉, CPU 코어 별로 cpufreq_policy 구조체의 정보를 cpufreq_cpu_data 변수가 저장하는 것입니다.
 
cpufreq_cpu_data 디버깅해보기
 
이어서 percpu 타입인 cpufreq_cpu_data 변수를 확인해보겠습니다.
다음은 크래시 유틸리티에서 cpufreq_cpu_data 변수를 확인한 결과입니다.
 
crash> p cpufreq_cpu_data
PER-CPU DATA TYPE:
  struct cpufreq_policy *cpufreq_cpu_data;
PER-CPU ADDRESSES:
  [0]: e6b1f838
  [1]: e6b32838
  [2]: e6b45838
  [3]: e6b58838
  [4]: e6b6b838
  [5]: e6b7e838
  [6]: e6b91838
  [7]: e6ba4838
 
친절하게 struct cpufreq_policy 구조체가 8개가 있다는 정보를 표현합니다. 코어의 갯수가 8개이니 인덱스의 범위가 0~7입니다.
 
이번에는 TRACE32로 cpufreq_cpu_data 변수를 확인해봅시다.
먼저 cpufreq_cpu_data 변수의 주소를 점검하겠습니다.
 
$ v.v %t %h &cpufreq_cpu_data
(struct cpufreq_policy * *) &cpufreq_cpu_data = 0xC16F0838
 
보시다시피 cpufreq_cpu_data 변수는 0xC16F0838 주소에 위치합니다.
cpufreq_cpu_data 변수가 percpu 타입이니 __per_cpu_offset[] 배열을 활용할 필요가 있습니다.
 
다음과 같은 명령어를 입력합시다.
 
$ v.v %tree.on %t %h %s %d %y (struct cpufreq_policy **)(0xC16F0838+__per_cpu_offset[0])
01  (struct cpufreq_policy * *) (struct cpufreq_policy **)(0xC16F0838+__per_cpu_offset[0]) = 0xE6B1F838 = _
02    (cpumask_var_t) cpus = (((long unsigned int [1]) bits = (15))),
03    (cpumask_var_t) related_cpus = (((long unsigned int [1]) bits = (15))),
04    (cpumask_var_t) real_cpus = (((long unsigned int [1]) bits = (15))),
05    (unsigned int) shared_type = 3,
06    (unsigned int) cpu = 0,
07    (struct clk *) clk = 0x0 = ,
08    (struct cpufreq_cpuinfo) cpuinfo = (
09      (unsigned int) max_freq = 2001000,
10      (unsigned int) min_freq = 900000,
11      (unsigned int) transition_latency = 1000),
12    (unsigned int) min = 2001000,
13    (unsigned int) max = 2001000,
14    (unsigned int) cur = 2001000,
15    (unsigned int) restore_freq = 2001000,
16    (unsigned int) suspend_freq = 0,
17    (unsigned int) policy = 0,
18    (unsigned int) last_policy = 0,
19    (struct cpufreq_governor *) governor = 0xC171D988 = cpufreq_gov_sched -> (
20      (char [16]) name = "schedplus",
21      (int (*)()) init = 0xC019F810 = cpufreq_sched_policy_init,
22      (void (*)()) exit = 0xC019F5FC = cpufreq_sched_policy_exit,
23      (int (*)()) start = 0xC019F540 = cpufreq_sched_start,
24      (void (*)()) stop = 0xC019F5A0 = cpufreq_sched_stop,
25      (void (*)()) limits = 0x0 = ,
26      (ssize_t (*)()) show_setspeed = 0x0 = ,
27      (int (*)()) store_setspeed = 0x0 = ,
 
이제부터 출력 결과를 확인하겠습니다. 
먼저 08~10번째 줄을 보겠습니다.
 
08    (struct cpufreq_cpuinfo) cpuinfo = (
09      (unsigned int) max_freq = 2001000,
10      (unsigned int) min_freq = 900000,
 
09번째 줄은 최대 frequency, 10번째 줄은 최소 frequency를 나타냅니다.
 
이번에는 14번째 줄을 확인합시다.
 
14    (unsigned int) cur = 2001000,
 
현재 실행 중인 CPU 코어의 주파수를 나타냅니다.
 
이어서 19~24번째 줄을 보겠습니다.
 
19    (struct cpufreq_governor *) governor = 0xC171D988 = cpufreq_gov_sched -> (
20      (char [16]) name = "schedplus",
21      (int (*)()) init = 0xC019F810 = cpufreq_sched_policy_init,
22      (void (*)()) exit = 0xC019F5FC = cpufreq_sched_policy_exit,
23      (int (*)()) start = 0xC019F540 = cpufreq_sched_start,
24      (void (*)()) stop = 0xC019F5A0 = cpufreq_sched_stop,
 
해당 CPU 코어에서 적용된 거버너 정보를 나타냅니다.
"schedplus" CPU 거버너인데 21~24번째 줄에서 함수 포인터의 정보를 확인할 수 있습니다.
 
CPU frequency 테이블 확인해보기
 
커널에서는 로드에 비례해 CPU 코어의 주파수를 설정할 수 있는 CPU frequency 테이블이 있습니다.
CPU frequency 테이블의 주소는 percpu 타입인 cpufreq_cpu_data->freq_table 에 저장돼 있습니다.
 
다음은 0번째 CPU의 cpufreq_cpu_data 변수의 정보입니다.
 
$ v.v %tree.on %t %h %s %d %y (struct cpufreq_policy*)(0xC16F0838+__per_cpu_offset[0])
01  (struct cpufreq_policy * *) (struct cpufreq_policy **)(0xC16F0838+__per_cpu_offset[0]) = 0xE6B1F838 = _
02    (cpumask_var_t) cpus = (((long unsigned int [1]) bits = (15))),
...
03    (struct cpufreq_frequency_table *) freq_table = 0xE4DA2500  
04      (unsigned int) flags = 0 = 0x0,
05      (unsigned int) driver_data = 0 = 0x0,
06     (unsigned int) frequency = 2001000 = 0x001E8868),
 
보사시피 03번째에서 freq_table 필드는 0xE4DA2500 주소를 저장합니다.
 
다음 포멧으로 명령어를 입력하면 CPU 코어의 Frequency 테이블을 확인할 수 있습니다.
 
$ v.v %i %t %d ((struct cpufreq_frequency_table *)0xE4DA2500)[0..15]
  (static xtract) ((struct cpufreq_frequency_table *)0xE4DA2500)[0..15] = (
    [0] = ((unsigned int) flags = 0, (unsigned int) driver_data = 0, (unsigned int) frequency = 2001000),
    [1] = ((unsigned int) flags = 0, (unsigned int) driver_data = 1, (unsigned int) frequency = 1961000),
    [2] = ((unsigned int) flags = 0, (unsigned int) driver_data = 2, (unsigned int) frequency = 1927000),
    [3] = ((unsigned int) flags = 0, (unsigned int) driver_data = 3, (unsigned int) frequency = 1897000),
    [4] = ((unsigned int) flags = 0, (unsigned int) driver_data = 4, (unsigned int) frequency = 1868000),
    [5] = ((unsigned int) flags = 0, (unsigned int) driver_data = 5, (unsigned int) frequency = 1838000),
    [6] = ((unsigned int) flags = 0, (unsigned int) driver_data = 6, (unsigned int) frequency = 1809000),
    [7] = ((unsigned int) flags = 0, (unsigned int) driver_data = 7, (unsigned int) frequency = 1779000),
    [8] = ((unsigned int) flags = 0, (unsigned int) driver_data = 8, (unsigned int) frequency = 1750000),
    [9] = ((unsigned int) flags = 0, (unsigned int) driver_data = 9, (unsigned int) frequency = 1617000),
    [10] = ((unsigned int) flags = 0, (unsigned int) driver_data = 10, (unsigned int) frequency = 1484000),
    [11] = ((unsigned int) flags = 0, (unsigned int) driver_data = 11, (unsigned int) frequency = 1351000),
    [12] = ((unsigned int) flags = 0, (unsigned int) driver_data = 12, (unsigned int) frequency = 1218000),
    [13] = ((unsigned int) flags = 0, (unsigned int) driver_data = 13, (unsigned int) frequency = 1085000),
    [14] = ((unsigned int) flags = 0, (unsigned int) driver_data = 14, (unsigned int) frequency = 979000),
    [15] = ((unsigned int) flags = 0, (unsigned int) driver_data = 15, (unsigned int) frequency = 900000))
 
보다시피 frequency 필드의 범위는 2001000~900000 kHz입니다.
 
 
cpufreq_cpu_data 변수에 액세스하는 함수 분석하기
 
이제까지 CPU 코어의 Frequency를 확인하는 방법을 cpufreq_cpu_data 변수로 확인했습니다.
cpufreq_cpu_data 변수는 어느 함수를 통해 액세스를 할까요?
 
정답은 다음 함수입니다.
 
    * cpufreq_cpu_get()
    * cpufreq_cpu_get_raw()
 
먼저 cpufreq_cpu_get() 함수를 보겠습니다. 
 
01 struct cpufreq_policy *cpufreq_cpu_get(unsigned int cpu)
02 { 
03 struct cpufreq_policy *policy = NULL;
04 unsigned long flags;
05
06 if (WARN_ON(cpu >= nr_cpu_ids))
07 return NULL;
08
09 /* get the cpufreq driver */
10 read_lock_irqsave(&cpufreq_driver_lock, flags);
11
12 if (cpufreq_driver) {
13 /* get the CPU */
14 policy = cpufreq_cpu_get_raw(cpu);
15 if (policy)
16 kobject_get(&policy->kobj);
17 }
18
19 read_unlock_irqrestore(&cpufreq_driver_lock, flags);
20
21 return policy;
22}
23 EXPORT_SYMBOL_GPL(cpufreq_cpu_get);
 
14번째 줄을 보면 cpufreq_cpu_get_raw() 함수를 호출하는데  cpufreq_cpu_get_raw() 함수는 struct cpufreq_policy 구조체 타입의 주소를 반환합니다.
 
이어서 cpufreq_cpu_get_raw() 함수를 보겠습니다.
 
1 struct cpufreq_policy *cpufreq_cpu_get_raw(unsigned int cpu)
2 {
3 struct cpufreq_policy *policy = per_cpu(cpufreq_cpu_data, cpu);
4
5 return policy && cpumask_test_cpu(cpu, policy->cpus) ? policy : NULL;
6 }
 
3번째 줄과 같이 percpu 타입인 cpufreq_cpu_data 변수에 접근해 포인터 형인 policy 변수가 가르키는 주소를 반환합니다. 
 
다음 시간에는 CPU 코어의 주파수를 측정하는 ftrace 이벤트와 트레이스 포인트를 설명합니다.

+ Recent posts