<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>skylarvv 님의 블로그</title>
    <link>https://skylarvv.tistory.com/</link>
    <description>skylarvv 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 8 Jul 2026 00:33:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>skylarvv</managingEditor>
    <item>
      <title>freeRTOS Resource Management(2)-Mutexes(and Binary Semaphores), Gatekeeper Tasks</title>
      <link>https://skylarvv.tistory.com/34</link>
      <description>&lt;h2&gt;1) Mutexes&lt;/h2&gt;
&lt;p&gt;뮤텍스는 공유 자원에 대한 접근을 제어하기 위한 동기화 도구, 뮤텍스는 한 번에 하나의 태스크만 자원에 접근할 수 있도록 보장하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;뮤텍스는 공유 리소스에 대한 액세스를 제어하는 데 사용되는 특수한 유형의 바이너리 세마포어이다&lt;/p&gt;
&lt;p&gt;바이너리 세마포어와 다른 점 : 뮤텍스는 우선순위 역전 현상을 예방해줄 메커니즘을 포함&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xSemaphoreHandle = xSemaphoreCreateMutex();            // 뮤텍스 생성

if (xSemaphoreTake(xMutex, portMAX_DELAY)) { // 뮤텍스 획득
    // 자원 사용
    xSemaphoreGive(xMutex);                  //뮤텍스 반환
}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;뮤텍스는 우선순위 역전 문제 방지를 위해 우선순위 상속 메커니즘을 제공&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;xSemaphoreCreateMutex(); 에서 NULL 반환 시 힙 영역 부족하다는 뜻 &lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/elFs4I/dJMcagSkJUg/wLXnicbqzuurvD1Hhtky2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elFs4I/dJMcagSkJUg/wLXnicbqzuurvD1Hhtky2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elFs4I/dJMcagSkJUg/wLXnicbqzuurvD1Hhtky2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelFs4I%2FdJMcagSkJUg%2FwLXnicbqzuurvD1Hhtky2K%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;뮤텍스를 태스크 A에서 사용중일 경우, 뮤텍스에 접근한 태스크 B는 Blocked state로 전환  &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cG6ttw/dJMcag5RNoi/Vn5asFPkBs2ioRXcnzp3ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cG6ttw/dJMcag5RNoi/Vn5asFPkBs2ioRXcnzp3ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cG6ttw/dJMcag5RNoi/Vn5asFPkBs2ioRXcnzp3ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcG6ttw%2FdJMcag5RNoi%2FVn5asFPkBs2ioRXcnzp3ZK%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;뮤텍스가 반환되면 B의 Blocked state 종료&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsM0MO/dJMcajaoXmi/2SGPIRU9KjHEm3ikdd0le1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsM0MO/dJMcajaoXmi/2SGPIRU9KjHEm3ikdd0le1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsM0MO/dJMcajaoXmi/2SGPIRU9KjHEm3ikdd0le1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsM0MO%2FdJMcajaoXmi%2F2SGPIRU9KjHEm3ikdd0le1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2) Binary Semaphores&lt;/h2&gt;
&lt;p&gt;0 과 1로만 이뤄진 semaphore다. Critical section에 대해 p연산을 수행하면 semaphore가 0이 되어서 다른 task의 접근을 막는다.&lt;/p&gt;
&lt;p&gt;접근을 요청한 task는 blocked 상태가 되며 들어갈 때 까지 기다린다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;take 함수에서 기다리는 시간을 설정&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xSemaphoreGive();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xSemaphoreTake();&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3) Priority Inheritance (우선순위 상속)&lt;/h2&gt;
&lt;p&gt;일시적으로 나의 우선순위(높은)를 주어서 Inversion 문제를 해결&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eAXkrj/dJMcadgNyZe/reJKW6TjXZIqrbdp9r7om1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eAXkrj/dJMcadgNyZe/reJKW6TjXZIqrbdp9r7om1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eAXkrj/dJMcadgNyZe/reJKW6TjXZIqrbdp9r7om1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeAXkrj%2FdJMcadgNyZe%2FreJKW6TjXZIqrbdp9r7om1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;314&quot; data-origin-width=&quot;1237&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리소스를 가진 LP의 우선순위를 HP 수준으로 일시적으로 끌어올려 줌&lt;/li&gt;
&lt;li&gt;따라서 MP가 HP보다 먼저 실행되지 않게 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4) Deadlock (or Deadly Embrace)&lt;/h2&gt;
&lt;p&gt;둘 이상의 태스크가 서로 가진 자원을 기다리며 무한 대기 상태에 빠지는 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Task A는 Mutex X를 점유하고 Mutex Y를 기다림&lt;/li&gt;
&lt;li&gt;Task B는 Mutex Y를 점유하고 Mutex X를 기다림&lt;/li&gt;
&lt;li&gt;→ 서로 양보하지 않아서 stuck&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리소스를 항상 &lt;strong&gt;같은 순서로&lt;/strong&gt; 획득하도록 설계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타임아웃&lt;/strong&gt; 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재귀 뮤텍스(Recursive Mutex)&lt;/strong&gt; 사용 (아래 설명)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5) Recursive Mutexes (재귀적 뮤텍스)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;xSemaphoreHandle mutex = xSemaphoreCreateRecursiveMutex();

xSemaphoreTakeRecursive(mutex);  
xSemaphoreTakeRecursive(mutex); // 같은 태스크이기 때문에 가능 (재귀)

xSemaphoreGiveRecursive(mutex);  
xSemaphoreGiveRecursive(mutex); // 두번 반환해야 완전 해제&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;하나의 태스크가 &lt;strong&gt;같은 뮤텍스를 여러 번 획득&lt;/strong&gt; 할 수 있도록 허용&lt;/li&gt;
&lt;li&gt;일반 Mutex는 한 번만 획득 가능 → 중첩 호출 시 문제 발생&lt;/li&gt;
&lt;li&gt;재귀 뮤텍스는 획득 횟수를 기억하고, 같은 수만큼 반환해야 진짜 반환됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6) Mutexes and Task Scheduling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mutex는 공유 자원 보호뿐 아니라 태스크 스케줄링에 간접적 영향도 줌&lt;/li&gt;
&lt;li&gt;예: 높은 우선순위 태스크가 뮤텍스를 기다리면 블로킹 상태로 빠지고, 그 사이 낮은 우선순위 태스크가 실행될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;→ 뮤텍스는 단순한 동기화 수단이 아니라, RTOS 스케줄링의 흐름까지 영향을 줌&lt;/p&gt;
&lt;h2&gt;7) Gatekeeper Tasks&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;1159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAGgkh/dJMcafyWffl/3wEnxC3oc8MsI5aUxVsVA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAGgkh/dJMcafyWffl/3wEnxC3oc8MsI5aUxVsVA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAGgkh/dJMcafyWffl/3wEnxC3oc8MsI5aUxVsVA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAGgkh%2FdJMcafyWffl%2F3wEnxC3oc8MsI5aUxVsVA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;631&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;1159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;특정 리소스에 대한 단독 소유권을 갖는 태스크&lt;/li&gt;
&lt;li&gt;다른 태스크는 해당 리소스를 직접 접근하지 않고, Gatekeeper를 통해 간접 접근&lt;/li&gt;
&lt;li&gt;Example 21 : gatekeeper 태스크를 사용하도록 vPrintString() 재구현(tick hook 함수를 이용해 200틱마다 메세지 전송)&lt;ul&gt;
&lt;li&gt;Gatekeeper는 큐(xPrintQueue) 를 통해 문자열 포인터를 수신&lt;/li&gt;
&lt;li&gt;출력 요청 태스크는 printf 직접 호출 대신, 메시지를 큐에 전송&lt;/li&gt;
&lt;li&gt;Gatekeeper는 메시지를 수신하면 printf로 출력, 이후 다시 대기&lt;/li&gt;
&lt;li&gt;대부분 시간은 xQueueReceive() 에서 Blocked 상태로 대기&lt;/li&gt;
&lt;li&gt;Tick Hook&lt;/li&gt;
&lt;li&gt;vApplicationTickHook() 사용 → 200틱마다 문자열 전송&lt;/li&gt;
&lt;li&gt;사용 전 FreeRTOSConfig.h에서 configUSE_TICK_HOOK = 1 설정 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tick Hook 주의사항&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;ul&gt;
&lt;li&gt;Tick ISR 컨텍스트에서 실행됨&lt;/li&gt;
&lt;li&gt;코드 짧게 작성, 스택 사용 최소화&lt;/li&gt;
&lt;li&gt;FromISR 계열 함수만 사용 가능&lt;/li&gt;
&lt;li&gt;pxHigherPriorityTaskWoken → 사용하지 않으면 NULL로 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;   { 
    char *pcMessageToPrint; 
  for( ;; ) 
  { 
    xQueueReceive( xPrintQueue, &amp;amp;pcMessageToPrint, portMAX_DELAY ); 

    printf( &amp;quot;%s&amp;quot;, pcMessageToPrint ); 
    fflush( stdout ); 
  } 
} &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;static void prvPrintTask( void *pvParameters ) 
{ 
  int iIndexToString; 
  const TickType_t xMaxBlockTimeTicks = 0x20; 

  iIndexToString = ( int ) pvParameters; 
  for( ;; ) 
  { 

    xQueueSendToBack( xPrintQueue, &amp;amp;( pcStringsToPrint[ iIndexToString ] ), 0 ); 

    vTaskDelay( ( rand() % xMaxBlockTimeTicks ) ); 
  } 
} &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;void vApplicationTickHook( void ) 
{ 
  static int iCount = 0; 

  iCount++; 
  if( iCount &amp;gt;= 200 ) 
  { 
    xQueueSendToFrontFromISR( xPrintQueue, &amp;amp;( pcStringsToPrint[ 2 ] ), NULL ); 
    iCount = 0; 
  } 
} &lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;static char *pcStringsToPrint[] = 
{ 
  &amp;quot;Task 1 ****************************************************\r\n&amp;quot;, 
  &amp;quot;Task 2 ----------------------------------------------------\r\n&amp;quot;, 
  &amp;quot;Message printed from the tick hook interrupt ##############\r\n&amp;quot; 
}; 

QueueHandle_t xPrintQueue; 

int main( void ) 
{ 

  xPrintQueue = xQueueCreate( 5, sizeof( char * ) ); 

  if( xPrintQueue != NULL ) 
  { 
    xTaskCreate( prvPrintTask, &amp;quot;Print1&amp;quot;, 1000, ( void * ) 0, 1, NULL ); 
    xTaskCreate( prvPrintTask, &amp;quot;Print2&amp;quot;, 1000, ( void * ) 1, 2, NULL ); 

    xTaskCreate( prvStdioGatekeeperTask, &amp;quot;Gatekeeper&amp;quot;, 1000, NULL, 0, NULL ); 

    vTaskStartScheduler(); 
  } 

  for( ;; ); 
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>RTOS</category>
      <category>mutex</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/34</guid>
      <comments>https://skylarvv.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 26 Feb 2026 21:36:23 +0900</pubDate>
    </item>
    <item>
      <title>가끔 와서 기본을 돌아보기!</title>
      <link>https://skylarvv.tistory.com/notice/33</link>
      <description></description>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/notice/33</guid>
      <pubDate>Thu, 26 Feb 2026 21:16:01 +0900</pubDate>
    </item>
    <item>
      <title>freeRTOS Resource Management(1)-Critical Sections and Suspending the Scheduler</title>
      <link>https://skylarvv.tistory.com/31</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티태스킹 시스템에서 여러 Task 나 ISR이 공유 자원에 동시에 접근&lt;br /&gt;&amp;rarr; Data Corruption이 발생 가능
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Accessing Peripherals (주변장치 접근)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 Task가 동시에 UART, SPI, I2C에 접근&lt;/li&gt;
&lt;li&gt;출력 충돌, 데이터 손상, 통신 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Read-Modify-Write Operations (읽기-수정-쓰기 연산)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;count++, total += value 같은 연산은 다단계 연산&lt;/li&gt;
&lt;li&gt;context switching 발생 시 값이 꼬임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Non-atomic Access to Variables (비원자적 변수 접근)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;32비트 변수나 구조체 등 멀티바이트 자료를 여러 Task가 접근&lt;/li&gt;
&lt;li&gt;중간 값이 읽히거나 쓰이면 예상과 다른 결과&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Function Reentrancy (재진입성 문제)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;printf, rand 같은 함수는 thread-safe 하지 않음&lt;/li&gt;
&lt;li&gt;여러 Task가 동시에 호출하면 내부 버퍼 충돌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;freeRTOS document에서 multitasking system의 오류 발생 포인트를 정리해보았다.&lt;/li&gt;
&lt;li&gt;FreeRTOS 시스템에서는 Task 뿐만 아니라 ISR도 공유자원에 접근 가능&lt;br /&gt;&amp;rarr; 위험성 증가 (ISR이 Task를 가로채 실행될 수 있음)&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/bcjiXu/dJMcabwvnDm/AAAAAAAAAAAAAAAAAAAAADgFRjvqi9DAbIGWIz_uo6WVVpIw07H-UsD_7Aea_YAH/img.webp?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1772290799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=86YZ%2BGL8qxuHZDhuxYf%2FjIE2WH8%3D&quot; alt=&quot;&quot; width=&quot;630&quot; height=&quot;172&quot; /&gt;&lt;/div&gt;
&lt;br /&gt;&amp;rArr; Mutual Exclusion 기법으로 관리되어야 함&lt;/li&gt;
&lt;li&gt;mutual exclusion : 한 시점에 단 하나의 Task 또는 ISR만 자원에 접근 가능하게 제어
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자원 보호 및 데이터 무결성 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FreeRTOS 시스템에서 다양한 mutual exclusion 도구를 제공&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/xBkGK/dJMcadgNytu/AAAAAAAAAAAAAAAAAAAAAOX9D7gdWS3NFLTvzoXJISWsopToSnIjrD7sgh_Hdcl6/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1772290799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=yScSIygJbZzalBDNO8WY2PGuZVc%3D&quot; width=&quot;526&quot; height=&quot;204&quot; data-image-src=&quot;https://blog.kakaocdn.net/dna/xBkGK/dJMcadgNytu/AAAAAAAAAAAAAAAAAAAAAOX9D7gdWS3NFLTvzoXJISWsopToSnIjrD7sgh_Hdcl6/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1772290799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=yScSIygJbZzalBDNO8WY2PGuZVc%3D&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;298&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 가장 간단하고 안전한 방법은 공유 자원이 없도록 설계하는 것&lt;br /&gt;&amp;rarr; 하나의 자원은 하나의 Task만 접근!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 위 4 방법 중 Critical Section을 만드는 두 가지 방법이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Critical section&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터럽트 응답 시간에 영향을 끼치므로, Critical Section은 짧아야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;1027&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYboJ/dJMcagYSZzD/wy59FVZuxnbVjT3K9s8KYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYboJ/dJMcagYSZzD/wy59FVZuxnbVjT3K9s8KYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYboJ/dJMcagYSZzD/wy59FVZuxnbVjT3K9s8KYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYboJ%2FdJMcagYSZzD%2Fwy59FVZuxnbVjT3K9s8KYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;515&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;1027&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;taskENTER_CRITICAL / taskEXIT_CRITICAL 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;taskENTER_CRITICAL&lt;/li&gt;
&lt;li&gt;critical section 시작&lt;/li&gt;
&lt;li&gt;taskEXIT_CRITICAL&lt;/li&gt;
&lt;li&gt;critical section 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 함수 모두 입력 인자와 반환 값이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수명이 FromISR로 끝나지 않으며, ISR로 호출되는 것만은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; ISR로부터 접근이 가능하기 때문에, 완전한 보호가 되지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1773&quot; data-origin-height=&quot;1104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8BLpR/dJMcahwKJKT/UXZ5BIPkuu8CBNJg0fO0Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8BLpR/dJMcahwKJKT/UXZ5BIPkuu8CBNJg0fO0Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8BLpR/dJMcahwKJKT/UXZ5BIPkuu8CBNJg0fO0Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8BLpR%2FdJMcahwKJKT%2FUXZ5BIPkuu8CBNJg0fO0Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;641&quot; height=&quot;399&quot; data-origin-width=&quot;1773&quot; data-origin-height=&quot;1104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;taskENTER_CRITICAL_FROM_ISR / taskEXIT_CRITICAL_FROM_ISR 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 함수에서 interrupt safe 버전이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;taskENTER_CRITICAL_FROM_ISR&lt;/li&gt;
&lt;li&gt;taskEXIT_CRITICAL_FROM_ISR&amp;rarr; ENTER 함수의 반환 값과 일치해야만 한다.&lt;/li&gt;
&lt;li&gt;입력 인자로서 ENTER의 반환 값을 받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Suspending (or Locking) the Scheduler&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scheduler에 lock을 걸어 선점을 막는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1401&quot; data-origin-height=&quot;1006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bguYix/dJMcaaj5gc3/u64q3bSTrNop1yjfImv36K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bguYix/dJMcaaj5gc3/u64q3bSTrNop1yjfImv36K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bguYix/dJMcaaj5gc3/u64q3bSTrNop1yjfImv36K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbguYix%2FdJMcaaj5gc3%2Fu64q3bSTrNop1yjfImv36K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;442&quot; data-origin-width=&quot;1401&quot; data-origin-height=&quot;1006&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vTaskSuspendAll / vTaskResumeAll 함수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vTaskSuspendAll
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;return 값&lt;/li&gt;
&lt;li&gt;스케줄러가 suspend 된 동안 요청된 context switch는 보류 상태로 남는데, 스케줄러가 재개될 때 실행된다. 보류된 context switch가 스케줄러 재개 전에 수행되면 pdTRUE를 반환하고, 그렇지 않으면 pdFALSE를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스케줄러를 suspend 한다.&lt;/li&gt;
&lt;li&gt;vTaskResumeAll&lt;/li&gt;
&lt;li&gt;스케줄러 Suspend 해제시킨다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>RTOS</category>
      <category>critical section</category>
      <category>Mutual Exclusion</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/31</guid>
      <comments>https://skylarvv.tistory.com/31#entry31comment</comments>
      <pubDate>Thu, 26 Feb 2026 20:54:26 +0900</pubDate>
    </item>
    <item>
      <title>메모리 맵 I/O - mmap()으로 매핑하여 GPIO 직접 제어해보기</title>
      <link>https://skylarvv.tistory.com/30</link>
      <description>&lt;p&gt;라즈베리파이 4 (BCM2711)에서 메모리 맵 I/O로 GPIO를 직접 제어해보는 예시 일부를 요약해서 필기해보았다.&lt;/p&gt;
&lt;h5&gt;메모리 맵 I/O (Memory-Mapped I/O)&lt;/h5&gt;
&lt;p&gt;개념:&lt;br&gt;CPU의 주소 공간에 메모리뿐 아니라 I/O 장치 레지스터(GPIO, UART, SPI 등)를 같은 주소 공간에 배치하는 방식.&lt;br&gt;→ CPU는 메모리에 접근하는 것과 동일한 방식으로 장치를 제어할 수 있음.&lt;br&gt;전체 메모리 공간을 메모리와 I/O 장치로 나눠서 사용&lt;/p&gt;
&lt;p&gt;버스 요약&lt;/p&gt;
&lt;p&gt;Address bus: CPU는 값을 가져오기 위해 접근할 메모리/장치 주소를 어드레스 버스를 통해 설정&lt;/p&gt;
&lt;p&gt;Data bus: 데이터버스를 통해 값을 읽어옴.&lt;/p&gt;
&lt;p&gt;Control bus: 읽기/쓰기 동작 제어 (입출력 방향 설정)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/eeadadac-9930-4df1-ac4c-2ce27429cc31/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;어드레스 버스를 통해 GPIO와 같은 H/W에 접근, 메모리 특정 주소를 사용해 GPIO에 값을 보내거나, 들어오는 값을 읽는다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/6d254480-3172-46d6-9edc-00bec8b9ccfb/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;mmap()으로 라즈베리파이 led 제어해보기&lt;/h4&gt;
&lt;p&gt;bcm2711의 메모리 주소 맵핑&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/a601ef6a-096e-46d7-a7a6-9bca367f4c5e/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/abecbc20-8282-4e36-beec-931037c6589f/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;BCM2711의 GPIO주소&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/05b771fa-a87b-426f-b074-43ca4c744813/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GPIO base 주소: 0x7E200000 (I/O base + 0x200000)&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;GPIO 주요 레지스터&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/0c2f6ea3-a9ac-4e46-ab6c-ef853957f0e9/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/b4364a35-4e93-4f35-be9a-d7c95cd9fb76/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/1107597d-529f-48f0-afb3-6e0b04411008/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;GPFSELn (GPIO Function Select) : 각 GPIO 핀의 입출력 모드 설정 (핀당 3비트)&lt;/p&gt;
&lt;p&gt;GPSETn : GPIO 출력 High (1로 설정)&lt;/p&gt;
&lt;p&gt;GPCLRn : GPIO 출력 Low (0으로 클리어)&lt;/p&gt;
&lt;p&gt;GPLEVn : 현재 입력 레벨 확인  &lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;GPIO 방향 설정 (GPFSEL)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/dd3def69-1e2a-46c4-92e0-d32d7a5cb355/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;핀 하나당 3비트씩 할당하여 I/O 방향을 설정.&lt;/p&gt;
&lt;p&gt;입력 : 000, 출력 : 001&lt;br&gt;특정 핀 번호를 GPFSEL 레지스터에서 찾는 방법:&lt;/p&gt;
&lt;p&gt;총 0~5번으로 6개의 GPFSEL레지스터. gpio핀은 58개,&lt;br&gt;핀번호 / 10 → 사용할 GPFSEL 레지스터 선택&lt;/p&gt;
&lt;p&gt;핀번호 % 10 → 그 레지스터 내에서의 비트 위치&lt;/p&gt;
&lt;h4&gt;mmap()&lt;/h4&gt;
&lt;p&gt;Memory-mapped I/O: I/O 장치를 메모리 주소 공간에 포함시켜 접근&lt;br&gt;Raspberry Pi 4 (BCM2711): GPIO base는 0xFE200000&lt;br&gt;직접 제어: /dev/mem → mmap()으로 매핑 → GPIO 레지스터 접근 → 토글&lt;br&gt;이제 mmap()을 알아보고 직접 제어를 해볼 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;sys/mman.h&amp;gt;

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/0785be87-b1c3-45d0-9858-bb1b55ceba43/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;(사물인터넷을 위한 리눅스 프로그래밍 with 라즈베리파이)&lt;/p&gt;
&lt;p&gt;mmap() : fd가 가리키는 객체를 offset의 값을 기준으로 length 만큼 메모리에 매핑하도록 커널에 요청하면 매핑된 시작 주소가 반환된다.&lt;/p&gt;
&lt;p&gt;munmap() : mmap()함수와 연결된 메모리 해제.&lt;/p&gt;
&lt;h4&gt;주요 코드 흐름&lt;/h4&gt;
&lt;p&gt;/dev/mem → mmap()으로 매핑 → GPIO 레지스터 접근 → 토글&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;/dev/mem 열기커널이 관리하는 물리 메모리 공간에 접근하기 위해 /dev/mem 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mem_fd = open(&amp;quot;/dev/mem&amp;quot;, O_RDWR | O_SYNC);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;mmap()으로 물리 주소를 가상 주소에 매핑GPIO 레지스터 주소 영역을 프로세스의 가상주소에 매핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpio_map = mmap(NULL, GPIO_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, GPIO_BASE);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GPIO 포인터 설정이제 gpio[]배열처럼 접근하면 실제 레지스터에 읽기, 쓰기 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gpio= (volatile unsigned *)gpio_map;&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;GPIO를 출력으로 설정 하는예&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;GPIO\_OUT(gno); &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매크로로 해당 핀을 출력 모드(001)로 설정.&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;p&gt;LED 토글&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GPIO_SET(gno) //high
sleep(1);
GPIO_CLR(gno) //Low
sleep(1);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;1초 간격으로 GPIO핀을 on/off하는 예.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;리소스 해제!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;munmap(gpio_map, GPIO_SIZE);
close(mem_fd);&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Linux</category>
      <category>memory-mapped i/o</category>
      <category>mmap()</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/30</guid>
      <comments>https://skylarvv.tistory.com/30#entry30comment</comments>
      <pubDate>Sun, 17 Aug 2025 17:43:10 +0900</pubDate>
    </item>
    <item>
      <title>문자 디바이스 파일 (주 번호와 부번호 할당, 번호 등록과 사용)</title>
      <link>https://skylarvv.tistory.com/29</link>
      <description>&lt;h4&gt;- 문자 디바이스 파일 character device file&lt;/h4&gt;
&lt;p&gt;사용자 공간 애플리케이션이 /dev/ 아래 디바이스 파일을 통해&lt;/p&gt;
&lt;p&gt;커널에 구현된 문자 디바이스 드라이버와 소통할 수 있게 하는 인터페이스.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문자 디바이스는 데이터를 byte 스트림단위로 읽고 쓴다.&lt;/li&gt;
&lt;li&gt;키보드,프린터,마우스,콘솔,직렬 포트(UART)등.&lt;/li&gt;
&lt;li&gt;file_operations 구조체의 read,write함수 등을 구현해 데이터 송수신 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;- 블록 디바이스 파일 Block device file&lt;/h4&gt;
&lt;p&gt;/dev/ 아래 존재, 사용자 공간 애플리케이션이 커널 모드의 블록 디바이스 드라이버와 소통하도록 하는 인터페이스.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터를 블록 단위(512B~수KB)로 읽고 씀.→랜덤 액세스 가능.&lt;/li&gt;
&lt;li&gt;HDD,SSD, eMMC,SD,USB저장장치,CD/DVD 등&lt;/li&gt;
&lt;li&gt;block_device_operations 구조체와 I/O스케줄러를 이용해 읽기/쓰기 요청을 큐에 넣고 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/5caab94b-e2c2-4f41-bcf9-c2ef1221d3c5/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;문자 디바이스 파일&lt;/h3&gt;
&lt;h4&gt;디바이스의 주 번호와 부 번호&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;
**디바이스 번호의 자료형**
dev_t : 주 번호 12비트/ 부 번호 20 비트 . 32비트의 크기

#define MAJOR(dev) ((unsigned int) ((dev) &amp;gt;&amp;gt; 20))
메이저 번호:  어떤 드라이버가 이 디바이스를 처리하는지 식별
#define MINOR(dev) ((unsigned int) ((dev) &amp;amp; 0xfffff))
마이너 번호: 동일 드라이버 내 어떤 장치인지(tty,tty0b

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;문자 디바이스 파일 번호 동적 할당과 등록&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;dev_t sk_dev;
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
alloc_chrdev_region(&amp;amp;my_dev, 0, 1, &amp;quot;my_dev&amp;quot;);

*dev : 할당된 디바이스 번호를 저장할 포인터.(my_dev에 저장)
baseminor:할당 시작할 부 번호(minor) (0부터 시작) 
count: 필요한 부 번호 개수 (디바이스 수)                
name: /proc/devices에 등록될 이름 (정보용)       &lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;문자 디바이스 번호 등록 (&lt;strong&gt;번호 공간 예약&lt;/strong&gt;)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;dev_t devno = MKDEV(GPIO_MAJOR,GPIO_MINOR);
미리 정한 메이저/마이너 번호를 dev_t로 생성
register_chrdev_region(devno,count,GPIO_DEVICE);
이미 정해진 번호를 커널에 예약 등록,이미 사용중-&amp;gt;에러.

#include &amp;lt;linux/fs.h&amp;gt;
int register_chrdev_region(dev_t from, unsigned count, const char *name);

from : 등록할 첫번째 디바이스 번호(메이저,마이너가 포함된 dev_t)
count : 등록할 연속된 디바이스 번호 개수
name : 디바이스 이름 (/proc/devices에 표시)
리턴 : 성공 시 0 실패시 음수 에러 (-EBUSY)

void unregister_chrdev_region(dev_t from, unsigned count);
register_chrdev_region으로 예약한 번호 공간 해제하여 다른 드라이버가 쓸 수 있도록 함.
from : 해제할 첫번째 디바이스 번호(예약한 dev_t)
count :해제할 연속 디바이스 번호 개수&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;번호 충돌 가능성 있음&lt;br&gt;번호를 명확히 알고 있어야 함&lt;br&gt;레거시 코드나 특별한 번호 필요할 때 사용&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;/proc/devices&lt;/td&gt;
&lt;td&gt;커널에 등록된 모든 캐릭터 및 블록 디바이스의 리스트. register_chrdev_region()에 의해 등록됨.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;./dev/my_dev&lt;/td&gt;
&lt;td&gt;유저 공간에서 사용하는 &lt;strong&gt;장치 파일 (device node)&lt;/strong&gt;. 커널이 자동으로 만들지 않음. 직접 mknod하거나 udev 설정 필요.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h4&gt;file_operations 구조체에 연산 등록하여 open,read,write 등의 파일 연산 함수 사용&lt;/h4&gt;
&lt;p&gt;include./linux/fs.h&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/14c64a7c-fc0e-4221-8ac6-3b11c7c23422/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;file_operations 구조체의 역할&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리눅스 커널에서 디바이스 드라이버가 파일 시스템 인터페이스를 구현할 수 있도록 해주는 콜백 함수 집합.&lt;br&gt;이 구조체에 함수를 등록함으로써&lt;br&gt;open(),read(),write(),ioctl(),mmap()등의 시스템 콜이 호출될 때, 해당 드라이버의 동작이 실행되도록 연결.  &lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;cdev 구조체 (linux/cdev.h)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct cdev { struct kobject kobj;//sysfs와 연동하기 위한 커널 오브젝트 
struct module \*owner;//(THIS\_MODULE)이 모듈이 언로드 되지 않도록 참조를 잡아줌 
const struct file\_operations \*ops;//open,read,write 등 파일 연산 함수들 집합 
struct list\_head list;//내부 연결 리스트. cdev가 여러개 일때 리스트로 관리 
dev\_t dev;//major, minor 조합의 디바이스 번호 
unsigned int count;// cdev가 몇개의 마이너 번호 다루는지(대부분 1개) } __randomize_layout; &lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;cdev_init (fs/char_dev.c)&lt;/h4&gt;
&lt;p&gt;구조체를 초기화하고 file operations에 open,write 등의 연산을 연결한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    memset(cdev, 0, sizeof *cdev);
    INIT_LIST_HEAD(&amp;amp;cdev-&amp;gt;list);
    kobject_init(&amp;amp;cdev-&amp;gt;kobj, &amp;amp;ktype_cdev_default);
    cdev-&amp;gt;ops = fops;
}
 /* cdev_init() - initialize a cdev structure
 * @cdev: the structure to initialize
 * @fops: the file_operations for this device
 */&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;cdev_add&lt;/h4&gt;
&lt;p&gt;커널 공간의 문자 디바이스 구조체(struct cdev)를 시스템에 연결하여, 유저가 /dev/~ 경로로 접근할 수 있도록 등록한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; /*cdev_add() - add a char device to the system
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this
 *         device
 *
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.
 */
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    int error;

    p-&amp;gt;dev = dev;
    p-&amp;gt;count = count;

    if (WARN_ON(dev == WHITEOUT_DEV)) {
        error = -EBUSY;
        goto err;
    }

    error = kobj_map(cdev_map, dev, count, NULL,
             exact_match, exact_lock, p);
             //cdev_map에 전역 맵에 해당 번호 등록. 
    if (error)
        goto err;

    kobject_get(p-&amp;gt;kobj.parent);

    return 0;

err:
    kfree_const(p-&amp;gt;kobj.name);
    p-&amp;gt;kobj.name = NULL;
    return error;
}&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;mknod&lt;/h4&gt;
&lt;p&gt;/dev/에 노드 파일을 만들어서 ,등록된 문자 디바이스에 파일로 유저가 접근할 수 있게한다. 디바이스 번호(major,minor)를 가진 파일을 만든다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;실행 흐름&lt;/th&gt;
&lt;th&gt;함수/명령어&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1. 디바이스 번호 할당 커널에서 Major,Minor번호 할당&lt;/td&gt;
&lt;td&gt;alloc_chrdev_region(&amp;amp;devmfirstminor,count,”name”)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. cdev 구조체 초기화 및 등록 (cdev 구조체에 파일 연산자 연결)&lt;/td&gt;
&lt;td&gt;cdev_init(&amp;amp;cdev, &amp;amp;fops) cdev_add(&amp;amp;cdev,dev,count)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. 유저 공간에서 디바이스 파일 생성&lt;/td&gt;
&lt;td&gt;mknod /dev/name c (major) (minor)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. 유저 프로그램에서 /dev/ 파일을 open() 호출하면 커널 드라이버의 open 함수 호출.&lt;/td&gt;
&lt;td&gt;open(”/dev/name”,flags)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</description>
      <category>Linux</category>
      <category>문자디바이스</category>
      <category>블록디바이스</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/29</guid>
      <comments>https://skylarvv.tistory.com/29#entry29comment</comments>
      <pubDate>Sun, 17 Aug 2025 16:23:24 +0900</pubDate>
    </item>
    <item>
      <title>[리눅스 커널과 디바이스 드라이버]애플리케이션-&amp;gt;syscall-&amp;gt;vfs-&amp;gt;디바이스파일open 까지</title>
      <link>https://skylarvv.tistory.com/28</link>
      <description>&lt;p&gt;유저 공간의 애플리케이션 코드에서 시스템 호출을 통해&lt;br&gt;커널을 통해 어떻게 디바이스 파일에 접근하여 장치를 사용하는지 함수 호출을 따라가보았다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/d21c8bd0-b14b-44b0-8418-c90f7a631ca7/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;사물인터넷을 위한 리눅스 프로그래밍 with 라즈베리파이(서영진)&lt;/p&gt;
&lt;h4&gt;1. &lt;strong&gt;애플리케이션 호출&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    fd = open(&amp;quot;/dev/sk_dev&amp;quot;, O_RDWR);&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2. &lt;strong&gt;시스템 호출&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/cf15ec3f-670a-40a4-a4af-895a6da9cd11/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;AT_FDCWD : 현재 작업 디렉토리에서 상대경로로 파일을 연다.  &lt;/p&gt;
&lt;p&gt;커널이 제공하는 매크로 (→ syscall 테이블에 매핑되어 호출.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/66345a19-b566-423d-ad34-649171a0c302/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/df477abe-8c85-4e75-a3dc-5c8eaf3e739f/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;do_sys_open→do_sys_openat2→do_filp_open&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/12d477b3-212d-4aa3-aca2-dc41a27bc696/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/dd5c8f51-bb02-4007-8ef8-5da8602d76d6/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt; do_filp_open→path_openat→do_open→vfsopen&lt;/p&gt;
&lt;p&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/d2855973-e114-44ad-ae7d-f28e873b5ad6/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt; nd : 경로 파싱 과정의 상태 저장소&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define EMBEDDED_LEVELS 2 struct nameidata { struct path path; struct qstr last; struct path root; struct inode *inode; /* path.dentry.d_inode */ unsigned int flags, state; unsigned seq, next_seq, m_seq, r_seq; int last_type; unsigned depth; int total_link_count; struct saved { struct path link; struct delayed_call done; const char *name; unsigned seq; } *stack, internal[EMBEDDED_LEVELS]; struct filename *name; struct nameidata *saved; unsigned root_seq; int dfd; vfsuid_t dir_vfsuid; umode_t dir_mode; } __randomize_layout;&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;3. ** 가상 파일 시스템 do_open-&amp;gt;vfs_open**&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/bed2d45f-f41a-458f-8eab-a118c63644fe/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/84c6ea97-f6fe-4712-a314-8c204096a9c3/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;open.c/vfs_open&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/3990e847-bc81-4040-a8b7-f10f78baa837/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;4. &lt;strong&gt;inode로 파일 연결 do_dentry_open&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;do_dentry_open&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    static int do_dentry_open(struct file *f,
                  int (*open)(struct inode *, struct file *))
    {
        static const struct file_operations empty_fops = {};
        struct inode *inode = f-&amp;gt;f_path.dentry-&amp;gt;d_inode;
        int error;

        path_get(&amp;amp;f-&amp;gt;f_path);
        f-&amp;gt;f_inode = inode;
        f-&amp;gt;f_mapping = inode-&amp;gt;i_mapping;
        f-&amp;gt;f_wb_err = filemap_sample_wb_err(f-&amp;gt;f_mapping);
        f-&amp;gt;f_sb_err = file_sample_sb_err(f);

        if (unlikely(f-&amp;gt;f_flags &amp;amp; O_PATH)) {
            f-&amp;gt;f_mode = FMODE_PATH | FMODE_OPENED;
            f-&amp;gt;f_op = &amp;amp;empty_fops;
            return 0;
        }

        if ((f-&amp;gt;f_mode &amp;amp; (FMODE_READ | FMODE_WRITE)) == FMODE_READ) {
            i_readcount_inc(inode);
        } else if (f-&amp;gt;f_mode &amp;amp; FMODE_WRITE &amp;amp;&amp;amp; !special_file(inode-&amp;gt;i_mode)) {
            error = file_get_write_access(f);
            if (unlikely(error))
                goto cleanup_file;
            f-&amp;gt;f_mode |= FMODE_WRITER;
        }

        /* POSIX.1-2008/SUSv4 Section XSI 2.9.7 */
        if (S_ISREG(inode-&amp;gt;i_mode) || S_ISDIR(inode-&amp;gt;i_mode))
            f-&amp;gt;f_mode |= FMODE_ATOMIC_POS;

        f-&amp;gt;f_op = fops_get(inode-&amp;gt;i_fop);
        if (WARN_ON(!f-&amp;gt;f_op)) {
            error = -ENODEV;
            goto cleanup_all;
        }

        error = security_file_open(f);
        if (error)
            goto cleanup_all;

        error = break_lease(file_inode(f), f-&amp;gt;f_flags);
        if (error)
            goto cleanup_all;

        /* normally all 3 are set; -&amp;gt;open() can clear them if needed */
        f-&amp;gt;f_mode |= FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
        if (!open)
            open = f-&amp;gt;f_op-&amp;gt;open;
        if (open) {
            error = open(inode, f);
            if (error)
                goto cleanup_all;
        }
        f-&amp;gt;f_mode |= FMODE_OPENED;
        if ((f-&amp;gt;f_mode &amp;amp; FMODE_READ) &amp;amp;&amp;amp;
             likely(f-&amp;gt;f_op-&amp;gt;read || f-&amp;gt;f_op-&amp;gt;read_iter))
            f-&amp;gt;f_mode |= FMODE_CAN_READ;
        if ((f-&amp;gt;f_mode &amp;amp; FMODE_WRITE) &amp;amp;&amp;amp;
             likely(f-&amp;gt;f_op-&amp;gt;write || f-&amp;gt;f_op-&amp;gt;write_iter))
            f-&amp;gt;f_mode |= FMODE_CAN_WRITE;
        if ((f-&amp;gt;f_mode &amp;amp; FMODE_LSEEK) &amp;amp;&amp;amp; !f-&amp;gt;f_op-&amp;gt;llseek)
            f-&amp;gt;f_mode &amp;amp;= ~FMODE_LSEEK;
        if (f-&amp;gt;f_mapping-&amp;gt;a_ops &amp;amp;&amp;amp; f-&amp;gt;f_mapping-&amp;gt;a_ops-&amp;gt;direct_IO)
            f-&amp;gt;f_mode |= FMODE_CAN_ODIRECT;

        f-&amp;gt;f_flags &amp;amp;= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
        f-&amp;gt;f_iocb_flags = iocb_flags(f);

        file_ra_state_init(&amp;amp;f-&amp;gt;f_ra, f-&amp;gt;f_mapping-&amp;gt;host-&amp;gt;i_mapping);

        if ((f-&amp;gt;f_flags &amp;amp; O_DIRECT) &amp;amp;&amp;amp; !(f-&amp;gt;f_mode &amp;amp; FMODE_CAN_ODIRECT))
            return -EINVAL;

        /*
         * XXX: Huge page cache doesn&amp;#39;t support writing yet. Drop all page
         * cache for this file before processing writes.
         */
        if (f-&amp;gt;f_mode &amp;amp; FMODE_WRITE) {
            /*
             * Depends on full fence from get_write_access() to synchronize
             * against collapse_file() regarding i_writecount and nr_thps
             * updates. Ensures subsequent insertion of THPs into the page
             * cache will fail.
             */
            if (filemap_nr_thps(inode-&amp;gt;i_mapping)) {
                struct address_space *mapping = inode-&amp;gt;i_mapping;

                filemap_invalidate_lock(inode-&amp;gt;i_mapping);
                /*
                 * unmap_mapping_range just need to be called once
                 * here, because the private pages is not need to be
                 * unmapped mapping (e.g. data segment of dynamic
                 * shared libraries here).
                 */
                unmap_mapping_range(mapping, 0, 0, 0);
                truncate_inode_pages(mapping, 0);
                filemap_invalidate_unlock(inode-&amp;gt;i_mapping);
            }
        }

        return 0;

    cleanup_all:
        if (WARN_ON_ONCE(error &amp;gt; 0))
            error = -EINVAL;
        fops_put(f-&amp;gt;f_op);
        put_file_access(f);
    cleanup_file:
        path_put(&amp;amp;f-&amp;gt;f_path);
        f-&amp;gt;f_path.mnt = NULL;
        f-&amp;gt;f_path.dentry = NULL;
        f-&amp;gt;f_inode = NULL;
        return error;
    }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;해당 inode를 파일에 연결한다.(f→f_path 참조 수 증가, 페이지 캐시용 mapping설정 등)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/125b32a9-d6a4-4b53-b352-6254ff9b6613/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/ebd63f9a-408a-4b24-9147-cb5d39ff5285/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;드라이버가 등록한 file operation을 읽어옴.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/263939db-f353-4c71-a457-454923f737e2/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;/dev/my_dev/에서 open이 null이면 op→에 등록된 .open 함수, (드라이버 등록한 함수,my_open,user_open...) 실행&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e9f0ec74-3132-453e-9d65-c8988a86bf47/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</description>
      <category>Linux</category>
      <category>가상파일시스템</category>
      <category>디바이스드라이버</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/28</guid>
      <comments>https://skylarvv.tistory.com/28#entry28comment</comments>
      <pubDate>Sun, 17 Aug 2025 15:13:02 +0900</pubDate>
    </item>
    <item>
      <title>리눅스 디바이스 드라이버 - module_init() 모듈 등록부터 사용자 호출까지</title>
      <link>https://skylarvv.tistory.com/27</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;리눅스 디바이스 드라이버 기본 뼈대 구조 (모듈로 구현할때)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. init / exit 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이버 로딩 시 초기화 (init)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이버 제거 시 자원 해제 (exit)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. register_chrdev(), platform_driver_register() 등으로 디바이스를 커널에 등록&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. file_operations 구조체&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 공간 &amp;rarr; 커널 공간 인터페이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open, read, write, unlocked_ioctl, release 등 시스템 콜과 연결&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 모듈 매크로&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;module_init(init_func) &amp;rarr; 드라이버 init 함수 등록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;module_exit(exit_func) &amp;rarr; 드라이버 exit 함수 등록&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 모듈 정보&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MODULE_LICENSE(&quot;GPL&quot;) &amp;rarr; 커널에 라이선스 정보 전달&lt;br /&gt;(GPL이 아니면 커널 내부 심볼 접근 제한)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택적으로 MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_VERSION 등을 추가&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자디바이스,블록디바이스, 플랫폼디바이스, 등 디바이스 드라이버 공부 시작때 정리해놓았던 것 처음부터 다시 블로그에 정리하려한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;module_init부터 initcall섹션 까지 따라가보기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널이 모듈을 로드하면 .initcall*.init 섹션을 순회하면서 함수 포인터를 하나씩 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 .initcall6.init에 들어간 initModule 함수가 호출!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게, 작성한 초기화 함수가 모듈 로딩 시 자동 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.module_init(initModule)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/edf4dd0e-8969-4e85-b409-89a8e5bb4afd/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;2. __initcall(initModule)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;include/linux/module.h (_init, _exit)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/7650837c-6fdf-45e0-b4f2-d24973cafb87/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;include/linux/init.h&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/b4cf564b-628f-4875-b9c4-ea152037b319/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;3&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;__define_initcall(initModule,init)&lt;br /&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/79eaa2f2-2a73-4971-bfdb-dfb638b68890/image.png&quot; alt=&quot;&quot; /&gt;)&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/ca9e01c9-d0e5-41fe-b902-76ecab7cf6da/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fn 함수 포인터를&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sectionc&lt;/b&gt;(__sec)라는 특정 섹션에 넣고 static으로 선언하며, __used 속성으로 컴파일러가 최적화하지 않도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fn 함수 주소가 .initcallxxx같은 섹션에 기록되어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널이나 모듈 로드시 이 함수들을 찾아서 실행한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;readelf -S hello_module.ko (정해진 섹션확인)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/ce166e29-7b2a-4646-9c09-b1401cb0a8f6/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/77891189-0831-4c31-a1f0-c778bb527e16/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;T initModule &amp;rarr; init 함수가 실제 텍스트 섹션에 있음&lt;br /&gt;syscall 등록 후 사용자 공간에서 호출하여 사용한다!&lt;/p&gt;</description>
      <category>Linux</category>
      <category>.ko</category>
      <category>디바이스드라이버</category>
      <category>모듈</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/27</guid>
      <comments>https://skylarvv.tistory.com/27#entry27comment</comments>
      <pubDate>Sun, 17 Aug 2025 00:47:17 +0900</pubDate>
    </item>
    <item>
      <title>syscall번호를 테이블에 넣어 커널에 빌트인하여 사용</title>
      <link>https://skylarvv.tistory.com/26</link>
      <description>&lt;h4&gt;빌트인 vs 모듈 형태 시스템 콜&lt;/h4&gt;
&lt;p&gt;&amp;lt;1&amp;gt;테이블에 번호를 추가해 빌트인 사용하는 것과&lt;br&gt;&amp;lt;2&amp;gt;.ko 모듈로 올렸다 내렸다 사용하는 방법 정리 후&lt;br&gt;모듈로 디바이스 드라이버 구현 시 initcall로 어떻게 시스템 콜이 되는지! 따라가보겠따.&lt;/p&gt;
&lt;h4&gt;&amp;lt;1&amp;gt; syscall번호를 테이블에 넣어 커널에 빌트인하여 사용&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;커널 공간 코드 작성.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt; // kernel/hello.c
 #include &amp;lt;linux/kernel.h&amp;gt;
 #include &amp;lt;linux/syscalls.h&amp;gt;

 SYSCALL_DEFINE0(hello)
 {
     printk(KERN_INFO &amp;quot;Hello, system call!\n&amp;quot;);
     return 0;
 }&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Makefile에 추가&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/0045c9ff-5288-4473-8236-9d5e7ba1b194/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;linux/arch/arm64/tools/syscall_64.tbl에 번호 추가&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/781cbbb5-a1ab-475e-a2b7-c9f7422ca2da/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;재빌드&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;cd linux
KERNEL=kernel8
make bcm2711_defconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;time make ARCH=arm64 CROSS_COMPILE=&amp;quot;ccache aarch64-linux-gnu-&amp;quot; -j6 Image.gz modules dtbs&lt;/p&gt;
&lt;p&gt;sudo make -j6 modules_install&lt;/p&gt;
&lt;p&gt;sudo cp /boot/firmware/$KERNEL.img /boot/firmware/$KERNEL-backup.img&lt;br&gt;sudo cp arch/arm64/boot/Image.gz /boot/firmware/$KERNEL.img&lt;br&gt;sudo cp arch/arm64/boot/dts/broadcom/&lt;em&gt;.dtb /boot/firmware/&lt;br&gt;sudo cp arch/arm64/boot/dts/overlays/&lt;/em&gt;.dtb* /boot/firmware/overlays/&lt;br&gt;sudo cp arch/arm64/boot/dts/overlays/README /boot/firmware/overlays/&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
5. application (유저 코드) 작성
```c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;sys/syscall.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

// 커널에서 지정한 syscall 번호 정의
#define __NR_hello 463

int main() {
    long ret;
    ret = syscall(__NR_hello);
    printf(&amp;quot;syscall returned: %ld\n&amp;quot;, ret);
    return 0;
}&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;&amp;lt;2&amp;gt; 모듈로 사용할때에 initcall 메커니즘.&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;대부분 모듈 내에서 디바이스 드라이버 코드로 구현이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;비교하여 정리하면, &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;빌트인 → 안정적, 항상 사용 가능, 커널 수정 필요&lt;br&gt;syscall_table[__NR_mycall] = &amp;my_syscall_func;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;모듈 → 유연, 테스트 용이, 런타임 등록/해제 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;재컴파일이 필요없는 커널의 오브젝트가되어 모듈로 올렸다 내렸다 사용하는 방법에 대해 정리에 앞서, 다음 글로 나눠 initcall로 모듈 로드 후 syscall 번호로 어떻게 호출되는지. 알아보겠다.&lt;/p&gt;</description>
      <category>Linux</category>
      <category>디바이스 드라이버</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/26</guid>
      <comments>https://skylarvv.tistory.com/26#entry26comment</comments>
      <pubDate>Sun, 17 Aug 2025 00:08:18 +0900</pubDate>
    </item>
    <item>
      <title>U-Boot 부팅 흐름 과정 요약 2 -Linux kernel 로드</title>
      <link>https://skylarvv.tistory.com/25</link>
      <description>&lt;p&gt;[Linux Foundation](- &lt;a href=&quot;https://youtu.be/INWghYZH3hI?si=8K44yBoVqtcxhiFB&quot;&gt;https://youtu.be/INWghYZH3hI?si=8K44yBoVqtcxhiFB&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;위 U-Boot 부트로더 설명 영상을 보며 정리해보고,&lt;br&gt;코드를 타고타고 따라가보았다.&lt;br&gt;코드는 원래 노션 토글로 간추렸던 것들을 여기에 일단 다 옮겨보았는데, 좀 긴 편이니 아랫 부분에 두었습니다.&lt;br&gt;이글 전에 첫번재 작성한 U-Boot글이 더 쉽게 정리해놓았긴합니다만&lt;/p&gt;
&lt;p&gt;U-Boot 사, Linux Foundation의 영상을 바탕으로도 정리해보았습니다. 부족한 내용정리라, 많은 조언 부탁드립니다~~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/1ad7a2c0-ddff-4a6b-bdb5-5291898f96ef/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;1. Boot ROM&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;전원 인가를 하게 되면 SoC칩 내부에서 실행이 되는 코드(ROM). (EL3 익셉션 레벨3)&lt;/li&gt;
&lt;li&gt;칩 내부의 H/W의 기본 유닛을 초기화 설정.&lt;/li&gt;
&lt;li&gt;Resides on reset vector(전원인가→리셋 벡터→boot ROM)&lt;/li&gt;
&lt;li&gt;BootROM: on-chip boot loader 라고도 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;Power-On Self-Test(POST) 자가 진단 수행 후, 비휘발성 메모리(NVRAM이나 CMOS)에 저장된 부팅 설정을 읽는다.&lt;/li&gt;
&lt;li&gt;부팅 디바이스 탐색 및 선택 후 &lt;strong&gt;1차 Bootloader&lt;/strong&gt;를 로드한다&lt;/li&gt;
&lt;li&gt;(Broadcom BCM2711)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 1st Bootloader&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DRAM 컨트롤러 초기화 전. (가상 메모리X, DRAMX).&lt;/li&gt;
&lt;li&gt;First bootloader는 램에서 실행되는게 아니라 칩 내부에 있는 Static RAM에서 실행되도록 보통 설계된다.&lt;/li&gt;
&lt;li&gt;PMU,GPIO,UART,PMIC(전원 칩),DDR 컨트롤러와 같은 여러 IP를 초기화하는 역할(전원이나 클럭같은 기본적인 설정)&lt;/li&gt;
&lt;li&gt;2차 부트로더 로드(EL3→EL2)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li&gt;bootcode.bin&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;U-Boot SPL&lt;/strong&gt;(secondary program loader) (부트로더로 U-Boot를 쓸때)&lt;ul&gt;
&lt;li&gt;u-boot.bin은 바로 boot ROM이 로드 하기에 커서, SPL으로 대신 로드(SRAM).&lt;/li&gt;
&lt;li&gt;First user-controlled code(Boot ROM이후)&lt;/li&gt;
&lt;li&gt;Responsible for additional HW initialization(DRAM,UART,i2C, PMIC, MMC등 H/W 초기화)&lt;/li&gt;
&lt;li&gt;Loads U-Boot or Kernel directly&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.u-boot.org/en/latest/usage/spl_boot.html&quot;&gt;https://docs.u-boot.org/en/latest/usage/spl_boot.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 2nd Boot loader(User boot loader, U-boot)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;RAM에서 실행 (EL2)&lt;/li&gt;
&lt;li&gt;전체 하드웨어 초기화 board_init_f(),crto.s,board_init_r()&lt;/li&gt;
&lt;li&gt;GPIO, UART, I2C, 타이머 등 주변 장치 초기화, 메모리 재배치,초기화&lt;/li&gt;
&lt;li&gt;SD카드로 부터 mmc를 이용해 디바이스 트리(device tree)와 커널 이미지를 메모리에 로드.&lt;/li&gt;
&lt;li&gt;Rootfs 경로를 bootargs로 커널에 전달.&lt;/li&gt;
&lt;li&gt;하드웨어 구성 정보를 담고 있는 구조체의 포인터 전달.&lt;/li&gt;
&lt;li&gt;커널에 커맨드 라인 포인터를 전달.&lt;/li&gt;
&lt;li&gt;제어권을 커널에게 넘겨주고, 메모리를 반환후 종료.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Linux Kernel&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;커널 이미지 실행되면서 디바이스 트리 참조해서 초기화 수행(EL1)&lt;/li&gt;
&lt;li&gt;startup code (head.S)&lt;/li&gt;
&lt;li&gt;주요 동작&lt;ul&gt;
&lt;li&gt;MMU/캐시 설정&lt;/li&gt;
&lt;li&gt;디바이스 트리 파싱&lt;/li&gt;
&lt;li&gt;메모리/디바이스 초기화&lt;/li&gt;
&lt;li&gt;mnt/root의 ext4 파일시스템을 “/”루트파일시스템로 mount 시도&lt;/li&gt;
&lt;li&gt;→부팅 중 메시지로 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. root FS- User space Process(init)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;kernel_init()으로 PID1인 init 프로세스 실행&lt;/li&gt;
&lt;li&gt;루트 파일시스템에서 /sbin/init, systemd, init.d 스크립트 등 실행&lt;/li&gt;
&lt;li&gt;system call, 쉘 등 사용자 공간 프로세스 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;리셋부터, u-boot 부트로더 동작, init/main.c start_kernel()까지 소스코드&lt;/h3&gt;
&lt;p&gt; start.S ENTRY(_start)부터 start_kernel()까지 (cmd처리 중심)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ENTRY(_start)&lt;/strong&gt;&lt;br&gt;파일 : arch/arm/cpu/armv8/start.S&lt;br&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e0171f0f-d2f0-4b76-9670-5aacfe61a346/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;bl lowlevel_init&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/295cdc0d-c835-4cfd-8adb-d8ae3dc9b5e1/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/1e974f3d-b026-4e6f-b04e-e604d2b1039c/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;board_init_f()까지 코드를 안전하게 실행하기 위한 최소한의 초기화.&lt;br&gt;가능한 아무것도 하지 않는다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  arch/arm/mach-bcm283x/lowlevel\_init.S  
![](https://velog.velcdn.com/images/yez_hong/post/42aff80b-2a3c-4984-86f1-192aefa5cdca/image.png))![](https://velog.velcdn.com/images/yez%5C_hong/post/42aff80b-2a3c-4984-86f1-192aefa5cdca/image.png)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;3.&lt;strong&gt;start.s로 돌아와 bl _main으로 crto.s의 main으로 점프한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/a5d82a27-80ce-4df4-ae92-b9bd8696e5f0/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;4.&lt;strong&gt;crt0.s:arch/arm/lib/crt0_64.s&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-   crt0\_64.s 설명 주석코드&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/83838a60-12a3-45ab-87b2-06319ef1b386/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/2bccf0e9-a12b-4081-91da-9a28cd66af7e/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;아래 보다시피&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e12c4db5-3e95-4ebd-860d-e396a59ad4bf/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e623c180-d144-4b0c-9a9f-709dbd7092cd/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;메모리 영역 재배치, bss영역 초기화 후 board_init_f로 분기한다.&lt;/p&gt;
&lt;p&gt;crt0.s⇒다음 세가지 포함.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;board_init_f&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;하드웨어 초기사용을 위한 함수. 초기 메모리가 부족한 시점에서 실행되어 시스템 메모리(DRAM) 셋업 등 필수 환경을 준비.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;relocate_code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;U-Boot 바이너리를 실행 가능한 위치(DRAM 등)로 복사하고, 내부 주소를 재조정해 코드가 정상 실행되게 함.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;board_init_r&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;부팅 후반부 초기화 함수. 파일 시스템, 네트워크, 콘솔 등 다양한 장치를 초기화하며, 사용자 명령 입력 대기 상태로 전환.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1.&lt;strong&gt;board_init_f()&lt;/strong&gt;&lt;br&gt;u-boot/common/board_f.c&lt;br&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/253eb383-8920-4f2c-92c7-4baa8ef041bf/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;board_init_r()로 넘어가기 전까지 시스템 메모리 초기화(SDRAM) 및 UART 등 기본 I/O준비.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/5936d74f-e45b-4f03-8bed-007176e67d76/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;다시 crto_64.s의 main으로 돌아간 후&lt;/p&gt;
&lt;p&gt;메모리 재배치, bss영역 초기화 등 다음, board_init_r 호출&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/4a08259b-8397-4c93-9bce-0f9d4458a946/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;board_init_r()&lt;/strong&gt;&lt;br&gt;u-boot/common/board_r.c&lt;br&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/f5dcc5c1-fb06-4611-985e-eae40585e342/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/cb63bdb4-c30a-4fe2-a2a0-a744fb425e80/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/5309635b-ccd9-4b4c-b106-5d037e85668a/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;board_init f, r()로 2차 부트로더의 하드웨어, 주변장치 초기화 단계를 수행 후,&lt;/p&gt;
&lt;p&gt;main_loop()→main.c로 진입한다.&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;common/main.c로 진입&lt;/strong&gt;&lt;br&gt; &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/65096974-4b0c-4d31-ab60-515c59ce6dc9/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/bfb15488-1321-4778-a8e6-096805e058d4/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt; &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/9d84d4ac-b2c1-4db8-9725-e1c5e069a517/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;사용자 버튼 처리(예 : GPIO 버튼 누를때 동작)&lt;br&gt;bootdelay처리,&lt;/p&gt;
&lt;p&gt;부팅할 명령어 실행&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cli.c→ cli_loop()→cli_simple_loop호출&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;common/cli_simple.c&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/81da321d-78b2-42e1-8e69-0249db83dbff/image.png&quot; alt=&quot;&quot;&gt;)&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/3c2e7c0b-a92f-4ac8-ad30-45b5e78be544/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;   입력받기 위해 cli_readline 호출&lt;/p&gt;
&lt;p&gt;   common/cli_readline.c&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/f9920c2c-67a0-4c04-883b-d492d2505a5c/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;   cli_readline_into_buffer&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/cbf8651f-5a2e-43d9-b605-e122362bf88b/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;   cread_line_simple&lt;br&gt;    &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/9c5e26c8-cf4e-4439-98c5-8721cfce7d5e/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cread_line_simple 전체코드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;            static int cread_line_simple(const char *const prompt, char *p)
            {
             char *p_buf = p;
             int n = 0; /* buffer index */
             int plen = 0; /* prompt length */
             int col; /* output column cnt */
             int c;

             /* print prompt */
             if (prompt) {
             plen = strlen(prompt);
             puts(prompt);
             }
             col = plen;

             for (;;) {
             if (bootretry_tstc_timeout())
             return -2; /* timed out */
             schedule(); /* Trigger watchdog, if needed */

             c = getchar();

             /*
              * Special character handling
              */
             switch (c) {
             case &amp;#39;\r&amp;#39;: /* Enter */
             case &amp;#39;\n&amp;#39;:
             *p = &amp;#39;\0&amp;#39;;
             puts(&amp;quot;\r\n&amp;quot;);
             return p - p_buf;

             case &amp;#39;\0&amp;#39;: /* nul */
             continue;

             case 0x03: /* ^C - break */
             p_buf[0] = &amp;#39;\0&amp;#39;; /* discard input */
             return -1;

             case 0x15: /* ^U - erase line */
             while (col &amp;gt; plen) {
             puts(erase_seq);
             --col;
             }
             p = p_buf;
             n = 0;
             continue;

             case 0x17: /* ^W - erase word */
             p = delete_char(p_buf, p, &amp;amp;col, &amp;amp;n, plen);
             while ((n &amp;gt; 0) &amp;amp;&amp;amp; (*p != &amp;#39; &amp;#39;))
             p = delete_char(p_buf, p, &amp;amp;col, &amp;amp;n, plen);
             continue;

             case 0x08: /* ^H  - backspace */
             case 0x7F: /* DEL - backspace */
             p = delete_char(p_buf, p, &amp;amp;col, &amp;amp;n, plen);
             continue;

             default:
             /* Must be a normal character then */
             if (n &amp;gt;= CONFIG_SYS_CBSIZE - 2) { /* Buffer full */
             putc(&amp;#39;\a&amp;#39;);
             break;
             }
             if (c == &amp;#39;\t&amp;#39;) { /* expand TABs */
             if (IS_ENABLED(CONFIG_AUTO_COMPLETE)) {
             /*
              * if auto-completion triggered just
              * continue
              */
             *p = &amp;#39;\0&amp;#39;;
             if (cmd_auto_complete(prompt,
                   console_buffer,
                   &amp;amp;n, &amp;amp;col)) {
             p = p_buf + n; /* reset */
             continue;
             }
             }
             puts(tab_seq + (col &amp;amp; 07));
             col += 8 - (col &amp;amp; 07);
             } else {
             char __maybe_unused buf[2];

             /*
              * Echo input using puts() to force an LCD
              * flush if we are using an LCD
              */
             ++col;
             buf[0] = c;
             buf[1] = &amp;#39;\0&amp;#39;;
             puts(buf);
             }
             *p++ = c;
             ++n;
             break;
             }
             }
            }&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;사용자가 U-Boot 프롬프트에 입력한 문자열은 p_buf에 저장됨.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;엔터 치면 cread_line_simple은 &lt;strong&gt;입력한 문자열 길이&lt;/strong&gt;를 리턴하고,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;그 다음 이 문자열은 &lt;strong&gt;명령어 파싱기로 넘어감.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;simple_loop로 돌아와 문자열 길이를 받으면 last_command에 문자열을 복사해 rum_command를 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/8425af73-9632-411e-b033-ca32ad2dccdf/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;run_command_list (cli.c)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;            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_HUSH_PARSER
             /* hush will never change our string */
             need_buff = 0;
            #else
             /* the built-in parser will change our string if it sees \n */
             need_buff = strchr(cmd, &amp;#39;\n&amp;#39;) != NULL;
            #endif
             }
             if (need_buff) {
             buff = malloc(len + 1);
             if (!buff)
             return 1;
             memcpy(buff, cmd, len);
             buff[len] = &amp;#39;\0&amp;#39;;
             }
            #ifdef CONFIG_HUSH_PARSER
             if (use_hush_old()) {
             rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
             } else {
             rcode = parse_string_outer_modern(buff, FLAG_PARSE_SEMICOLON);
             }
            #else
             /*
              * This function will overwrite any \n it sees with a \0, which
              * is why it can&amp;#39;t work with a const char *. Here we are making
              * using of internal knowledge of this function, to avoid always
              * doing a malloc() which is actually required only in a case that
              * is pretty rare.
              */
            #ifdef CONFIG_CMDLINE
             rcode = cli_simple_run_command_list(buff, flag);
            #else
             rcode = board_run_command(buff);
            #endif
            #endif
             if (need_buff)
             free(buff);

             return rcode;
            }&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;cmd_process (command.c)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;            enum command_ret_t cmd_process(int flag, int argc, char *const argv[],
                    int *repeatable, ulong *ticks)
            {
             enum command_ret_t rc = CMD_RET_SUCCESS;
             struct cmd_tbl *cmdtp;

            #if defined(CONFIG_SYS_XTRACE)
             char *xtrace;

             xtrace = env_get(&amp;quot;xtrace&amp;quot;);
             if (xtrace) {
             puts(&amp;quot;+&amp;quot;);
             for (int i = 0; i &amp;lt; argc; i++) {
             puts(&amp;quot; &amp;quot;);
             puts(argv[i]);
             }
             puts(&amp;quot;\n&amp;quot;);
             }
            #endif

             /* Look up command in command table */
             cmdtp = find_cmd(argv[0]);
             if (cmdtp == NULL) {
             printf(&amp;quot;Unknown command &amp;#39;%s&amp;#39; - try &amp;#39;help&amp;#39;\n&amp;quot;, argv[0]);
             return 1;
             }

             /* found - check max args */
             if (argc &amp;gt; cmdtp-&amp;gt;maxargs)
             rc = CMD_RET_USAGE;

            #if defined(CONFIG_CMD_BOOTD)
             /* avoid &amp;quot;bootd&amp;quot; recursion */
             else if (cmdtp-&amp;gt;cmd == do_bootd) {
             if (flag &amp;amp; CMD_FLAG_BOOTD) {
             puts(&amp;quot;&amp;#39;bootd&amp;#39; recursion detected\n&amp;quot;);
             rc = CMD_RET_FAILURE;
             } else {
             flag |= CMD_FLAG_BOOTD;
             }
             }
            #endif

             /* If OK so far, then do the command */
             if (!rc) {
             int newrep;

             if (ticks)
             *ticks = get_timer(0);
             rc = cmd_call(cmdtp, flag, argc, argv, &amp;amp;newrep);
             if (ticks)
             *ticks = get_timer(*ticks);
             *repeatable &amp;amp;= newrep;
             }
             if (rc == CMD_RET_USAGE)
             rc = cmd_usage(cmdtp);
             return rc;
            }&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;/* Look up command in command table */&lt;br&gt;cmdtp = find_cmd(argv[0]);&lt;br&gt;find_cmd()(command.c)&lt;/p&gt;
&lt;p&gt;find_cmd(”boot”)는 cmdtp→cmd_rep(아래 설명)== do_booti_wrapper인 구조체를 찾아 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/92c8bc59-3067-4108-b166-b398fe69d405/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;cmd_call(command.c)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/29dc2925-0802-4f80-a738-d855e4ea46f8/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;cmd_tbl을 통해 cmd_rep()으로 호출 (command.h)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/8af31873-9260-4fd4-af52-80c787448429/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cmd_rep는 struct cmd_tbl 내 명령어 실행 함수 포인터&lt;ul&gt;
&lt;li&gt;명령어 등록 매크로(U_BOOT_CMD 등)가 이 함수 포인터를 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;command.h&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/9b3e476c-2672-4cdb-8c71-c49ae1aed662/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;cmd/booti.c&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/18aabbcf-ae35-456d-b842-7b52bc35cfc8/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  이 매크로는 내부적으로 __u_boot_cmd라는 특수 섹션에 struct cmd_tbl을 등록, 이 구조체의 .cmd_rep 필드는 do_booti(), do_booti_wrapper()포인터가 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;do_booti함수 실행 (cmd/booti.c)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;            int do_booti(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
            {
             struct bootm_info bmi;
             int states;
             int ret;

             /* Consume &amp;#39;booti&amp;#39; */
             argc--; argv++;

             bootm_init(&amp;amp;bmi);
             if (argc)
             bmi.addr_img = argv[0];
             if (argc &amp;gt; 1)
             bmi.conf_ramdisk = argv[1];
             if (argc &amp;gt; 2)
             bmi.conf_fdt = argv[2];
             bmi.boot_progress = true;
             bmi.cmd_name = &amp;quot;booti&amp;quot;;
             /* do not set up argc and argv[] since nothing uses them */

             if (booti_start(&amp;amp;bmi))
             return 1;

             /*
              * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
              * disable interrupts ourselves
              */
             bootm_disable_interrupts();

             images.os.os = IH_OS_LINUX;
             if (IS_ENABLED(CONFIG_RISCV))
             if (IS_ENABLED(CONFIG_64BIT))
             images.os.arch = IH_ARCH_RISCV64;
             else
             images.os.arch = IH_ARCH_RISCV;
             else if (IS_ENABLED(CONFIG_ARM64))
             images.os.arch = IH_ARCH_ARM64;

             states = BOOTM_STATE_MEASURE | BOOTM_STATE_OS_PREP |
             BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO;
             if (IS_ENABLED(CONFIG_SYS_BOOT_RAMDISK_HIGH))
             states |= BOOTM_STATE_RAMDISK;

             ret = bootm_run_states(&amp;amp;bmi, states);

             return ret;
            }&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;커널 entry로 점프&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;- boot_jump_linux (bootm.c) 커널 entry주소로 점프 (보통 Image or vmlinux 시작 주소)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;            int do_bootm_linux(int flag, struct bootm_info *bmi)
            {
             struct bootm_headers *images = bmi-&amp;gt;images;

             /* No need for those on ARM */
             if (flag &amp;amp; BOOTM_STATE_OS_BD_T || flag &amp;amp; BOOTM_STATE_OS_CMDLINE)
             return -1;

             if (flag &amp;amp; BOOTM_STATE_OS_PREP) {
             boot_prep_linux(images);
             return 0;
             }

             if (flag &amp;amp; (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
             boot_jump_linux(images, flag);
             return 0;
             }

             boot_prep_linux(images);
             boot_jump_linux(images, flag);
             return 0;
            }

            #if defined(CONFIG_BOOTM_VXWORKS)
            void boot_prep_vxworks(struct bootm_headers *images)
            {
            #if defined(CONFIG_OF_LIBFDT)
             int off;

             if (images-&amp;gt;ft_addr) {
             off = fdt_path_offset(images-&amp;gt;ft_addr, &amp;quot;/memory&amp;quot;);
             if (off &amp;gt; 0) {
             if (arch_fixup_fdt(images-&amp;gt;ft_addr))
             puts(&amp;quot;## WARNING: fixup memory failed!\n&amp;quot;);
             }
             }
            #endif
             cleanup_before_linux();
            }

            void boot_jump_vxworks(struct bootm_headers *images)
            {
            #if defined(CONFIG_ARM64) &amp;amp;&amp;amp; defined(CONFIG_ARMV8_PSCI)
             armv8_setup_psci();
             smp_kick_all_cpus();
            #endif

             /* ARM VxWorks requires device tree physical address to be passed */
             ((void (*)(void *))images-&amp;gt;ep)(images-&amp;gt;ft_addr);
            }
            #endif&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;            /* Subcommand: GO */
            static void boot_jump_linux(struct bootm_headers *images, int flag)
            {
            #ifdef CONFIG_ARM64
             void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
             void *res2);
             int fake = (flag &amp;amp; BOOTM_STATE_OS_FAKE_GO);

             kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
             void *res2))images-&amp;gt;ep;

             debug(&amp;quot;## Transferring control to Linux (at address %lx)...\n&amp;quot;,
             (ulong) kernel_entry);
             bootstage_mark(BOOTSTAGE_ID_RUN_OS);

             announce_and_cleanup(fake);

             if (!fake) {
             do_nonsec_virt_switch();

             update_os_arch_secondary_cores(images-&amp;gt;os.arch);
            #else
             if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &amp;amp;&amp;amp;
                 (images-&amp;gt;os.arch == IH_ARCH_ARM))
             armv8_switch_to_el2(0, (u64)gd-&amp;gt;bd-&amp;gt;bi_arch_number,
                 (u64)images-&amp;gt;ft_addr, 0,
                 (u64)images-&amp;gt;ep,
                 ES_TO_AARCH32);
             else
             armv8_switch_to_el2((u64)images-&amp;gt;ft_addr, 0, 0, 0,
                 images-&amp;gt;ep,
                 ES_TO_AARCH64);
            #endif
             }
            #else
            #endif
            }&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;arch/arm/cpu/armv8/transition.S&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;            .pushsection .text.armv8_switch_to_el2, &amp;quot;ax&amp;quot;
            ENTRY(armv8_switch_to_el2)
             bl armv8_switch_to_el2_prep
             nop
             switch_el x6, 1f, 0f, 0f
            0:
             cmp x5, #ES_TO_AARCH64
             b.eq 2f
             /*
              * When loading 32-bit kernel, it will jump
              * to secure firmware again, and never return.
              */
             bl armv8_el2_to_aarch32
            2:
             /*
              * x4 is kernel entry point or switch_to_el1
              * if CONFIG_ARMV8_SWITCH_TO_EL1 is defined.
                     * When running in EL2 now, jump to the
              * address saved in x4.
              */
             br x4
            1: armv8_switch_to_el2_m x4, x5, x6
            ENDPROC(armv8_switch_to_el2)
            .popsection&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;starting kernel 메세지 출력 (common/bootm.c) 메세지는 u-boot에서 출력된다.&lt;/p&gt;
&lt;p&gt;(하드웨어관련 메세지도 추가하기)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/3509233d-5579-4b76-8b85-84d928502e20/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;커널 이미지 로딩 후 , head.S 실행. ~/linux/arch/arm64/kernerl/head.S에서 start_kernel()로 진입&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/bb13716a-8ef1-4587-95bb-85ab3e79448d/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;init/main.c :start_kernel() 진입해 초기화&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        [U-Boot]
        cli_loop()
         └─&amp;gt; cli_simple_loop()
             └─&amp;gt; cli_readline()             // 콘솔에서 사용자 명령어 입력 대기 및 읽기
             └─&amp;gt; run_command(&amp;quot;booti 80000 - 90000&amp;quot;)  // 입력받은 명령어 실행 요청
                 └─&amp;gt; run_command_list()     // 명령어가 여러 개일 경우 분리해서 처리
                     └─&amp;gt; cmd_process()      // 명령어 문자열에서 명령어 구조체 검색 및 실행
                         └─&amp;gt; find_cmd(&amp;quot;booti&amp;quot;)   // 명령어 테이블에서 &amp;#39;booti&amp;#39; 명령어 찾음
                             └─&amp;gt; do_booti()       // &amp;#39;booti&amp;#39; 명령어의 실제 실행 함수 호출 (cmd/booti.c)
            → booti_start(&amp;amp;bmi)
            → bootm_disable_interrupts()
            → bootm_run_states()
               └─&amp;gt; BOOTM_STATE_OS_PREP
               └─&amp;gt; BOOTM_STATE_OS_GO
                   └─&amp;gt; arch/arm/lib/bootm.c: do_bootm_linux()
                       └─&amp;gt; kernel 이미지 주소로 분기 → 커널 실행!


        [Linux Kernel]
        arch/arm64/kernel/head.S
         └─&amp;gt; stext
             └─&amp;gt; start_kernel()
                 └─&amp;gt; rest_init()
                     └─&amp;gt; kernel_init()&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e25407c5-19ab-4b7f-96bb-f8e176d593d9/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;⇒ 전원을 켰을 때 main loop 안의 autoboot에 의해 시간 내에&lt;/p&gt;
&lt;p&gt;키보드 입력이 들어오면 u-boot 프롬프트로 진입하고, run bootcmd(디폴트 부팅 커맨드)를 입력하면 부팅하여 start kernel 한다.&lt;/p&gt;
&lt;p&gt;이제 명령어 해석기 (CLI)에서 계속 커맨드 받는 동작!!&lt;/p&gt;
&lt;h3&gt;start_kernel()이후 첫 프로세스 실행까지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;start_kernel()이후 소스코드 동작 (보완하기)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    // Kernel startup entry point
    - 부트로더가 ARM64의 레지스터 x0에 디바이스 트리의 주소를 넣어주고,
     커널이 하드웨어 정보를 읽는다.
     (MMU랑 데이터 캐시 등이 비활성화된 상태여야 함)

    x0 = __pa(KERNEL_START)
    bl start_kernel&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/41dda15f-5539-4229-aab0-f2bcc50296cb/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;init/main.c 의 start_kernel()&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여기서 시스템 전역 데이터 구조 초기화, 스케줄러, 인터럽트, 타이머, 콘솔 등 핵심 서브시스템이 순차적으로 초기화된다.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/fb5dac8e-6db5-4ed0-bccc-beaaaea137dd/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;start_kernel()의 마지막 rest_init()이 호출되고, 하는 역할&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;첫번째 커널 스레드 생성 kernel_init()→ do_initcalls()를 통해 각종 서브시스템, 드라이버, 모듈을 초기화하는 스레드이다.&lt;/p&gt;
&lt;p&gt;  &lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/3ace0316-3207-482d-a982-2b70a10927f4/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;![](https://velog.velcdn.com/images/yez_hong/post/2fd2bf70-d4a2-4f30-93ae-ed3cdad6a9f2/image.png)&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;        static noinline void __ref __noreturn rest_init(void)
        {
         struct task_struct *tsk;
         int pid;

         rcu_scheduler_starting();
         /*
          * We need to spawn init first so that it obtains pid 1, however
          * the init task will end up wanting to create kthreads, which, if
          * we schedule it before we create kthreadd, will OOPS.
          */
          // 유저모드 스레드 생성1
         pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
         // 커널모드 스레드 생성2
         pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
         system_state = SYSTEM_SCHEDULING;
         /* Call into cpu_idle with preempt disabled */
         cpu_startup_entry(CPUHP_ONLINE);
        }&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;-----------------------------------------------------------------&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;        void cpu_startup_entry(enum cpuhp_state state)
        {
         current-&amp;gt;flags |= PF_IDLE;
         arch_cpu_idle_prepare();
         cpuhp_online_idle(state);
         while (1)
         do_idle();
        }&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;kernel_init()내부 주요 흐름&lt;/li&gt;
&lt;li&gt;&lt;code&gt;- 루트 파일 시스템 마운트 시도 - initrd나 initramfs처리 - do_basic_setup()→do_initcalls()호출&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;            static int __ref kernel_init(void *unused)
            {
             kernel_init_freeable();
             -&amp;gt;  do_basic_setup(); -&amp;gt; do_initcalls(); 호출
             =&amp;gt; 유저 모드를 사용하기 전, 
             커널의 주요 기능을 사용하기 위한 초기화 완료&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;do_initcalls()는&lt;/li&gt;
&lt;li&gt;&lt;code&gt;- 커널에 등록된 각종 초기화 함수들을 실행 (드라이버, 서브시스템 등) - __initcall_start ~ __initcall_end 섹션의 함수들을 순차적으로 호출 - 두번째 스레드 : do_idle()로 진입해 스케줄러가 일을 줄 때까지 대기&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;사용자 공간(userspace) 진입&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kernel_init() 함수가 마지막으로 init 프로세스(PID 1)를 실행.&lt;/li&gt;
&lt;li&gt;/sbin/init, 또는 systemd, upstart, busybox init 등이 사용된다.&lt;/li&gt;
&lt;li&gt;이후 사용자 공간에서 서비스, 로그인, 데스크탑 환경 등 다양한 프로세스가 실행됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;static int run_init_process(const char *init_filename)
{
 const char *const *p;

 argv_init[0] = init_filename;
 pr_info(&amp;quot;Run %s as init process\n&amp;quot;, init_filename);
 pr_debug(&amp;quot;  with arguments:\n&amp;quot;);
 for (p = argv_init; *p; p++)
 pr_debug(&amp;quot;    %s\n&amp;quot;, *p);
 pr_debug(&amp;quot;  with environment:\n&amp;quot;);
 for (p = envp_init; *p; p++)
 pr_debug(&amp;quot;    %s\n&amp;quot;, *p);
 return kernel_execve(init_filename, argv_init, envp_init);
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Linux</category>
      <category>Linux Kernel</category>
      <category>U-boot</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/25</guid>
      <comments>https://skylarvv.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 16 Aug 2025 23:34:02 +0900</pubDate>
    </item>
    <item>
      <title>UART3 활성화하여 PC와 시리얼 통신[dtoverlay 사용해보기]</title>
      <link>https://skylarvv.tistory.com/24</link>
      <description>&lt;h4&gt;라즈베리파이에서 UART3 시리얼 통신을 이용해 PC에서 Tera Term으로 문자를 출력됨을 확인해보기(minicom이용).(dtoverlay 작성을 해보는 간단한 연습)&lt;/h4&gt;
&lt;p&gt;예전에 했던 실습인데, 장치를 활성화 할때, menuconfig 설정으로 활성화를 하는 간단한 방법도 있지만,&lt;br&gt;이렇게 기존 dts 방식을 찾아가 수정하거나, 새로운 오버레이를 작성하는 연습을 했던 이유를 프로젝트 끝나서 느꼈다.&lt;br&gt;라즈베리파이 처럼 잘 알려지지 않은 타사의 보드에서 원하는 통신 모듈이나, 장치를 이용하고자 할때 호환이 되거나, 테스트된 정보가 없는 경우가 많다. 이럴때에 아래와 같은 dts 디바이스 트리 노드가 있는지 찾아본 후 , 수정하거나 추가함으로써 포팅을 시도하여 쓸 수 있다.&lt;/p&gt;
&lt;h4&gt;1. uart3 4,5번 핀 → TX,RX&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/d0ddb28e-0249-4554-a79e-9ea24cf3fdc2/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;핀맵&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/93edaa3b-e2c4-4164-ab9c-2b7d37849162/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. /boot/fimware/config.txt 추가&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e15d676d-f989-498c-823d-c898c1d47dc2/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;3. 연결 확인&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/32d0c61b-e142-4809-a8dd-b8ba404a9636/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/95220644-21f3-4aea-b72c-89988d1267f0/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;4. minicom 실행해 연결 확인&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    sudo minicom -b 115200 -D /dev/ttyAMA3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;minicom에서 문자 입력하여 tera term 터미널에 출력 확인&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/2325ba59-0fd3-4dc2-bae1-9c9f9d800f19/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;5. 프로그램 코드 구현&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    #include &amp;lt;stdio.h&amp;gt;
    #include &amp;lt;fcntl.h&amp;gt;
    #include &amp;lt;unistd.h&amp;gt;
    #include &amp;lt;string.h&amp;gt;

    #define BUFFER_SIZE 1024

    int main(int argc, char *argv[]) {
        if (argc &amp;lt; 2) {
            printf(&amp;quot;Usage: %s &amp;lt;string to send&amp;gt;\n&amp;quot;, argv[0]);
            return 1;
        }

        char *message = argv[1];

        // UART3 포트 열기
        int fd = open(&amp;quot;/dev/ttyAMA3&amp;quot;, O_RDWR | O_NOCTTY | O_SYNC);
        // 읽기/쓰기 모드로 열며, 제어 터미널로 사용되지 않도록 설정하고, 쓰기 연산이 완료될 때까지 대기하는 동기식 모드로 열겠
        if (fd &amp;lt; 0) {
            perror(&amp;quot;Failed to open /dev/ttyAMA3&amp;quot;);
            return -1;
        }

        //  (UART3 포트로 보내기)
        ssize_t bytes_written = write(fd, message, strlen(message));
        if (bytes_written &amp;lt; 0) {
            perror(&amp;quot;Failed to write to UART3&amp;quot;);
            close(fd);
            return -1;
        }

        printf(&amp;quot;Written %zd bytes: %s\n&amp;quot;, bytes_written, message);

        // (UART3 포트에서 읽기)
        char buffer[BUFFER_SIZE];  // 버퍼 크기
        ssize_t bytes_read;
        int i = 0;


        while ((bytes_read = read(fd, &amp;amp;buffer[i], 1)) &amp;gt; 0) {

            if (buffer[i] == &amp;#39;\n&amp;#39; || buffer[i] == &amp;#39;\r&amp;#39;) {
                break;
            }
            if(i&amp;lt;BUFFER_SIZE -1){
            i++;}
        }

        if (bytes_read &amp;lt; 0) {
            perror(&amp;quot;Failed to read from UART3&amp;quot;);
            close(fd);
            return -1;
        }

        buffer[i] = &amp;#39;\0&amp;#39;;  
        printf(&amp;quot;Read %d bytes: %s\n&amp;quot;,i+1, buffer);

        close(fd);
        return 0;
    }&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;    gcc -o uart3_test uart3_test.c&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;실행 파일 생성&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/ce46c8f2-ebf7-4d4e-8fb4-6ed71b4cb953/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;6. .dtbs 작성&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/637b6005-1886-4d2f-9d5b-084cf006ff71/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;기존 .dtsi 참고&lt;/p&gt;
&lt;p&gt;bcm2711.dtsi&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/c3f313c8-6fdd-4e08-a456-ccafd267158b/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/01173b80-35a7-4ce0-b3cf-f57b5c8c6939/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;첫번째 시도 (/dev/ttyAMA3 장치파일 안뜸)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;        /dts-v1/;
        /plugin/;

        / {
            compatible = &amp;quot;brcm,bcm2711&amp;quot;;

            // UART3 포트 활성화 및 하드웨어 설정
            uart3: uart@7e201600 {
                compatible = &amp;quot;arm,pl011&amp;quot;;  // UART3에 대한 호환성 설정
                reg = &amp;lt;0x7e201600 0x200&amp;gt;;  // UART3의 레지스터 주소와 크기
                status = &amp;quot;okay&amp;quot;;  // UART3 포트 활성화
            };
        };&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;기존 .dtbs파일의 status 만 활성화하여 사용해보기  &lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;두번째 시도 (시리얼 통신 동작함)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;*인터럽트, 클럭을 지우고 함(기존 파일 참고해 써볼 수는 있을 것 같다)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        /dts-v1/;
        /plugin/;

        / {
            compatible = &amp;quot;bcm2711,pi17&amp;quot;;  // Raspberry Pi 4에 맞는 설정

            // fragment 0: UART3 포트 활성화
            fragment@0 {
                target = &amp;lt;&amp;amp;uart3&amp;gt;;
                __overlay__ {
                    status = &amp;quot;okay&amp;quot;;  // UART3 포트 활성화
                };
            };

            // fragment 1: UART3 하드웨어 설정
            fragment@1 {
                target = &amp;lt;&amp;amp;uart3&amp;gt;;
                __overlay__ {
                    compatible = &amp;quot;arm,pl011&amp;quot;;  // UART3에 대한 호환성 설정
                    reg = &amp;lt;0x7e201600 0x200&amp;gt;;  // UART3의 레지스터 주소와 크기
                //    interrupts = &amp;lt;GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH&amp;gt;;  // 인터럽트 설정
                //    clocks = &amp;lt;&amp;amp;clocks BCM2835_CLOCK_UART&amp;gt;, &amp;lt;&amp;amp;clocks BCM2835_CLOCK_VPU&amp;gt;;  // 클록 설정
                 //   clock-names = &amp;quot;uartclk&amp;quot;, &amp;quot;apb_pclk&amp;quot;;  // 클록 이름 설정
                };
            };
        };&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;7. .dtbo 빌드&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    dtc -@ -I dts -O dtb -o uart3-overlay.dtbo uart3-overlay.dts&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/a27c1649-9381-4bfb-9d02-55848f3ed553/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;인터럽트,클럭 관련 지워보고 시도 &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/0f15b26d-2952-4af2-9d73-b3c5ddf7a2f7/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;warning이 나지만 시도해봄.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/23f71518-e40e-417f-a05f-c1c13669f074/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;8. .dtbo파일 /boot/overlays에 복사&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;    sudo cp uart3-overlay.dtbo /boot/overlays
    sudo nano /boot/firmware/config.txt&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;config.txt에 추가&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/e09de284-fc9f-4fec-b25f-ff0a250944b0/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;9. reboot 후 실행&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;   ls -l /dev/ttyA*로 장치 파일 뜨는지 확인.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;문자열 helloimpi17 쓰기 pc→teraterm 송신한 출력 결과&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;teraterm에서 byebye 읽어 출력.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/724011ef-8cc4-44fa-ba06-21bcaf71335a/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/yez_hong/post/a2111eee-e2c5-49c3-bccf-8f3ab9df88db/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;확인 완료!&lt;/p&gt;</description>
      <category>Linux</category>
      <category>dtoverlay</category>
      <category>디바이스드라이버</category>
      <author>skylarvv</author>
      <guid isPermaLink="true">https://skylarvv.tistory.com/24</guid>
      <comments>https://skylarvv.tistory.com/24#entry24comment</comments>
      <pubDate>Sat, 16 Aug 2025 22:52:46 +0900</pubDate>
    </item>
  </channel>
</rss>