<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Prejudice</title>
    <link>https://prejudice.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 8 Jun 2026 19:54:20 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>편견장</managingEditor>
    <image>
      <title>Prejudice</title>
      <url>https://tistory1.daumcdn.net/tistory/5103524/attach/9b9d0df295c64cbcac050a35d22b4787</url>
      <link>https://prejudice.tistory.com</link>
    </image>
    <item>
      <title>How to Use the Pirate King Hat - Crimson Desert Tips</title>
      <link>https://prejudice.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;약 250시간 동안 플레이하며 엔딩을 보고 가장 유용하게 장비 아이템인 모자 &lt;span class=&quot;inline-em&quot;&gt;해적왕 모자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹여나 모자의 &lt;b&gt;보물탐지 &lt;/b&gt;기능에 대해 모르는 사람들이 있을까 팁을 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*게임에 대한 스포는 없음&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;장비 : 해적왕 모자 - 보물찾기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;붉은 사막의 장비에는 다양한 기능을 하는 모자가 있고 그중 &lt;span class=&quot;inline-em&quot;&gt;장착 시 보물 탐지&lt;/span&gt; 스킬을 가진 모자가 존재한다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEW11X/dJMcagZ1MiR/76qQppHMkv9HLExzO57SM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEW11X/dJMcagZ1MiR/76qQppHMkv9HLExzO57SM0/img.png&quot; data-alt=&quot;해적왕 모자 - 장착 시 보물 탐지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEW11X/dJMcagZ1MiR/76qQppHMkv9HLExzO57SM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEW11X%2FdJMcagZ1MiR%2F76qQppHMkv9HLExzO57SM0%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;925&quot; height=&quot;426&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해적왕 모자 - 장착 시 보물 탐지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션은 액티브 스킬이 아니라, 실제 모자를 착용해서 확인해야 하는 &lt;b&gt;UI 스킬&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보물탐지를 하기 위해서는 &lt;b&gt;해적왕 모자를 꼭 착용하고 모험을 다녀야 효과가 있다&lt;/b&gt;.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선행조건: 투구 외형 표현 옵션&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;붉은사막 모자들은 특히나 멋이 없거나 스토리몰입에 방해가 되는 경우가 많기 때문에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;게임 출시후 붉은 사막 패치 1.02.00 패치 때 아주 빠른 속도로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;투구 외형 표현 &lt;/b&gt;옵션이 추가되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하지만 보물 탐지 스킬을 사용하기 위해선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;투구가 보이는 상태&lt;/b&gt;여야 하기 때문에,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;기타-플레이-투구 외형 표현&lt;/span&gt; 옵션을 비활성화 해야 한다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cynRTM/dJMcaci1Kou/WMsKdW1A8mfQQ6KfVsNkJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cynRTM/dJMcaci1Kou/WMsKdW1A8mfQQ6KfVsNkJ0/img.png&quot; data-alt=&quot;투구 외형 표현 옵션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cynRTM/dJMcaci1Kou/WMsKdW1A8mfQQ6KfVsNkJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcynRTM%2FdJMcaci1Kou%2FWMsKdW1A8mfQQ6KfVsNkJ0%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;1920&quot; height=&quot;1079&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;투구 외형 표현 옵션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&quot;장착 시 보물 탐지&quot; 스킬이란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모자의 외형에 &lt;b&gt;두 가닥의 깃털을 확인할 수 있는데, 평소에는 누런빛의 일반 색을 띄지만,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보물상자가 근처에 있을 때는 &lt;b&gt;밝게 빛나는 방식&lt;/b&gt;으로 유저에게 알린다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJUwA/dJMcafUqo2m/E2CY5POeIkZn6tHXnb4qw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJUwA/dJMcafUqo2m/E2CY5POeIkZn6tHXnb4qw1/img.png&quot; data-alt=&quot;해적왕 모자의 보물 탐지 스킬&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJUwA/dJMcafUqo2m/E2CY5POeIkZn6tHXnb4qw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJUwA%2FdJMcafUqo2m%2FE2CY5POeIkZn6tHXnb4qw1%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;1277&quot; height=&quot;707&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해적왕 모자의 보물 탐지 스킬&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oj1Yb/dJMcagTkeWX/5FyF8xRqf7dfKh95lLrWdk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oj1Yb/dJMcagTkeWX/5FyF8xRqf7dfKh95lLrWdk/img.gif&quot; data-alt=&quot;해적왕 모자의 보물 탐지 스킬 GIF&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oj1Yb/dJMcagTkeWX/5FyF8xRqf7dfKh95lLrWdk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/oj1Yb/dJMcagTkeWX/5FyF8xRqf7dfKh95lLrWdk/img.gif&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;400&quot; height=&quot;510&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해적왕 모자의 보물 탐지 스킬 GIF&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;보물 상자 조건/위치&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보물상자는 특수한 아이템이 들어있는 일반 상자가 아닌 아래 사진과 같은 보물 상자(찾은 후 지도에 표시되는 상자)만 해당된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모자가 빛나면 근처에 보물상자가 있다는 것으로, 주위 20~30걸음 이내에서 &lt;b&gt;보물상자&lt;/b&gt;를 찾을 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;붉은 사막 특성상 &lt;b&gt;비밀공간&lt;/b&gt; 속에 있거나 &lt;b&gt;집중 지정타(모아치기)&lt;/b&gt;가 필요한 경우가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q2qMK/dJMcabRXtsY/CdptplBucmxUqklDLsnBx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q2qMK/dJMcabRXtsY/CdptplBucmxUqklDLsnBx0/img.png&quot; data-alt=&quot;보물상자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q2qMK/dJMcabRXtsY/CdptplBucmxUqklDLsnBx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ2qMK%2FdJMcabRXtsY%2FCdptplBucmxUqklDLsnBx0%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;712&quot; height=&quot;718&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;보물상자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://prejudice.tistory.com/39&quot;&gt;붉은사막 TIP: 2026.03.23 - [게임] - 붉은사막 컨트롤러 키조합 세팅 - 8BitDo 조작감 개선&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780792941106&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;붉은사막 컨트롤러 키조합 세팅 - 8BitDo 조작감 개선&quot; data-og-description=&quot;부정적인 리뷰가 많아 남들에게 말하기 부끄럽지만,이번에 새로나온 펄어비스의 붉은사막(Crimson Desert)을 나름 즐기고 있다. 진짜 불쾌한 경험인 붉은사막의 조작감은 리뷰에서도 말이 많은데...&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/39&quot; data-og-url=&quot;https://prejudice.tistory.com/39&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/839oW/dJMb9c9FyTN/JZYfvTjz7zdgEYo4aSKjHK/img.png?width=800&amp;amp;height=800&amp;amp;face=355_323_421_395,https://scrap.kakaocdn.net/dn/U2PVf/dJMb9eTXf0k/JAOYEn3Lrjk1KFjzkckBU0/img.png?width=800&amp;amp;height=800&amp;amp;face=355_323_421_395,https://scrap.kakaocdn.net/dn/dfgEKm/dJMb9gxs9Tr/EyIgMwLzsC2nm8BjzWN34K/img.png?width=691&amp;amp;height=443&amp;amp;face=0_0_691_443&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/39&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/839oW/dJMb9c9FyTN/JZYfvTjz7zdgEYo4aSKjHK/img.png?width=800&amp;amp;height=800&amp;amp;face=355_323_421_395,https://scrap.kakaocdn.net/dn/U2PVf/dJMb9eTXf0k/JAOYEn3Lrjk1KFjzkckBU0/img.png?width=800&amp;amp;height=800&amp;amp;face=355_323_421_395,https://scrap.kakaocdn.net/dn/dfgEKm/dJMb9gxs9Tr/EyIgMwLzsC2nm8BjzWN34K/img.png?width=691&amp;amp;height=443&amp;amp;face=0_0_691_443');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;붉은사막 컨트롤러 키조합 세팅 - 8BitDo 조작감 개선&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;부정적인 리뷰가 많아 남들에게 말하기 부끄럽지만,이번에 새로나온 펄어비스의 붉은사막(Crimson Desert)을 나름 즐기고 있다. 진짜 불쾌한 경험인 붉은사막의 조작감은 리뷰에서도 말이 많은데...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;</description>
      <category>Game</category>
      <category>crimsondesert</category>
      <category>Detect Treasure</category>
      <category>Pirate King Hat</category>
      <category>게임팁</category>
      <category>보물상자</category>
      <category>보물탐지</category>
      <category>붉은사막</category>
      <category>붉은사막장비</category>
      <category>붉은사막팁</category>
      <category>해적왕모자</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/51</guid>
      <comments>https://prejudice.tistory.com/51#entry51comment</comments>
      <pubDate>Sun, 7 Jun 2026 10:06:35 +0900</pubDate>
    </item>
    <item>
      <title>Windows Jenkins 설치 - 사내 CI/CD 서버 구축 따라하기</title>
      <link>https://prejudice.tistory.com/50</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베디드를 다루는 중소기업이라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD의 필요성은 느끼지만, 인력부족 &amp;middot; 레거시 시스템 유지 등등 각종 핑계로 구축을 하염없이 연기하고 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 현재 다니고 있는 회사에서도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 배포를 위한 최종 인스톨러를 만들고자 할 때는 따로 Linux 가상머신을 이용해 Installer를 제작하거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일할 수 있는 담당자에게 요청하여 업무를 처리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 오는 위험은, 내가 부분적으로 개발한 시스템이 전체 시스템에 어떤 버그를 야기하는지 테스트해 볼 수 없다는 데 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹여나 Installer를 자주 배포하거나, 담당자가 퇴사라도 한다면 야근을 해야할지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사내 CI/CD를 구축해 &lt;b&gt;배포용 프로그램 컴파일 자동화&lt;/b&gt;를 달성하기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 누구나 따라할 수 있게 &lt;b&gt;Windows에 Jenkins를 설치하는 방법을 정리&lt;/b&gt;해 보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Windows Jenkins 설치 방법&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: GungSeo, serif;&quot;&gt;개발자라면 Mac또는 Linux OS, Docker를 좋아할 텐데, 임베디드 개발자라면 마우스 딸깍이 있는 윈도우에서 한다!&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins Installer For Windows 다운로드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Jenkins Server는 거의 모든 플랫폼을 지원하고 있어 설치가 매우 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD 서버로 사용하고자 하는 Windows PC에 설치하고 다음 과정을 진행하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jenkins.io/download/thank-you-downloading-windows-installer/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jenkins.io/download/thank-you-downloading-windows-installer/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780637835482&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Thank you for downloading Windows installer&quot; data-og-description=&quot;Jenkins &amp;ndash; an open source automation server which enables developers around the world to reliably build, test, and deploy their software&quot; data-og-host=&quot;www.jenkins.io&quot; data-og-source-url=&quot;https://www.jenkins.io/download/thank-you-downloading-windows-installer/&quot; data-og-url=&quot;https://www.jenkins.io/download/thank-you-downloading-windows-installer/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCvAH1/dJMb9iIOrQf/SwK3E30TyBeLsV41VvgEXk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/I2IxY/dJMb8ZvI2xQ/m5J5JnDTIdCtKSsjKXbozK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/BbD2U/dJMb9eTW5nI/vdSopDgGVPyQz7KP0KumhK/img.png?width=533&amp;amp;height=275&amp;amp;face=0_0_533_275&quot;&gt;&lt;a href=&quot;https://www.jenkins.io/download/thank-you-downloading-windows-installer/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jenkins.io/download/thank-you-downloading-windows-installer/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCvAH1/dJMb9iIOrQf/SwK3E30TyBeLsV41VvgEXk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/I2IxY/dJMb8ZvI2xQ/m5J5JnDTIdCtKSsjKXbozK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/BbD2U/dJMb9eTW5nI/vdSopDgGVPyQz7KP0KumhK/img.png?width=533&amp;amp;height=275&amp;amp;face=0_0_533_275');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for downloading Windows installer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jenkins &amp;ndash; an open source automation server which enables developers around the world to reliably build, test, and deploy their software&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jenkins.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Management Web Server Port 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Installer를 실행하면 다음과같이 웹서버로 사용할 Port를 설정창으로 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins의 장점은 자동화서버를 웹형식으로 접근하여 사용할 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;span class=&quot;inline-em&quot;&gt;8080&lt;/span&gt; 포트를 사용한다면 &lt;span class=&quot;inline-em&quot;&gt;http://127.0.0.1:8080&lt;/span&gt; 주소로 접속해 자동화 Pipeline을 관리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wROq7/dJMcacDoYms/Dxhmls5XKxMdb2Lmkwvd8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wROq7/dJMcacDoYms/Dxhmls5XKxMdb2Lmkwvd8K/img.png&quot; data-alt=&quot;Jenkins의 관리용 웹 서버 포트 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wROq7/dJMcacDoYms/Dxhmls5XKxMdb2Lmkwvd8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwROq7%2FdJMcacDoYms%2FDxhmls5XKxMdb2Lmkwvd8K%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;495&quot; height=&quot;387&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jenkins의 관리용 웹 서버 포트 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java (JDK or JRE) Home Directory 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 창으로 넘어가면 Java Home Directory 설정창을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins는 내부적으로 Java(21 ver ~ 25 ver)를 사용하기 때문에, 서버가 되는 PC에 JAVA가 설치되어 있어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUBpZa/dJMcafNF0Jy/CFPlunse3l9LthXNnHCpn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUBpZa/dJMcafNF0Jy/CFPlunse3l9LthXNnHCpn1/img.png&quot; data-alt=&quot;Java Home directory 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUBpZa/dJMcafNF0Jy/CFPlunse3l9LthXNnHCpn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUBpZa%2FdJMcafNF0Jy%2FCFPlunse3l9LthXNnHCpn1%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;495&quot; height=&quot;387&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java Home directory 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JAVA를 설치할때는 너무 최신버전인 경우 지원하지 않아서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 링크에서 지원하는 버전과 OS를 맞춰 설치하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.oracle.com/kr/java/technologies/downloads/#java25&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.oracle.com/kr/java/technologies/downloads/#java25&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2573&quot; data-origin-height=&quot;1005&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuTeZ7/dJMcagMzJp3/lJiky33dkNinAfwwwyZz0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuTeZ7/dJMcagMzJp3/lJiky33dkNinAfwwwyZz0K/img.png&quot; data-alt=&quot;JAVA SDK 25.0.3(64-bit) 설치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuTeZ7/dJMcagMzJp3/lJiky33dkNinAfwwwyZz0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuTeZ7%2FdJMcagMzJp3%2FlJiky33dkNinAfwwwyZz0K%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;2573&quot; height=&quot;1005&quot; data-origin-width=&quot;2573&quot; data-origin-height=&quot;1005&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JAVA SDK 25.0.3(64-bit) 설치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JAVA 설치 이후, &lt;span class=&quot;inline-em&quot;&gt;Jenkins Installer&lt;/span&gt; 로 돌아와 홈 디렉터리 경로를 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JAVA를 기본 경로로 설치했다면 C:\Program Files\JAVA 폴더 안에 jdk-[xx.yy.zz] 형식으로 폴더가 있을 텐데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bin 폴더나 lib 폴더가 아닌 해당 &lt;b&gt;폴더&lt;/b&gt; &lt;span class=&quot;inline-em&quot;&gt;jdk-[xx.yy.zz]&lt;/span&gt;&lt;b&gt;를 선택&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0yDJX/dJMcaaeu8Qc/VAw1KzdmE6wuiqDbATejS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0yDJX/dJMcaaeu8Qc/VAw1KzdmE6wuiqDbATejS0/img.png&quot; data-alt=&quot;JAVA Home Directory 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0yDJX/dJMcaaeu8Qc/VAw1KzdmE6wuiqDbATejS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0yDJX%2FdJMcaaeu8Qc%2FVAw1KzdmE6wuiqDbATejS0%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;495&quot; height=&quot;387&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;JAVA Home Directory 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방화벽 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 WebServer다 보니 방화벽(8080 포트)을 열어주는 옵션이 있다. &lt;br /&gt;&lt;span class=&quot;inline-em&quot;&gt;Firewall Exception&lt;/span&gt; 을 체크하면 기본적으로 &lt;b&gt;Windows 방화벽의 인바운드 규칙(Inbound Rules)&lt;/b&gt;에 8080 포트로 접근하는 규칙을 추가해 외부에서 접근할 수 있도록 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;(*뒤에서 따로 방화벽 포트를 설정하는 방법이 있기에 그냥 Next를 눌러 넘어간다.)&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF4ZcM/dJMcafti3oO/gjkF3DTtHlkEnLQh84Mn6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF4ZcM/dJMcafti3oO/gjkF3DTtHlkEnLQh84Mn6K/img.png&quot; data-alt=&quot;설치시 방화벽 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF4ZcM/dJMcafti3oO/gjkF3DTtHlkEnLQh84Mn6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF4ZcM%2FdJMcafti3oO%2FgjkF3DTtHlkEnLQh84Mn6K%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;495&quot; height=&quot;387&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설치시 방화벽 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Jenkins 설정 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jenkins Web Server 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치를 완료했다면 로컬 PC에서 인터넷 브라우저를 통해 웹서버에 접속할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;http://localhost:8080&lt;/span&gt; 주소로 접속하면 다음과 같이 최초 비밀번호를 입력하는 창이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oay1h/dJMcaicyFCP/tgWRaRKaL7dIxwzOsMZdk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oay1h/dJMcaicyFCP/tgWRaRKaL7dIxwzOsMZdk1/img.png&quot; data-alt=&quot;초기 비밀번호 입력 창&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oay1h/dJMcaicyFCP/tgWRaRKaL7dIxwzOsMZdk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Foay1h%2FdJMcaicyFCP%2FtgWRaRKaL7dIxwzOsMZdk1%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;1002&quot; height=&quot;603&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;초기 비밀번호 입력 창&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 비밀번호는 위에 나온 경로인 C:\ProgramData\Jenkins\.jenkins\secrets\initialAdminPasswork 파일에 있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ProgramData폴더는 숨김폴더로 설정돼 있어서 &lt;b&gt;숨김 항목&lt;/b&gt;을 표시해야 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz1dJf/dJMcah5Mu6d/WLEkKSlPlBBr2NhiHTyVIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz1dJf/dJMcah5Mu6d/WLEkKSlPlBBr2NhiHTyVIK/img.png&quot; data-alt=&quot;C:\ProgramData 숨김 항목 표시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz1dJf/dJMcah5Mu6d/WLEkKSlPlBBr2NhiHTyVIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz1dJf%2FdJMcah5Mu6d%2FWLEkKSlPlBBr2NhiHTyVIK%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;1125&quot; height=&quot;634&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;C:\ProgramData 숨김 항목 표시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 비밀번호 등록 후 Jenkins가 정상적으로 켜지는 걸 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2186&quot; data-origin-height=&quot;761&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB4fIc/dJMcahdCf5V/a7rsJCyNlchBkxi85h2cD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB4fIc/dJMcahdCf5V/a7rsJCyNlchBkxi85h2cD1/img.png&quot; data-alt=&quot;Jenkins 설정 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB4fIc/dJMcahdCf5V/a7rsJCyNlchBkxi85h2cD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB4fIc%2FdJMcahdCf5V%2Fa7rsJCyNlchBkxi85h2cD1%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;2186&quot; height=&quot;761&quot; data-origin-width=&quot;2186&quot; data-origin-height=&quot;761&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Jenkins 설정 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방화벽 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Jenkins 설치 시 &lt;span class=&quot;inline-em&quot;&gt;FireWall Exception&lt;/span&gt;을 체크하지 않았다면 다른 외부 PC에서 &lt;span class=&quot;inline-em&quot;&gt;https://[ip]:8080&lt;/span&gt;으로 접속이 아예 안될 텐데, 방화벽에서 8080 포트를 막고 있기 때문일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방화벽 설정은 Windows 키를 누르고 &lt;span class=&quot;inline-em&quot;&gt;고급 보안이 포함된 Windows Defender 방화벽&lt;/span&gt;에서 추가할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFA9wb/dJMcaa6Epsv/vvHH7xCwG4rwOTkM3bqREK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFA9wb/dJMcaa6Epsv/vvHH7xCwG4rwOTkM3bqREK/img.png&quot; data-alt=&quot;Windows 방화벽 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFA9wb/dJMcaa6Epsv/vvHH7xCwG4rwOTkM3bqREK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFA9wb%2FdJMcaa6Epsv%2FvvHH7xCwG4rwOTkM3bqREK%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;613&quot; height=&quot;333&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Windows 방화벽 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 8080 포트로 Jenkins Server로 들어올 수 있도록 허용하기 위해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인바운드 규칙&lt;/b&gt;을&amp;nbsp;&lt;span class=&quot;inline-em&quot;&gt;새 규칙-포트&lt;/span&gt; 로 8080 포트를 추가해 접속을 허용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eqvhPn/dJMcagFOggB/fjXcPfLGnzFPIF44AddcK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eqvhPn/dJMcagFOggB/fjXcPfLGnzFPIF44AddcK0/img.png&quot; data-alt=&quot;방화벽 인바운드 8080포트 규칙 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eqvhPn/dJMcagFOggB/fjXcPfLGnzFPIF44AddcK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeqvhPn%2FdJMcagFOggB%2FfjXcPfLGnzFPIF44AddcK0%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;960&quot; height=&quot;539&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;방화벽 인바운드 8080포트 규칙 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 CI/CD 자동화를 위한 Jenkins(젠킨스) 서버 구축이 끝났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 사내에 24시간 구동되는 서버 PC가 있어 거기에 구축하지만, Local PC에서 컴파일할 때만 사용해도 되고, 시스템별로 서버를 여러 개 두어도 좋을 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Jenkins로 파이프라인을 만들어두면 추후 병렬컴파일이나 AI와 연동한 작업이 훨씬 쉬워지기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금씩이라도 자동화를 구축해 나가며 시스템을 확장시켜 나가는 게 효과적일 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 해야 할 작업은 Jenkins를 이용한 컴파일 자동화인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;형상관리도구(SVN) Update&lt;/span&gt;&amp;rarr; &lt;span class=&quot;inline-em&quot;&gt;Windows Installer 컴파일 &lt;/span&gt;&amp;rarr;&lt;span class=&quot;inline-em&quot;&gt;Linux Installer 컴파일&lt;/span&gt;&amp;rarr;&lt;span class=&quot;inline-em&quot;&gt;AArch64 컴파일&lt;/span&gt;&amp;nbsp;&lt;br /&gt;파이프라인을 만들고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVN이 Checkout 될 때마다 자동으로 실행하도록 하면 쓸만한 기능이 나올 듯하다.&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글: 2026.01.21 - [개발] - CI 입문 ㅡ Jenkins로 C++ 빌드&amp;middot;유닛 테스트 자동화 따라하기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780637713499&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;CI 입문 ㅡ Jenkins로 C++ 빌드&amp;middot;유닛 테스트 자동화 따라하기&quot; data-og-description=&quot;들어가며최근 빌드 환경 구성이 잦아지면서 적지 않은 스트레스를 겪고 있다. 현재 진행 중인 프로젝트는 VirtualBox로 각자 빌드 환경을 구성하고,PowerShell 스크립트를 이용해 Windows, Linux, Embedded Li&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/21&quot; data-og-url=&quot;https://prejudice.tistory.com/21&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/foTIC/dJMb8T97fpa/mvuIbKBMPn2xNXK867ikYK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/6bQ22/dJMb9lleFny/dkurIkpKGWYh6keUF1tKQ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FdcUA/dJMb9jOurrI/bks8g64vEKwJRRkkFHTov0/img.png?width=960&amp;amp;height=674&amp;amp;face=0_0_960_674&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/21&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/21&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/foTIC/dJMb8T97fpa/mvuIbKBMPn2xNXK867ikYK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/6bQ22/dJMb9lleFny/dkurIkpKGWYh6keUF1tKQ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FdcUA/dJMb9jOurrI/bks8g64vEKwJRRkkFHTov0/img.png?width=960&amp;amp;height=674&amp;amp;face=0_0_960_674');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CI 입문 ㅡ Jenkins로 C++ 빌드&amp;middot;유닛 테스트 자동화 따라하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며최근 빌드 환경 구성이 잦아지면서 적지 않은 스트레스를 겪고 있다. 현재 진행 중인 프로젝트는 VirtualBox로 각자 빌드 환경을 구성하고,PowerShell 스크립트를 이용해 Windows, Linux, Embedded Li&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;</description>
      <category>개발</category>
      <category>CI/CD</category>
      <category>devops</category>
      <category>jenkins</category>
      <category>Jenkins설치</category>
      <category>Windows</category>
      <category>빌드자동화</category>
      <category>임베디드</category>
      <category>자동화</category>
      <category>젠킨스</category>
      <category>젠킨스설치</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/50</guid>
      <comments>https://prejudice.tistory.com/50#entry50comment</comments>
      <pubDate>Fri, 5 Jun 2026 17:59:45 +0900</pubDate>
    </item>
    <item>
      <title>비개발자도 쓰는 AI Agent 만들기 - Copilot CLI와 바이브 코딩</title>
      <link>https://prejudice.tistory.com/49</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서 말했듯, 회사에서 사용하는 AI 도구를 GitHub Copilot으로 전환했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요 몇 달간 VS Code Agent Extension, GitHub Copilot CLI, Code review 등등 다양한 도구들을 사용해보고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마침, 내년 결혼준비를 앞두고 신혼집과 결혼식에 대해 부쩍 관심을 가지기 시작했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비개발자인 애인도 함께 사용할 수 있는 맞춤형 AI Agent를 만들어보면 좋을 것 같아서 글을 작성해 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트는 &lt;b&gt;개발 지식이 있는 사람&lt;/b&gt;이라면 따라 할 수 있을 정도로, &lt;b&gt;AI에게 개발을 요청하는 바이브 코딩&lt;/b&gt; 중심으로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 서비스는 &lt;b&gt;비개발자도 사용할 수 있을 정도로 쉽게&lt;/b&gt; &lt;b&gt;만드는 것&lt;/b&gt;을 목표로 만들어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에 들어가기 앞서, 프로젝트를 시작하게된 계기인 Agent-Worker패턴과 Multi-Agent패턴에 대해 알고있으면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://prejudice.tistory.com/47&quot;&gt;AGENT 개념 정리 : 2026.05.11 - [개발] - AI Agent 개념정리 - LLM &amp;middot; Worker-Agent 패턴 &amp;middot; Multi-Agent 패턴&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1779711559914&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;AI Agent 개념정리 - LLM &amp;middot; Worker-Agent 패턴 &amp;middot; Multi-Agent 패턴&quot; data-og-description=&quot;들어가며회사에서 사용하는 AI를 GPT에서 Github Copilot으로 변경하고 약 한 달이 지났다.VsCode, Cursor, Antigravity에 이르기 까지 AI IDE가 빠르게 발전하면서 AI Agent의 설정과 사용이 매우 쉬워졌다. 오늘&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/47&quot; data-og-url=&quot;https://prejudice.tistory.com/47&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c2ydFs/dJMb8WMvUPK/HdKZBctcmkz2Imt2xckp81/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bbDEXI/dJMb9frLFpe/M0KVCIO1fNDKJ94Ll2J6qk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/byZpki/dJMb8UHVu4A/ctbelkMkrAENKT9gKnrm5k/img.png?width=561&amp;amp;height=291&amp;amp;face=0_0_561_291&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/47&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c2ydFs/dJMb8WMvUPK/HdKZBctcmkz2Imt2xckp81/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bbDEXI/dJMb9frLFpe/M0KVCIO1fNDKJ94Ll2J6qk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/byZpki/dJMb8UHVu4A/ctbelkMkrAENKT9gKnrm5k/img.png?width=561&amp;amp;height=291&amp;amp;face=0_0_561_291');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AI Agent 개념정리 - LLM &amp;middot; Worker-Agent 패턴 &amp;middot; Multi-Agent 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며회사에서 사용하는 AI를 GPT에서 Github Copilot으로 변경하고 약 한 달이 지났다.VsCode, Cursor, Antigravity에 이르기 까지 AI IDE가 빠르게 발전하면서 AI Agent의 설정과 사용이 매우 쉬워졌다. 오늘&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;서비스 구상&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아이디어 컨셉&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애인과 나는 둘 다 전세경험도 없고, 세대주 분리도 안되고 부모님 집에 함께 사는 중으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결혼과 부동산에 대한 지식이 정말 백지상태라고 말할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;며칠간 웨딩 관련 용어들을 검색해 보고 알아보며 1년 동안 준비하며 돌파해 나가는 장기간 프로젝트라는 것을 알았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OneNote나 Notion을 이용한 기록 보다, &lt;b&gt;우리만의 결혼정보에 최적화된 Agent&lt;/b&gt;가 있으면 좋을 것 같았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;맞춤정보 (토요일 or 일요일 &amp;middot; 하객 인원수 &amp;middot; 지역 &amp;middot; 우선순위 등)를 데이터로 사용.&lt;/li&gt;
&lt;li&gt;일정과 예산을 정리하고 파일로 저장할 수 있음.&lt;/li&gt;
&lt;li&gt;모르는 용어나 과정을 검색할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;프롬프트를 계속 업데이트해가며 사용해도 되고, 내가 만들려는 서비스의 최종 결과가 Cloud Agent와 비슷한 것 같다.&lt;br /&gt;하지만 이미 GitHub Copilot 계정을 사용 중이고 여자친구도 쉽게 쓸 수 있도록 직접 만들어 보기로 했다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Prototype Service Architecture 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아키텍처를 구상하면서 가장 고민한 부분은 파일 입출력이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 한번 선택해 기록한 파일이 있다면, 다음 질문에선 그 정보를 바탕으로 응답하는 맞춤형 AI를 원했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Copilot CLI를 설치하고 웹 서버와 연결하는 방식을 떠올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 역할은 크게 두 가지로 각각의 Agent를 두어 관리해 보려 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Wedding Planner Agent&lt;/b&gt;: 결혼 준비에 관련된 정보, 일정관리, 준비목록, 체크리스트를 관리하는 역할.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;APT Manager Agent&lt;/b&gt;: 신혼집에 관련된 청약 공고 분석, 일정관리, 자산 및 대출비용 계산 업무를 담당하는 역할.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu1BI0/dJMcadhOIm0/SOUrCay7dp4zYgcTCUyhY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu1BI0/dJMcadhOIm0/SOUrCay7dp4zYgcTCUyhY0/img.png&quot; data-alt=&quot;Wedding Elder 아키텍처&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu1BI0/dJMcadhOIm0/SOUrCay7dp4zYgcTCUyhY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu1BI0%2FdJMcadhOIm0%2FSOUrCay7dp4zYgcTCUyhY0%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;511&quot; height=&quot;251&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Wedding Elder 아키텍처&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 간단해서 아키텍처라 불러도 되나 부끄럽지만, 바이브코딩으로 개발을 진행하며 수정을 해나가려 했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;서비스 개발&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Workspace 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트명이 마땅히 떠오르지 않아 &lt;span class=&quot;inline-em&quot;&gt;WeddingElder&lt;/span&gt; 워크스페이스를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 IDE로 VS Code를 주로 사용하기 때문에 VS Code로 워크스페이스를 열어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AI 바이브 코딩을 위해&lt;/b&gt;&amp;nbsp;&lt;span class=&quot;inline-em&quot;&gt;GitHub Copilot Chat Extension&lt;/span&gt; 을 설치하면 개발 준비가 끝난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpF6MV/dJMcadB1l8K/FbmZ7XmW2s2DfRhxKsPNJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpF6MV/dJMcadB1l8K/FbmZ7XmW2s2DfRhxKsPNJK/img.png&quot; data-alt=&quot;Github Copilot Extension&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpF6MV/dJMcadB1l8K/FbmZ7XmW2s2DfRhxKsPNJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpF6MV%2FdJMcadB1l8K%2FFbmZ7XmW2s2DfRhxKsPNJK%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;350&quot; height=&quot;282&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Copilot Extension&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI Agent 생성&lt;/h3&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; VS Code에서는 AI 대화창을&lt;/span&gt;&amp;nbsp;이용해 쉽게 Agent 프롬프트를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;/create-agent&lt;/span&gt; 명령으로 대략적인 프롬프트를 생성하고, 추후에 나의 상황에 맞게 프롬프트를 수정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 간단하게 &quot;wedding-planner&quot;라는 이름과 역할정도만 주고 생성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ltm28/dJMcabqMVCm/al9enuCjgHe6DKQ1ZdzNL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ltm28/dJMcabqMVCm/al9enuCjgHe6DKQ1ZdzNL1/img.png&quot; data-alt=&quot;Copilot Extension Agent생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ltm28/dJMcabqMVCm/al9enuCjgHe6DKQ1ZdzNL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fltm28%2FdJMcabqMVCm%2Fal9enuCjgHe6DKQ1ZdzNL1%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;687&quot; height=&quot;429&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Copilot Extension Agent생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WeddingElder\.github\agents\ 경로에 &lt;b&gt;wedding-planner.agent.md&lt;/b&gt; 파일이 생성된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 폴더구조는 VS Code의 GitHub Copilot의 전용 규칙으로 특정 Agent를 호출할 때 해당 프롬프트를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 부동산을 관리하는 APT Manager Agent를 추가할 예정이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Agent들이 공통으로 사용하는 &lt;span class=&quot;inline-em&quot;&gt;AGENTS.md&lt;/span&gt; 파일을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;*AGENTS.md 파일 또한 Copilot이 인식하는 파일로, root폴더(WeddingElder) 또는 .github 폴더 등 어디에 두든 상관없다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1779432306686&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; WeddingElder
 ┣   AGENTS.md # Workspace 총괄 가이드
 ┗  .github
    ┗  agents
       ┗  wedding-planner.agent.md # 결혼식 관리 Agent&lt;/code&gt;&lt;/pre&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WeddingElder\AGENTS.md&lt;/p&gt;
&lt;pre id=&quot;code_1779432451102&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 웨딩플래너 워크스페이스 가이드

## 목적
- 이 워크스페이스는 내년 10월 중반 토요일 예식을 목표로 한 결혼 준비를 위한 공간이다.
- 일반적인 소프트웨어 구현보다 일정표, 체크리스트, 예산안, 업체 비교표, 진행 메모, 의사결정 기록 같은 준비 산출물을 우선해서 돕는다.

## 작업 방식
- 추천은 실용적이고, 시점이 분명하며, 바로 실행 항목으로 옮길 수 있게 정리한다.
- 세부 정보가 비어 있으면 목표 예식 시점을 기준으로 잡고, 어떤 가정을 두었는지 명확히 적는다.
- 결과물은 메모, 스프레드시트, 작업 관리 도구에 옮기기 쉬운 구조로 제시한다.

## 선호 출력 형식
- 큰 준비 업무는 월 단위로 나누고, 필요하면 주간 다음 행동까지 이어서 정리한다.
- 업체나 선택지를 비교할 때는 비용, 가능 일정, 적합도, 리스크, 추가 확인 질문 같은 동일한 기준을 사용한다.
- 지역, 예산, 하객 수, 가족 선호에 따라 달라지는 항목은 분명하게 표시한다.

## 문서 언어
- 이 워크스페이스에서 작성하거나 수정하는 Markdown 문서는 기본적으로 한글로 작성한다.
- 외부 서비스명, 브랜드명, 계약 용어처럼 원문 유지가 필요한 표현만 예외로 둔다.

## 에이전트 사용
- 결혼 준비 일정, 예산 정리, 업체 비교 기준, 준비 체크리스트 작성은 전용 웨딩플래너 agent를 우선 사용한다.

## 파일 생성
- 파일을 생성하거나 관리할때는 /files 폴더를 사용한다.
- 엑셀을 만들때나 일회용으로 쓰는 임시파일 같은 경우는 /temp 폴더를 사용한다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WeddingElder\.github\agents\wedding-planner.agent.md&lt;/p&gt;
&lt;pre id=&quot;code_1779432654623&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;---
name: 웨딩쀼
description: &quot;결혼 준비를 계획하거나, 일정표를 만들거나, 예산을 정리하거나, 웨딩홀 및 업체를 비교하거나, 체크리스트를 구성하거나, 27년 10월 중반 토요일 예식을 목표로 준비할 때 사용한다.&quot;
tools: [read, edit, search, execute, todo, web]
user-invocable: true
---
이 워크스페이스 전용 웨딩 준비 전문 agent다.

역할은 사용자가 27년 10월 중반 토요일 예식을 목표로 결혼을 준비할 수 있도록, 현실적인 계획과 바로 실행 가능한 정리를 제공하는 것이다.

## 핵심 역할
- 현실적인 결혼 준비 타임라인을 만들고 유지한다.
- 막연한 목표를 구체적인 다음 행동으로 바꾼다.
- 예산, 예식장, 협력업체, 하객 수, 복장, 식순, 행정 준비를 구조적으로 정리한다.
- 선택지 비교 시 기준과 트레이드오프를 명확하게 드러낸다.

## 기본 가정
- 정확한 날짜가 아직 확정되지 않았다면, 27년 10월 중반 토요일을 목표 날짜로 가정한다.
- 지역, 예산, 하객 수는 사용자가 지정하기 전까지 미정 변수로 둔다.
- 워크스페이스 문맥이 한국어이므로, 사용자가 따로 말하지 않으면 한국 기준의 결혼 준비 관행을 우선 반영한다.

## 제약
- 실시간 가격, 실시간 예약 가능 여부, 법률 자문을 알고 있는 것처럼 답하지 않는다.
- 확정되지 않은 예약, 계약, 마감일을 사실처럼 만들지 않는다.
- 사용자가 결정이나 계획을 원할 때 막연한 아이디어 나열로 끝내지 않는다.

## 진행 방식
1. 무엇을 결정해야 하는지, 얼마나 급한지, 어떤 조건이 비어 있는지 먼저 파악한다.
2. 답변에 큰 영향을 주는 가정이 있으면 먼저 밝힌다.
3. 사용자가 파일, 표, 템플릿, 비교표, 체크리스트를 원하면 워크스페이스에서 직접 만들거나 수정한다.
4. 필요하면 마지막에 가장 작은 다음 행동이나 확인 질문을 덧붙인다.

## 출력 선호
- 일정표는 월별 또는 의사결정 단계별로 묶는다.
- 예산안은 항목, 예상 범위, 비용 리스크 메모를 함께 적는다.
- 비교 요청은 기준, 장점, 단점, 추가 확인 질문이 있는 표 형태를 우선한다.
- 체크리스트는 필수 항목과 선택 업그레이드를 구분한다.
- Markdown으로 답할 때는 기본적으로 한글로 작성한다.

## 기본 계획 영역
- 예식장과 날짜 선택
- 예산 배분과 우선순위 조정
- 스튜디오, 드레스, 메이크업, 촬영 준비
- 하객 명단과 청첩장 시점 조율
- 예식 및 피로연 진행 순서
- 신혼여행 일정 의존성 정리
- 행정 및 계약 관련 준비

## 성공 기준
모든 답변은 사용자의 불확실성을 줄이거나, 우선순위를 분명히 하거나, 27년 10월 중반 예식을 향한 구체적인 다음 단계 하나 이상을 제공해야 한다.

도구가 허용된 경우에는 필요한 검색, 파일 편집, 문서 생성, 표 정리, 셸 실행을 직접 수행해 결과물을 남긴다.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Copilot-CLI 설치와 WebServer 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC에 원격으로 접근해서 VS Code의 Copilot 대화창을 이용해 AI를 사용해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사용자가 쉽게 접근할 수 있도록 &lt;b&gt;WebServer로 대화창을 제공&lt;/b&gt;해 보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 VS Code IDE 없이도 AI를 호출할 수 있도록 &lt;span class=&quot;inline-em&quot;&gt;Copilot CLI&lt;/span&gt; 를 설치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Copilot CLI는 GitHub 홈페이지에서 설치할 수 있으며, 정상적으로 설치 후 CMD 창에서 &lt;span class=&quot;inline-em&quot;&gt;Copilot&lt;/span&gt; 명령으로 실행할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bid2bO/dJMcadWpTOQ/35tQTlFtOG7qoNzRrVckVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bid2bO/dJMcadWpTOQ/35tQTlFtOG7qoNzRrVckVk/img.png&quot; data-alt=&quot;winget을 통한 GitHub Copilot CLI 설치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bid2bO/dJMcadWpTOQ/35tQTlFtOG7qoNzRrVckVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbid2bO%2FdJMcadWpTOQ%2F35tQTlFtOG7qoNzRrVckVk%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;642&quot; height=&quot;405&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;542&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;winget을 통한 GitHub Copilot CLI 설치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du0MO9/dJMcahLipZh/O0FkltKSYDaLL1ItOn9TN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du0MO9/dJMcahLipZh/O0FkltKSYDaLL1ItOn9TN1/img.png&quot; data-alt=&quot;Windows CMD 에서 실행한 Copilot CLI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du0MO9/dJMcahLipZh/O0FkltKSYDaLL1ItOn9TN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu0MO9%2FdJMcahLipZh%2FO0FkltKSYDaLL1ItOn9TN1%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;644&quot; height=&quot;363&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Windows CMD 에서 실행한 Copilot CLI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 VS Code IDE로 돌아가서 이 명령을 수행할 수 있도록 연결하는 웹서버를 만들어 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 명령 후, 몇 가지 체크를 하고 권한을 주면 뚝딱뚝딱 만들기 시작한다.&lt;/p&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;▶ webserver폴더에 python으로 웹 서버 프로그램을 만들자.&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;웹 서버의 주요 기능은 다음과 같아.&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;&amp;nbsp;- 사용자가 웹 페이지에 접속해서 Local PC의 Command창과 연결 후 Copilot CLI를 사용.&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;&amp;nbsp;- 사용자가 웹 페이지를 통해 파일들을 다운받거나 삭제할 수 있도록 제공.&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;&amp;nbsp;- Copilot CLI는 현재 폴더를 기준으로 워크스페이스를 제공.&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;&amp;nbsp;- Copilot CLI는 파일생성/수정을 할 수 있고 임시폴더로 temp폴더를, 생성된 파일은 files폴더를 사용해 관리.&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&amp;gt; Asking a question ...&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4IQhh/dJMcajh0ABy/i95wSJZNcH0W1gcFKmtaVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4IQhh/dJMcajh0ABy/i95wSJZNcH0W1gcFKmtaVK/img.png&quot; data-alt=&quot;몇가지 질문을 통해 WebServer 만드는 Copilot&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4IQhh/dJMcajh0ABy/i95wSJZNcH0W1gcFKmtaVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4IQhh%2FdJMcajh0ABy%2Fi95wSJZNcH0W1gcFKmtaVK%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;306&quot; height=&quot;408&quot; data-origin-width=&quot;306&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;몇가지 질문을 통해 WebServer 만드는 Copilot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 webserver 폴더에 만들라고 지정했기 때문에 폴더가 지저분해지는 것을 피하고, 나름 깔끔하게 구조를 유지할 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1779425478686&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; WeddingElder
 ┣   AGENTS.md # Workspace 총괄 가이드
 ┣  .github
 ┃  ┗  agents
 ┃    ┗  wedding-planner.agent.md # 결혼식 관리 Agent
 ┣  webserver # python으로 개발된 웹 서버
 ┃  ┗  [webserver Files]
 ┣  temp # 임시 생성될 파일 경로
 ┗  files # 파일저장 경로&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;나의 경우 PC에 Python을 설치해 두지 않았다.&lt;br /&gt;때문에 AI가 파이썬 가상환경인 venv를 설치했고, Workspace에도 따로 적진 않았지만 .venv폴더를 만들어졌다.&lt;/blockquote&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webserver 폴더에 들어가 python으로 웹서버 프로그램을 실행하고, &lt;b&gt;인터넷으로 접속해 Copilot CLI에 접근할 수 있었다&lt;/b&gt;.&lt;br /&gt;&lt;span class=&quot;inline-em&quot;&gt;(.venv) PS C:\..\WeddingElder\webserver&amp;gt; python.exe .\app.py&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ITLY/dJMcaiXFVpM/2BydDfyYXWBLB5RCwr6XPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ITLY/dJMcaiXFVpM/2BydDfyYXWBLB5RCwr6XPK/img.png&quot; data-alt=&quot;Web으로 접속한 Copilot-CLI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ITLY/dJMcaiXFVpM/2BydDfyYXWBLB5RCwr6XPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ITLY%2FdJMcaiXFVpM%2F2BydDfyYXWBLB5RCwr6XPK%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;1053&quot; height=&quot;657&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Web으로 접속한 Copilot-CLI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Agent Tool 설치 - Excel 파일 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent의 강력한 기능은 명령 수행 시 사용할 수 있는 도구를 함께 전송해서 명령을 수행할 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Workspace에서 Copilot에게 &lt;b&gt;엑셀파일을 만들 수 있도록 도구를 설치&lt;/b&gt;하라고 명령했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python가상환경에 Excel파일을 만드는 라이브러리인 openpyxl을 설치했고,&lt;br /&gt;&lt;span class=&quot;inline-em&quot;&gt;wedding-planner.agent.md&lt;/span&gt; 파일에 &lt;b&gt;tools를 추가&lt;/b&gt;했다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음, Copilot에게 엑셀 파일로 정리해 달라고 하면 다음과 같은 과정을 통해 파일을 생성한다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;웹소켓으로 서버로 전송&lt;/span&gt;&amp;rarr;&lt;span class=&quot;inline-em&quot;&gt;Coiplot CLI(AGENTS.md)&lt;br /&gt;&lt;/span&gt;&amp;rarr;&lt;span class=&quot;inline-em&quot;&gt;Excel 생성용 python코드 작성&lt;/span&gt;&amp;rarr;&lt;span class=&quot;inline-em&quot;&gt;python실행(.xlsx)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;▶ 신도림 웨딩홀들을 찾고 예약일이 보통 언제쯤 열리는지 엑셀파일로 정리해&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&amp;gt; 엑셀 파일 생성 완료!&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmSv2a/dJMcadPCeEj/9KKHKswhIokd6RGtkuDR10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmSv2a/dJMcadPCeEj/9KKHKswhIokd6RGtkuDR10/img.png&quot; data-alt=&quot;Agent의 Excel파일 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmSv2a/dJMcadPCeEj/9KKHKswhIokd6RGtkuDR10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmSv2a%2FdJMcadPCeEj%2F9KKHKswhIokd6RGtkuDR10%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;1016&quot; height=&quot;586&quot; data-origin-width=&quot;1016&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Agent의 Excel파일 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계획대로라면 임시파일은 \temp폴더를 사용하고 최종 결과 파일은 \files 폴더를 사용해야 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어째서인지 AGENT가 WeddingElder\temp 폴더에 만든 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: GungSeo, serif;&quot;&gt;*하지만 Excel 파일 생성은 확인할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b076bD/dJMcacpIJMX/KGX0K8OqGfjQKkZmV76po1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b076bD/dJMcacpIJMX/KGX0K8OqGfjQKkZmV76po1/img.png&quot; data-alt=&quot;생성된 .xlsx 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b076bD/dJMcacpIJMX/KGX0K8OqGfjQKkZmV76po1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb076bD%2FdJMcacpIJMX%2FKGX0K8OqGfjQKkZmV76po1%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;1862&quot; height=&quot;164&quot; data-origin-width=&quot;1862&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생성된 .xlsx 파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제는&amp;nbsp;파일을 관리하는 Agent를 따로 만들어서 복사를 하거나, AGENTS.md파일을 수정하여 해결할 수 있을 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 한 줄도 보지 않고 개발을 한건 처음이었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent를 커스터마이징하고 도구를 연결하는 모든 과정을 자연어로 개발하는 과정 자체에서 재미를 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그에반해 결과물 측면에서는 Copilot CLI의 작업 속도가 느렸고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹세션을 이용한 UI 부분도 기대했던 것에 비해 밤티나서 아쉬움이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 이번 프로토타입 프로젝트에서는 Copilot CLI를 오케스트레이터처럼 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 LLM Model을 코드를 잘 작성하는 Calude Sonnet 4.6으로 사용했는데, Model선정에 있어서도 성능차이가 클 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 구조개선과 UI개선을 통해 프로젝트 완성도를 높일 수 있었으면 좋겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서비스 완성도를 높이기 위해 시도해 볼 만한 것들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Orchestrator-Worker 구조&lt;/b&gt;로 작업 분리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파일관리 Agent 추가&lt;/b&gt; : 폴더구조 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;APT Manager Agent 추가&lt;/b&gt; : 신혼집(부동산) 관련 Agent&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MD 파일 수정&lt;/b&gt; &lt;b&gt;기능&lt;/b&gt;: Agent를 웹에서 커스터마이징 가능하도록 UI 제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&lt;/b&gt; : 인증/접근제어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;온라인 개시&lt;/b&gt; : 도메인 연결 (TOOL: ngrok, pyngrok)&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 코드 공개 (GitHub)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/BlueBird-0/WeddingElder&quot;&gt;https://github.com/BlueBird-0/WeddingElder&lt;/a&gt; &lt;/p&gt;
&lt;figure id=&quot;og_1779711596168&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - BlueBird-0/WeddingElder: AI Agent Project for management of wedding.&quot; data-og-description=&quot;AI Agent Project for management of wedding. Contribute to BlueBird-0/WeddingElder development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/BlueBird-0/WeddingElder&quot; data-og-url=&quot;https://github.com/BlueBird-0/WeddingElder&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMFYHe/dJMb8Qesr2e/rRQFNpjSigBOmAMiMggKn1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/biLcz8/dJMb9kmiTwu/qjS6lET3MKGSvL4annYO60/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/BlueBird-0/WeddingElder&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/BlueBird-0/WeddingElder&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMFYHe/dJMb8Qesr2e/rRQFNpjSigBOmAMiMggKn1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/biLcz8/dJMb9kmiTwu/qjS6lET3MKGSvL4annYO60/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - BlueBird-0/WeddingElder: AI Agent Project for management of wedding.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AI Agent Project for management of wedding. Contribute to BlueBird-0/WeddingElder development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발</category>
      <category>AI Agent</category>
      <category>ai 커스터마이징</category>
      <category>Copilot CLI</category>
      <category>GitHub Copilot</category>
      <category>multiagent</category>
      <category>VS Code</category>
      <category>개인화ai</category>
      <category>바이브코딩</category>
      <category>비개발자AI</category>
      <category>웨딩플래너</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/49</guid>
      <comments>https://prejudice.tistory.com/49#entry49comment</comments>
      <pubDate>Mon, 25 May 2026 21:33:30 +0900</pubDate>
    </item>
    <item>
      <title>AI Agent 개념정리 - LLM &amp;middot; Worker-Agent 패턴 &amp;middot; Multi-Agent 패턴</title>
      <link>https://prejudice.tistory.com/47</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 사용하는 AI를 GPT에서 Github Copilot으로 변경하고 약 한 달이 지났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VsCode, Cursor, Antigravity에 이르기 까지 AI IDE가 빠르게 발전하면서 AI Agent의 설정과 사용이 매우 쉬워졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 &lt;b&gt;AI Agent가 Local PC에서 행동할 수 있는 원리&lt;/b&gt;와, &lt;b&gt;Multi-Agent의 장단점&lt;/b&gt;에 대해 정리해보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;LLM과 Agent의 차이점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LLM (Large Language Model)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM(Large Language Model)이란 텍스트를 입력받아 텍스트를 반환하는 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 작성한 방대한 글을 학습해, 입력받은 텍스트 다음에 나올 내용을 추론하는 &lt;b&gt;생성형 AI&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 LLM으로는 &lt;span class=&quot;inline-em&quot;&gt;GPT-4o(OpenAI)&lt;/span&gt;, &lt;span class=&quot;inline-em&quot;&gt;Gemini(Google)&lt;/span&gt;,&lt;span class=&quot;inline-em&quot;&gt;Claude(Anthropic)&lt;/span&gt; 등이 있으며,&lt;br /&gt;각각 학습된 데이터를 바탕으로 추론하기 때문에 사람에 비유하면 &lt;b&gt;&quot;뇌&quot;&lt;/b&gt;에 비유할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점으로는 &lt;b&gt;사실이 아닌 내용을 사실인 듯 생성&lt;/b&gt;하는 환각(Hallucination) 현상과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM을 만들기 위해 사용하는 방대한 데이터 처리를 위해서 &lt;b&gt;거대자본이 투입되어야 한다는 한계&lt;/b&gt;를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 짚고 넘어갈 점은, &lt;b&gt;LLM은 텍스트를 반환하는 모델일 뿐 직접적인 행동을 할 수 없다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[사용자] &quot;로그인 함수 짜줘&quot;&lt;br /&gt;&amp;nbsp; &amp;darr;&lt;br /&gt;[LLM] 코드 텍스트 생성&lt;br /&gt;&amp;nbsp; &amp;darr;&lt;br /&gt;[사용자] (직접 복사해서 편집기에 붙여 넣기)&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI Agent&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI Agent는 LLM에 &lt;b&gt;실행 능력을 더한 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 CMD창을 조작하거나 내 PC의 조작이 가능한 LLM&lt;b&gt;(=Agent)&lt;/b&gt;에게 요청한다고 상상해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 일련의 과정을 직접 수행할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[사용자] &quot;로그인 함수 짜줘&quot;&lt;br /&gt;&amp;nbsp; &amp;darr;&lt;br /&gt;[Agent] 코드 생성 &amp;rarr; 파일 저장 &amp;rarr; 빌드 &amp;rarr; 결과 확인&lt;br /&gt;&amp;nbsp; &amp;darr;&lt;br /&gt;[사용자] 결과 확인&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Agent가 행동하는 원리 - Tool Calling / Context Injection&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tool Calling&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 텍스트만 다룰 수 있기 때문에, Agent가 파일을 만들거나 터미널 명령을 실행하려면 &lt;b&gt;LLM 혼자서는 불가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하는 것이 &lt;b&gt;Tool Calling(도구 호출)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent가 도구들을 사용하기 위해서는 &lt;b&gt;도구목록과 사용방법들을 미리 알고 있어야 하는데&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 작성한 MCP(Model Context Protocol) 글에서 다룬 적 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.02.26 - [개발] - AI와 공장 자동화(FA)연동 따라하기 - PLC 예제로 구현한 MCP&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778480672773&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;AI와 공장 자동화(FA)연동 따라하기 - PLC 예제로 구현한 MCP&quot; data-og-description=&quot;들어가며최근 AI가 빠르게 발달하면서 FA(Factory Automation) 업계에서도 AI의 적극적인 도입을 검토하는 움직임이 있다. 현장에서는 &amp;quot;우리 공장 설비(서비스)를 AI와 연동할 수 있을까?&amp;quot;에 대한 수요가&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/34&quot; data-og-url=&quot;https://prejudice.tistory.com/34&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fH5BO/dJMb9bv6MMm/cmBRdzra6ep29FSXhkCMjK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/pdoVk/dJMb9g5fDOA/LxMtmCPJn3jgNQmX7xxX1k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/p3QIG/dJMb9iaVFOu/KZUvfAhHomkgE0Kg6ofms1/img.png?width=1033&amp;amp;height=537&amp;amp;face=0_0_1033_537&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/34&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fH5BO/dJMb9bv6MMm/cmBRdzra6ep29FSXhkCMjK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/pdoVk/dJMb9g5fDOA/LxMtmCPJn3jgNQmX7xxX1k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/p3QIG/dJMb9iaVFOu/KZUvfAhHomkgE0Kg6ofms1/img.png?width=1033&amp;amp;height=537&amp;amp;face=0_0_1033_537');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AI와 공장 자동화(FA)연동 따라하기 - PLC 예제로 구현한 MCP&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며최근 AI가 빠르게 발달하면서 FA(Factory Automation) 업계에서도 AI의 적극적인 도입을 검토하는 움직임이 있다. 현장에서는 &quot;우리 공장 설비(서비스)를 AI와 연동할 수 있을까?&quot;에 대한 수요가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Agent의 Tool Calling 핵심은 LLM이 &lt;b&gt;도구에 맞는 명령을 생성&lt;/b&gt;하면, &lt;b&gt;Agent 런타임이 실행하는&lt;/b&gt; &lt;b&gt; 구조&lt;/b&gt;라 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D0Ixz/dJMcadu8Pjj/gXJcGNQ7vNIaDRKK6iLgk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D0Ixz/dJMcadu8Pjj/gXJcGNQ7vNIaDRKK6iLgk0/img.png&quot; data-alt=&quot;그림1. Agent&amp;amp;harr;LLM 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D0Ixz/dJMcadu8Pjj/gXJcGNQ7vNIaDRKK6iLgk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD0Ixz%2FdJMcadu8Pjj%2FgXJcGNQ7vNIaDRKK6iLgk0%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;631&quot; height=&quot;251&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. Agent&amp;harr;LLM 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context Injection&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;그림1&lt;/span&gt; 에서 Agent가 LLM을 호출할 때 &lt;b&gt;현재 환경 정보를 프롬프트에 함께 담아 전달&lt;/b&gt;하는 것을 &lt;b&gt;Context Injection&lt;/b&gt;이라고 한다.&lt;br /&gt;예를 들어 PC에 설치된 컴파일러, 프로젝트 환경설정, 사용 가능한 도구들, 수행계획 등등 다양한 정보를 함께 전달하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 이 정보를 보고 판단하여 &lt;b&gt;환경에 맞는 명령을 생성&lt;/b&gt;한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Monolithic 프로젝트나 관련 리소스가 많은 경우, 모든 정보를 담아 LLM에 요청하는 일이 발생할 수 있다.&lt;br /&gt;이는 응답이 너무 길어져 처리 중 중단되거나 토큰 사용량이 많이 발생하는 원인이 된다.&lt;/blockquote&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;AI 우회 전략 - Worker Agent 패턴&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최근에 회사에서 사용하는 AI를 Github Copilot으로 바꿨는데, &lt;b&gt;Github Copilot Business 계정&lt;/b&gt;을 사용한다면 다음 제약이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;VSCode Extention이나 Copilot CLI는 연결된 GitHub 계정으로 로그인하여 사용할 수 있지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사내 API Key는 보안이슈로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;직접 만든 프로그램에서 Copilot을 호출하는 것은 정책상 불가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VSCode Extension&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copilot CLI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Github API Token (직접 호출)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Business 계정 정책으로 불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 제약을 우회할 수 있는 방법이&amp;nbsp;&lt;b&gt;Worker Agent 패턴&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;그림1&lt;/span&gt; 에서 Agent가 실행하기 위한 핵심은 LLM에게 환경을 전달하는 것을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;Context Injection을 대신 수행하는 중간 Agent&lt;/b&gt;를 두면 직접 API 호출 없이 우회가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내 프로그램이 Copilot에게 직접 요청하는 대신,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CLI를 대신 실행해 주는 중간 프로세스(Worker Agent)를 통하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ONJvT/dJMcaf7BPLT/orDIewgBHQO9mqVG28gIz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ONJvT/dJMcaf7BPLT/orDIewgBHQO9mqVG28gIz1/img.png&quot; data-alt=&quot;Worker Agent Pattern&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ONJvT/dJMcaf7BPLT/orDIewgBHQO9mqVG28gIz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FONJvT%2FdJMcaf7BPLT%2ForDIewgBHQO9mqVG28gIz1%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;630&quot; height=&quot;251&quot; data-origin-width=&quot;630&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Worker Agent Pattern&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Worker Agent가 단순히 CLI 한 번 호출하고 끝이라면 User가 직접 Copilot CLI를 실행해도 동일하다.&lt;br /&gt;Worker Agent가 진가를 발휘하는 것은 &lt;b&gt;Tool 실행 루프가 필요한 경우&lt;/b&gt;, &lt;br /&gt;즉 LLM이 &quot;파일 읽기&amp;rarr;분석&amp;rarr;빌드&amp;rarr;테스트&amp;rarr;파일 수정&quot;처럼 여러 번 왔다 갔다 해야 할 때 이다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Multi Agent - Orchestrator 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Single Agent의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Agent에 설계, 구현, 테스트, 리뷰를 전부 하도록 구성하면 다음과 같은 문제가 발생한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 과부하:&lt;/b&gt; 토큰 수가 너무 길어져 앞의 내용을 잊거나 생성 품질이 떨어진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역할 혼재:&amp;nbsp;&lt;/b&gt;서로 특성이 맞지 않는 작업을 동시에 진행하면 역효과가 난다. (ex. 소설 쓰기/정보검색을 동시에 진행하는 Agent)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재사용 불가: &lt;/b&gt;특정 프로젝트에 오버피팅된 Agent는 다른 곳에 쓰기 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Multi Agent의 장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Agent를 역할별로 분리하고, &lt;b&gt;전체 흐름을 관리하는 Orchestrator Agent를 두는 Multi-Agent 방식&lt;/b&gt;이 등장했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;전문화:&amp;nbsp;&lt;/b&gt;각각의 Agent가 자신의 역할에 집중, 품질상승.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 절약: &lt;/b&gt;역할에 필요한 정보만 가지므로 토큰 낭비를 줄일 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재사용성: &lt;/b&gt;Project의존성을 제거할 수 있어 재사용이 가능함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;병렬처리: &lt;/b&gt;독립된 Agent끼리 동시에 작업 가능.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수:&lt;/b&gt; 문제 발생 시 어느 Agent에서 발생했는지 특정하기 쉬움.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZdUSC/dJMcaiiYnF7/EkldfSLQ5Ges95kStwnSCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZdUSC/dJMcaiiYnF7/EkldfSLQ5Ges95kStwnSCk/img.png&quot; data-alt=&quot;Multi-Agent 구조 (Orchestrator Pattern)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZdUSC/dJMcaiiYnF7/EkldfSLQ5Ges95kStwnSCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZdUSC%2FdJMcaiiYnF7%2FEkldfSLQ5Ges95kStwnSCk%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;561&quot; height=&quot;291&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Multi-Agent 구조 (Orchestrator Pattern)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 항시 느끼는 것은 AI발전은 너무 빠르고 성능평가가 이루어지지 않은 채 AI를 사용하면서,&amp;nbsp;공포감을 느낀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생산성을 위해 AI를 필수로 사용해야 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 빠르게 만들어주는 주면서 지식이 단기기억으로 들어가는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그나마 이번에 시간을 내 Agent에 대한 개념과 동작원리를 정리할 수 있었고, 다음번엔 Worker Agent를 직접 만들어&lt;br /&gt;Github Copilot과 연동해 실행하는 프로그램을 만들어 봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;PS.&lt;/span&gt; LLM에 대해 학습하며 VLM(Vision Language Model), VLA(Vision-Language-Action) 모델이라는 것을 알았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;LLM처럼 빅데이터를 이용해 Model 생성시점부터 Vision영상분석과 Action추론이 가능하도록 만든 생성형 모델이다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;LLM&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;VLM&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;VLA (피지컬 AI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;입력&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트 + 이미지&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트 + 이미지 + 센서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;출력&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트 + 행동 명령&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;현실 세계 상호작용&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;불가능&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;관찰만 가능&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;직접 조작 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;학습 데이터&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;인터넷 텍스트&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;텍스트 + 이미지&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;현장 센서 + 로봇 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/31&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;함께 볼만한 글 AI IDE: 2026.02.17 - [개발] - 구글 Antigravity 사용 후기 - QML프로젝트로 AI IDE 특징 알아보기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778511217423&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;구글 Antigravity 사용 후기 - QML프로젝트로 AI IDE 특징 알아보기&quot; data-og-description=&quot;들어가며요즘 AI를 활용한 이른바 '바이브 코딩(Vibe Coding)'은 더 이상 특별한 일이 아니다.간단한 프로젝트 생성부터 기능 추가, 빌드 자동화까지 AI에게 맡기는 흐름이 자연스러워지고 있다. 특&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/31&quot; data-og-url=&quot;https://prejudice.tistory.com/31&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fQJQi/dJMb9cBMEh7/SJz7qrqVWzpJRK4klk6FWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bMWL5d/dJMb9jOrvpd/4EtzxSkwY0waPyLrmjHNh0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/uYuoZ/dJMb9bv6QXt/1HFV8F0ZJY6dL5K1ICp3a0/img.png?width=1665&amp;amp;height=933&amp;amp;face=0_0_1665_933&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/31&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/31&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fQJQi/dJMb9cBMEh7/SJz7qrqVWzpJRK4klk6FWk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bMWL5d/dJMb9jOrvpd/4EtzxSkwY0waPyLrmjHNh0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/uYuoZ/dJMb9bv6QXt/1HFV8F0ZJY6dL5K1ICp3a0/img.png?width=1665&amp;amp;height=933&amp;amp;face=0_0_1665_933');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;구글 Antigravity 사용 후기 - QML프로젝트로 AI IDE 특징 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며요즘 AI를 활용한 이른바 '바이브 코딩(Vibe Coding)'은 더 이상 특별한 일이 아니다.간단한 프로젝트 생성부터 기능 추가, 빌드 자동화까지 AI에게 맡기는 흐름이 자연스러워지고 있다. 특&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발</category>
      <category>AI</category>
      <category>AI Agent</category>
      <category>Context Injection</category>
      <category>GitHub Copilot</category>
      <category>llm</category>
      <category>Multi-Agent</category>
      <category>orchestrator</category>
      <category>tool calling</category>
      <category>Worker Agent</category>
      <category>생성형AI</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/47</guid>
      <comments>https://prejudice.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 11 May 2026 23:55:03 +0900</pubDate>
    </item>
    <item>
      <title>ROS2를 처음 분석하며 정리한 핵심 개념 - DDS, QoS, ROS Architecture</title>
      <link>https://prejudice.tistory.com/46</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 몇 년 간 AMR&amp;middot;다관절로봇이 떠오르며 ROS 경력 구인공고나, RTOS나 Micro-ROS에 포팅 수요가 많아진 걸 체감 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그만큼 임베디드 시스템에도 AI와 함께 모듈형 설계가 중요해지고 복잡성이 증가했기 때문일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 초보 개발자의 관점에서 &lt;b&gt;ROS2의 강점과 특징에 대해 알아보자&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: GungSeo, serif;&quot;&gt;*ROS 기본 개념은 인터넷에 잘 나와있으니 다음 717lumos 님의 게시글로 대체한다..&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1777356563939&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[ROS] ROS 기본 개념&quot; data-og-description=&quot;로봇 소프트웨어 플랫폼, ROS 소개, ROS 주요 개념 및 컨셉, 파일 시스템&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@717lumos/ROS-ROS-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90#4-1-%EB%A7%88%EC%8A%A4%ED%84%B0master&quot; data-og-url=&quot;https://velog.io/@717lumos/ROS-ROS-기본-개념&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dLRixU/dJMb8956D2A/gCpC0FgzgYKVWf8auhNpuK/img.png?width=4224&amp;amp;height=2377&amp;amp;face=0_0_4224_2377,https://scrap.kakaocdn.net/dn/C4IPl/dJMb9hC4iEv/LY9EbbW0fx7IEYSnbSt8e1/img.png?width=4224&amp;amp;height=2377&amp;amp;face=0_0_4224_2377,https://scrap.kakaocdn.net/dn/q9mm7/dJMb9hC4iEu/WalxwAY5OKdBfT3V0kICQ0/img.png?width=4224&amp;amp;height=2377&amp;amp;face=0_0_4224_2377&quot;&gt;&lt;a href=&quot;https://velog.io/@717lumos/ROS-ROS-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90#4-1-%EB%A7%88%EC%8A%A4%ED%84%B0master&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@717lumos/ROS-ROS-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90#4-1-%EB%A7%88%EC%8A%A4%ED%84%B0master&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dLRixU/dJMb8956D2A/gCpC0FgzgYKVWf8auhNpuK/img.png?width=4224&amp;amp;height=2377&amp;amp;face=0_0_4224_2377,https://scrap.kakaocdn.net/dn/C4IPl/dJMb9hC4iEv/LY9EbbW0fx7IEYSnbSt8e1/img.png?width=4224&amp;amp;height=2377&amp;amp;face=0_0_4224_2377,https://scrap.kakaocdn.net/dn/q9mm7/dJMb9hC4iEu/WalxwAY5OKdBfT3V0kICQ0/img.png?width=4224&amp;amp;height=2377&amp;amp;face=0_0_4224_2377');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[ROS] ROS 기본 개념&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;로봇 소프트웨어 플랫폼, ROS 소개, ROS 주요 개념 및 컨셉, 파일 시스템&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Embedded 개발자의 시선에서 본 ROS 주요 개념&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Multi-Platform&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROS2(Robot Operating System)는 DDS 기반 미들웨어 위에 구축된 로봇 소프트웨어 프레임워크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 OS에서 동작할 수 있고, 계층별/언어별로 라이브러리를 제공하여 개발이 수월하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DDS Pub/Sub Message구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDS(Data Distribution Service)는 데이터를 주고받는 미들웨어 표준으로 ROS2는 DDS를 통신 미들웨어로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROS2 노드 간 데이터는 Message 형태로 전송되는데, DDS Layer를 통해 Pub/Sub 구조로 통신한다.&lt;br /&gt;이때 DDS추상 레이어(rmw)를 두어 벤더 독립성(Vendor Independence)을 유지하고, 시스템 복잡성을 낮춘다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DDS의 QoS (Quality of Service)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROS2를 AMR &amp;middot; 다관절 로봇에 적용하기 좋은 이유는 DDS에서 QoS를 제공하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 DDS 메시지를 구독한다고 할 때, 다음과 같은 QoS정책을 설정할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Deadline: &lt;/b&gt;메시지가 일정 주기(ex. 10㎳) 내에 도착해야 함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reliability:&lt;/b&gt; 누락된 메시지를 무시하고 진행 or 재전송 해&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 실시간성(Deadline안에 센서 처리를 끝내고 publish까지의 과정)은 내부 칩이나 RTOS를 통해 따로 확보해야 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;QoS정책 설정으로 아키텍처가 단순해지고 모듈별로 책임을 세분화할 수 있다&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;513&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0DMuR/dJMcaiXofAI/ejTaR9qA5jsCcau3mM0CUK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0DMuR/dJMcaiXofAI/ejTaR9qA5jsCcau3mM0CUK/img.jpg&quot; data-alt=&quot;ROS2 Architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0DMuR/dJMcaiXofAI/ejTaR9qA5jsCcau3mM0CUK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0DMuR%2FdJMcaiXofAI%2FejTaR9qA5jsCcau3mM0CUK%2Fimg.jpg&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;600&quot; height=&quot;513&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;513&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ROS2 Architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;ROS1 &lt;span style=&quot;color: #ee2323;&quot;&gt;vs&lt;/span&gt; ROS2 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 차이는 위에서 언급한 &lt;b&gt;통신 방식의 차이&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROS1은 TCP/UDP 프로토콜을 사용했고 따라서 QoS 측면에서 한계가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ROS2는&lt;/b&gt; &lt;b&gt;DDS를 사용&lt;/b&gt;해 QoS 설정 및 신뢰성을 확보함과 동시에 Pub/Sub 구조로 기존의 &lt;b&gt;ROS Master노드가 불필요해졌다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;호환성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROS1과 ROS2는 통신 방식이 달라 상호 호환되지 않는다. (ROS1 &amp;ne; ROS2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 ROS2 내부에서도 버전 간 완전한 호환성이 보장되지 않기 때문에 동일한 버전을 쓰는 것이 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 의존성이 큰 ROS를 배포할 때는, Docker/Container를 활용해 배포하거나 패키지 매니저를 통해 배포하는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 DDS 구현방식과 QoS 설정에 따라 동작이 달라질 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 95.8141%; height: 273px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style10&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;&lt;b&gt;패키지 명 (출시일)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 85px;&quot; rowspan=&quot;5&quot;&gt;&lt;b&gt;ROS 2 (권장 버전)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Jazzy Jalisco (2024.05 - LTS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Iron Irwini (2023.05)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Humble Hawksbill (2022.05 - LTS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Galactic Geochelone (2021.05)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Foxy Fitzroy (2020.06 - LTS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 38px;&quot; rowspan=&quot;2&quot;&gt;&lt;b&gt;ROS 1 (Legacy)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px;&quot;&gt;Noetic Ninjemys (2020.05 - 마지막 ROS1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;Melodic Morenia (2018.05)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;*ROS2의 릴리즈는 연 1회 발표하며, LTS는 발표된 이후 5년간 지원한다.&lt;br /&gt;*ROS 인터페이스 배포를 위해서는 ROS 배포판, QoS, 토픽 타입, 빌드 환경 등을 맞춰야 한다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROS2를 살펴보며, 복잡한 센서 및 로봇 시스템에 적용할 경우 아키텍처를 단순화하고 개발 시간을 크게 단축할 수 있는 프레임워크라는 점을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDS 기반의 강력한 통신 구조를 바탕으로 모듈화와 확장성을 자연스럽게 확보할 수 있다는 점 역시 인상적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 실제로 유저 레벨까지 서비스를 제공하는 관점에서는 배포 방식이나 버전 관리, 실행 환경 구성 등에서 추가적으로 고려해야 할 요소들이 많고 운영 측면의 고민이 함께 필요한 분명한 장단점을 가지는 기술이라 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근&amp;nbsp; PLC 프로젝트가 고도화되며 조직이 Robot Control Solution 팀으로 개편된 만큼,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향후 AMR이나 Motion 기능 개발을 진행하게 된다면 ROS를 이용해 작업하며 실무에 관한 글을 이어갈 수 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 작성하며 SW개발자의 관점에서 가장 궁금한 점은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 아키텍처 구조 속에서 DDS기반 데이터 통신이 실제 환경에서 어느 정도의 성능과 안정성이 나올지 궁금하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 이를 직접 검증해 보는 과정이 재밌는 과제가 될 것 같다.&lt;/p&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Linux실시간성에 관한 글:&lt;/b&gt; 2026.04.06 - [개발] - Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1777386963347&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&quot; data-og-description=&quot;들어가며최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다. SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.따라서 OS에서 프로세스의 &quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/42&quot; data-og-url=&quot;https://prejudice.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnWtnw/dJMb9fZypys/F6ZGebg1OXP9KL69IXZgd1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b6McQM/dJMb8RRUXOc/Kfx1i99aJMAy31H2LBZOFK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Go2oi/dJMb9gxov2I/BkPk6VK3EwqGiueWfvHKik/img.png?width=1773&amp;amp;height=452&amp;amp;face=0_0_1773_452&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/42&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnWtnw/dJMb9fZypys/F6ZGebg1OXP9KL69IXZgd1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b6McQM/dJMb8RRUXOc/Kfx1i99aJMAy31H2LBZOFK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Go2oi/dJMb9gxov2I/BkPk6VK3EwqGiueWfvHKik/img.png?width=1773&amp;amp;height=452&amp;amp;face=0_0_1773_452');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다. SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.따라서 OS에서 프로세스의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://docs.ros.org/en/rolling/index.html#&quot;&gt;&lt;b&gt;ROS공식 Documents:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://docs.ros.org/en/rolling/&quot;&gt;https://docs.ros.org/en/rolling/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1777386972327&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ROS 2 Documentation &amp;mdash; ROS 2 Documentation: Rolling  documentation&quot; data-og-description=&quot;&amp;copy; Copyright 2026, Open Robotics.&quot; data-og-host=&quot;docs.ros.org&quot; data-og-source-url=&quot;https://docs.ros.org/en/rolling/&quot; data-og-url=&quot;https://docs.ros.org/en/rolling/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.ros.org/en/rolling/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.ros.org/en/rolling/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ROS 2 Documentation &amp;mdash; ROS 2 Documentation: Rolling documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;copy; Copyright 2026, Open Robotics.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.ros.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발</category>
      <category>DDS</category>
      <category>Linux</category>
      <category>QoS</category>
      <category>ros</category>
      <category>ROS2</category>
      <category>RTOS</category>
      <category>ubuntu</category>
      <category>로봇개발</category>
      <category>소프트웨어아키텍처</category>
      <category>임베디드</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/46</guid>
      <comments>https://prejudice.tistory.com/46#entry46comment</comments>
      <pubDate>Wed, 29 Apr 2026 00:14:26 +0900</pubDate>
    </item>
    <item>
      <title>C++ Preemptive Task Scheduler 구현 및 성능 비교 (Windows &amp;middot; Linux &amp;middot; Linux PREEMPT_RT)</title>
      <link>https://prejudice.tistory.com/45</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 며칠 동안 진행 중인 SW PLC 프로젝트의 핵심 로직인 &lt;b&gt;멀티 태스킹(Multi-Tasking)&lt;/b&gt; 기능을 구현하는 데 집중하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업의 세부 목표는 총 3가지이며, 최종적으로는 &lt;b&gt;사용자의 다중 Task를 선점 스케줄링(Preemptive Scheduling) 방식&lt;/b&gt;으로 수행하는 클래스를 개발하려 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Multi-Platform 지원: '&lt;/b&gt;Windows', 'Linux', 'Linux PREEMPT_RT Kernel' 환경에서 모두 정상 동작할 것.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실시간성(Real-Time) 확보:&lt;/b&gt; 선점 스케줄링 방식으로 Task를 관리하되, 스레드 전환 및 스케줄링 작업이 무겁지 않아야 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Task세분화: &lt;/b&gt;Task의 종류를 CycleTask(정 주기 실행)와 EventTask(이벤트 트리거 실행) 두 가지로 구성할 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에 들어가기 앞서 3개의 태스크가 선점형으로 동작하는 최종 모습을 타이밍 차트로 그려 정리했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mKuHm/dJMcagFdD1j/i3UjknD49FY3vg8xYwDwf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mKuHm/dJMcagFdD1j/i3UjknD49FY3vg8xYwDwf0/img.png&quot; data-alt=&quot;그림1. 선점 스케줄러의 3개 Task 동작 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mKuHm/dJMcagFdD1j/i3UjknD49FY3vg8xYwDwf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmKuHm%2FdJMcagFdD1j%2Fi3UjknD49FY3vg8xYwDwf0%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;943&quot; height=&quot;203&quot; data-origin-width=&quot;943&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. 선점 스케줄러의 3개 Task 동작 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Multi Task / Preemptive Task Scheduler 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 시스템의 가장 단순한 UML을 구상하고, &lt;b&gt;각 클래스별로 책임과 역할&lt;/b&gt;을 선정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;TaskManager :&lt;/b&gt; 다중 태스크(사용자 태스크)의 리스트와 전체 생명주기를 관리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TaskThread :&lt;/b&gt; OS에 맞춰 Thread 등록 / 실제 Task Logic을 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TaskScheduler :&lt;/b&gt; Preemptive Task Queue 관리 / TaskThread들을 우선순위에 따라 유예 &amp;middot; 실행&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;middot;&lt;span&gt; 재개&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점형 스케줄링 기능을 직접 제어하기 위해, 크로스 플랫폼 개발 중임에도 QThread를 사용하지 않고 &lt;b&gt;OS Thread를 사용&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS Thread의 경우 스래드 실행 중에 &lt;span class=&quot;inline-em&quot;&gt;suspend()&lt;/span&gt;호출로 현재 스택 포인터(SP)를 저장하고 &lt;span class=&quot;inline-em&quot;&gt;resume()&lt;/span&gt;을 통해 재개할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &lt;span class=&quot;inline-em&quot;&gt;그림1&lt;/span&gt; 처럼 Task 중간에 멈추고 다른 Task를 실행하기에 적합하다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVpa7S/dJMcajaOgP8/3QCB2QPDHPkNQ6MI277LM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVpa7S/dJMcajaOgP8/3QCB2QPDHPkNQ6MI277LM1/img.png&quot; data-alt=&quot;Component Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVpa7S/dJMcajaOgP8/3QCB2QPDHPkNQ6MI277LM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVpa7S%2FdJMcajaOgP8%2F3QCB2QPDHPkNQ6MI277LM1%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;464&quot; height=&quot;272&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Component Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 로직 구현을 위한 Class Diagram을 다음과 같이 그릴 수 있다.&lt;br /&gt;&lt;span class=&quot;inline-em&quot;&gt;TaskThread::createForPlatform()&lt;/span&gt; 은 Factory 패턴을 사용해 OS에 맞춰 &lt;b&gt;각각 독립적인 Thread 인스턴스를 생성&lt;/b&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA8kHe/dJMcajhzG4q/Nveh884NZIyCwJsQTOVKX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA8kHe/dJMcajhzG4q/Nveh884NZIyCwJsQTOVKX0/img.png&quot; data-alt=&quot;Class Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA8kHe/dJMcajhzG4q/Nveh884NZIyCwJsQTOVKX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA8kHe%2FdJMcajhzG4q%2FNveh884NZIyCwJsQTOVKX0%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;1051&quot; height=&quot;774&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Class Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TaskThread 인스턴스의 정 주기 신호, 혹은 외부 trigger() 신호가 발생하면 &lt;span class=&quot;inline-em&quot;&gt;TaskScheduler::scheduleTask(task)&lt;/span&gt; 를 호출하여 스케줄러에게 스케줄링을 요청한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청을 받은 &lt;b&gt;스케줄러&lt;/b&gt;는 동기화 락 구조 안에서 Task의 우선순위에 따라 여러 태스크를 제어(Suspend &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;middot;&lt;/span&gt; Resume &amp;middot; Execute)한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span class=&quot;inline-em&quot;&gt;scheduleTask()&lt;/span&gt; 가 발생했을 때의 로직흐름은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sQCHS/dJMcagSJSju/ngJtDZOR8mkG6mAcbBZodK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sQCHS/dJMcagSJSju/ngJtDZOR8mkG6mAcbBZodK/img.png&quot; data-alt=&quot;Task Scheduleing Flowchart&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sQCHS/dJMcagSJSju/ngJtDZOR8mkG6mAcbBZodK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsQCHS%2FdJMcagSJSju%2FngJtDZOR8mkG6mAcbBZodK%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;482&quot; height=&quot;710&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Task Scheduleing Flowchart&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Multi Task / Preemptive Task Scheduler 구현&lt;/h2&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조와 역할을 잘 분리해 둔 덕분에 실제 구현은 어렵지 않았지만, 애를 먹은 부분은 테스트였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows, Linux(일반 커널), Linux(PREEMPT_RT 커널) 등 3가지 환경에서 실행하고 검증해야 했으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Scheduling 전환 작업이 밀리초(㎳) 단위로 빠르게 실행되기 때문에 &lt;b&gt;I/O를 사용하는 Log 출력&lt;/b&gt;으로 인해 느려지는 버그나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OS 타이머 분해능, 객체 동적 할당 지연 등을 고려&lt;/b&gt;해야 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1776412143210&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; TaskTest
┃  TaskTest.pro  # Qt Project
┃  main.cpp
┃  TaskManager.cpp
┃  TaskManager.h
┃  TaskScheduler.cpp
┃  TaskScheduler.h
┗  TaskThread
  ┣  TaskThread.cpp
  ┣  TaskThread.h
  ┣  TaskThreadLinux.cpp
  ┣  TaskThreadLinux.h
  ┣  TaskThreadWin.cpp
  ┗  TaskThreadWin.h&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/brS9Ka/dJMb9968WcA/QDkKPUKWsWIo0zI6Bxi3qk/TaskTest.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;TaskTest.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.01MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskTest.pro&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412416398&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;QT = core
CONFIG += console
CONFIG -= app_bundle

# POSIX 스레드/실시간 라이브러리 (Linux 전용)
unix:LIBS += -lpthread -lrt

# Windows 멀티미디어 타이머 라이브러리
win32:LIBS += -lwinmm

# 정적 링크: libstdc++와 libgcc를 실행 파일에 포함
unix:QMAKE_LFLAGS += -static-libstdc++ -static-libgcc

# RPATH 설정: Qt 라이브러리용
unix:QMAKE_LFLAGS += -Wl,-rpath,\'\$$ORIGIN/lib\'

TARGET = TaskTest
TEMPLATE = app

DESTDIR     = $$PWD/build
OBJECTS_DIR = $$PWD/build
MOC_DIR     = $$PWD/build

INCLUDEPATH += $$PWD/TaskThread

SOURCES += \
    main.cpp \
    TaskThread/TaskThread.cpp \
    TaskScheduler.cpp \
    TaskManager.cpp

unix:SOURCES  += TaskThread/TaskThreadLinux.cpp
win32:SOURCES += TaskThread/TaskThreadWin.cpp

HEADERS += \
    TaskThread/TaskThread.h \
    TaskScheduler.h \
    TaskManager.h

unix:HEADERS  += TaskThread/TaskThreadLinux.h
win32:HEADERS += TaskThread/TaskThreadWin.h&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;main.cpp&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412484164&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;QCoreApplication&amp;gt;
#include &amp;lt;QTimer&amp;gt;
#ifdef __linux__
#include &amp;lt;sys/mman.h&amp;gt;
#endif
#ifdef _WIN32
#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;mmsystem.h&amp;gt;
#endif
#include &amp;lt;iostream&amp;gt;
#include &quot;TaskManager.h&quot;

#ifdef __linux__
// Stack prefaulting - prevent page faults
static void prefaultStack()
{
    unsigned char dummy[8 * 1024];  // 8KB stack
    memset(dummy, 0, sizeof(dummy));
}
#endif

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

#ifdef __linux__
    // RT memory locking and stack prefaulting (need root)
    if (mlockall(MCL_CURRENT | MCL_FUTURE) == 0) {
        std::cout &amp;lt;&amp;lt; &quot;[RT] Memory locked (no page faults)&quot; &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;[RT] Failed to lock memory (need root)&quot; &amp;lt;&amp;lt; std::endl;
    }

    prefaultStack();
#endif

    TaskManager manager;
    manager.start();

    QTimer::singleShot(5000, &amp;amp;app, SLOT(quit())); // Quit after 5 seconds

    return app.exec();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskManager.cpp&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412500394&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;TaskManager.h&quot;
#include &quot;TaskThread.h&quot;

#include &amp;lt;iostream&amp;gt;

TaskScheduler *g_scheduler = nullptr;

TaskManager::TaskManager()
{
    g_scheduler = new TaskScheduler();
    
    TaskThread *primaryTask = TaskThread::createForPlatform(&quot;PrimaryTask&quot;, 0, 300, 1000, g_scheduler);
    TaskThread *taskB       = TaskThread::createForPlatform(&quot;TaskB&quot;,       1, 1300, 2500, g_scheduler);
    // TaskThread *taskC       = TaskThread::createForPlatform(&quot;TaskC&quot;,       2, 1500, 0, g_scheduler); // 0 = 외부 트리거

    m_tasks.push_back(primaryTask);
    m_tasks.push_back(taskB);
    // m_tasks.push_back(taskC);
}

TaskManager::~TaskManager()
{
    for (TaskThread *task : m_tasks)
        delete task;
    
    delete g_scheduler;
    g_scheduler = nullptr;
}

void TaskManager::start()
{
    std::cout &amp;lt;&amp;lt; &quot;=== TaskManager started ===&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;  PrimaryTask : 1000ms period  priority=0  work=300ms&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; &quot;  TaskB       : 2500ms period  priority=1  work=1300ms&quot; &amp;lt;&amp;lt; std::endl;
    std::cout &amp;lt;&amp;lt; std::endl;

    g_scheduler-&amp;gt;resetBaseTime();

    for (TaskThread *task : m_tasks)
        task-&amp;gt;start();
}

void TaskManager::stop()
{
    for (TaskThread *task : m_tasks)
        task-&amp;gt;stop();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskManager.h&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412528002&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef TASKMANAGER_H
#define TASKMANAGER_H

#include &amp;lt;vector&amp;gt;
#include &quot;TaskScheduler.h&quot;

extern TaskScheduler *g_scheduler;

class TaskThread;

class TaskManager
{
public:
    explicit TaskManager();
    ~TaskManager();

    void start();
    void stop();

private:
    std::vector&amp;lt;TaskThread*&amp;gt;   m_tasks;
};

#endif // TASKMANAGER_H&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskScheduler.cpp&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412555978&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;TaskScheduler.h&quot;

#include &amp;lt;iostream&amp;gt;
#include &amp;lt;cstdio&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;time.h&amp;gt;

TaskScheduler::TaskScheduler()
{
    // Start asynchronous logger thread
    m_logRunning.store(true);
    m_logThread = std::thread([this]() { loggerLoop(); });
}

TaskScheduler::~TaskScheduler()
{
    // Signal logger thread to stop and join
    {
        std::lock_guard&amp;lt;std::mutex&amp;gt; lock(m_logMutex);
        m_logRunning.store(false);
    }
    m_logCv.notify_one();
    if (m_logThread.joinable())
        m_logThread.join();
}

void TaskScheduler::resetBaseTime()
{
    clock_gettime(CLOCK_MONOTONIC, &amp;amp;m_baseTime);
}

long long TaskScheduler::getElapsedUs() const
{
    timespec now;
    clock_gettime(CLOCK_MONOTONIC, &amp;amp;now);
    return (now.tv_sec - m_baseTime.tv_sec) * 1000000 +
           (now.tv_nsec - m_baseTime.tv_nsec) / 1000;
}

void TaskScheduler::log(const char *msg) const
{
    LogEntry entry;
    entry.ts = getElapsedUs();
    strncpy(entry.msg, msg, sizeof(entry.msg) - 1);
    entry.msg[sizeof(entry.msg) - 1] = '\0';

    {
        std::lock_guard&amp;lt;std::mutex&amp;gt; lock(m_logMutex);
        m_logQueue.push(entry);
    }
    m_logCv.notify_one();
}

void TaskScheduler::loggerLoop()
{
    while (true) {
        std::unique_lock&amp;lt;std::mutex&amp;gt; lock(m_logMutex);
        m_logCv.wait(lock, [this]() {
            return !m_logQueue.empty() || !m_logRunning.load();
        });

        std::queue&amp;lt;LogEntry&amp;gt; batch;
        std::swap(batch, m_logQueue);
        lock.unlock();

        while (!batch.empty()) {
            const LogEntry &amp;amp;e = batch.front();
            printf(&quot;[t=%8lld.%03lldms] %s\n&quot;, e.ts / 1000, e.ts % 1000, e.msg);
            batch.pop();
        }
        fflush(stdout);

        if (!m_logRunning.load() &amp;amp;&amp;amp; m_logQueue.empty())
            break;
    }
}

void TaskScheduler::insertByPriority(TaskThread *task)
{
    for (size_t i = 0; i &amp;lt; m_pendingQueue.size(); ++i) {
        if (task-&amp;gt;priority() &amp;lt; m_pendingQueue[i]-&amp;gt;priority()) {
            m_pendingQueue.insert(m_pendingQueue.begin() + i, task);
            return;
        }
    }
    m_pendingQueue.push_back(task);
}

void TaskScheduler::scheduleTask(TaskThread *task)
{
    std::lock_guard&amp;lt;std::mutex&amp;gt; lock(m_mutex);
    insertByPriority(task);

    char buf[256];
    snprintf(buf, sizeof(buf), &quot;[Scheduler] triggered: %s  pending=%zu  running=%s&quot;,
             task-&amp;gt;name().toUtf8().constData(),
             m_pendingQueue.size(),
             m_currentTask ? m_currentTask-&amp;gt;name().toUtf8().constData() : &quot;-&quot;);
    log(buf);

    if (m_currentTask != nullptr &amp;amp;&amp;amp; task-&amp;gt;priority() &amp;lt; m_currentTask-&amp;gt;priority()) {
        snprintf(buf, sizeof(buf), &quot;[Scheduler] PREEMPT: suspend %s -&amp;gt; run %s&quot;,
                 m_currentTask-&amp;gt;name().toUtf8().constData(),
                 task-&amp;gt;name().toUtf8().constData());
        log(buf);

        TaskThread *preempted = m_currentTask;
        m_currentTask   = nullptr;

        preempted-&amp;gt;suspend();
        insertByPriority(preempted);
    }
    
    tryRunNext();
}

void TaskScheduler::notifyFinished(const char *name, long long elapsedMs)
{
    std::lock_guard&amp;lt;std::mutex&amp;gt; lock(m_mutex);
    
    char buf[256];
    snprintf(buf, sizeof(buf), &quot;[Scheduler] finished : %s  wall=%lldms  pending=%zu&quot;, name, elapsedMs, m_pendingQueue.size());
    log(buf);

    m_currentTask = nullptr;
    
    if (!m_pendingQueue.empty())
        tryRunNext();
}

// ── Attempt to run the next task (called within mutex) ─────────────────────────────
void TaskScheduler::tryRunNext()
{
    if (m_pendingQueue.empty() || m_currentTask != nullptr)
        return;

    m_currentTask = m_pendingQueue.front();
    m_pendingQueue.erase(m_pendingQueue.begin());

    char buf[256];
    if (m_currentTask-&amp;gt;isSuspended()) {
        snprintf(buf, sizeof(buf), &quot;[Scheduler] resume  : %s  (priority=%d)&quot;,
                 m_currentTask-&amp;gt;name().toUtf8().constData(),
                 m_currentTask-&amp;gt;priority());
        log(buf);
        m_currentTask-&amp;gt;resume();
    } else {
        snprintf(buf, sizeof(buf), &quot;[Scheduler] execute : %s  (priority=%d)&quot;,
                 m_currentTask-&amp;gt;name().toUtf8().constData(),
                 m_currentTask-&amp;gt;priority());
        log(buf);
        m_currentTask-&amp;gt;execute();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskScheduler.h&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412646859&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef TASKSCHEDULER_H
#define TASKSCHEDULER_H

#include &quot;TaskThread.h&quot;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;mutex&amp;gt;
#include &amp;lt;condition_variable&amp;gt;
#include &amp;lt;thread&amp;gt;
#include &amp;lt;atomic&amp;gt;
#include &amp;lt;time.h&amp;gt;

class TaskScheduler
{
public:
    explicit TaskScheduler();
    ~TaskScheduler();

    void scheduleTask(TaskThread *task);
    void notifyFinished(const char *name, long long elapsedMs);

    timespec getBaseTime() const { return m_baseTime; }
    void resetBaseTime();
    long long getElapsedUs() const;

private:
    void insertByPriority(TaskThread *task);
    void tryRunNext();
    void log(const char *msg) const;
    void loggerLoop();

    // ── Scheduler state (protected by m_mutex) ───────────────────────────────────
    std::mutex            m_mutex;
    TaskThread           *m_currentTask{nullptr};
    std::vector&amp;lt;TaskThread*&amp;gt;    m_pendingQueue;
    timespec              m_baseTime;

    // ── Asynchronous logger ──────────────────────────────────────────────────────
    struct LogEntry { long long ts; char msg[256]; };
    mutable std::mutex              m_logMutex;
    mutable std::condition_variable m_logCv;
    mutable std::queue&amp;lt;LogEntry&amp;gt;    m_logQueue;
    std::thread                     m_logThread;
    std::atomic&amp;lt;bool&amp;gt;               m_logRunning{false};
};

#endif // TASKSCHEDULER_H&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskThread\TaskThread.cpp&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412773227&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;TaskThread.h&quot;
#include &quot;TaskScheduler.h&quot;

#ifdef Q_OS_WIN
#  include &quot;TaskThreadWin.h&quot;
#else
#  include &quot;TaskThreadLinux.h&quot;
#endif

TaskThread::TaskThread(const QString &amp;amp;name,
                       int priority,
                       int workMs,
                       int intervalMs,
                       TaskScheduler *scheduler)
    : m_name(name)
    , m_priority(priority)
    , m_workMs(workMs)
    , m_intervalMs(intervalMs)
    , m_scheduler(scheduler)
{
}

TaskThread::~TaskThread()
{
}

void TaskThread::trigger()
{
    if (m_scheduler) {
        m_scheduler-&amp;gt;scheduleTask(this);
    }
}

TaskThread* TaskThread::createForPlatform(const QString &amp;amp;name,
                               int priority,
                               int workMs,
                               int intervalMs,
                               TaskScheduler *scheduler)
{
#ifdef Q_OS_WIN
    return new TaskThreadWin(name, priority, workMs, intervalMs, scheduler);
#else
    return new TaskThreadLinux(name, priority, workMs, intervalMs, scheduler);
#endif
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskThread\TaskThread.h&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412794011&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef TASKTHREAD_H
#define TASKTHREAD_H

#include &amp;lt;QString&amp;gt;
#include &amp;lt;atomic&amp;gt;

class TaskScheduler;

class TaskThread
{
    friend class TaskScheduler;

public:
    explicit TaskThread(const QString &amp;amp;name,
                       int priority,
                       int workMs,
                       int intervalMs,
                       TaskScheduler *scheduler);
    virtual ~TaskThread();

    static TaskThread* createForPlatform(const QString &amp;amp;name,
                             int priority,
                             int workMs,
                             int intervalMs,
                             TaskScheduler *scheduler);

    QString name()     const { return m_name; }
    int     priority() const { return m_priority; }

    virtual void start() = 0;
    virtual void stop()  = 0;

    void trigger();

    bool isSuspended() const { return m_suspended.load(); }

protected:
    //  ── Scheduler-only methods (do not call from outside) ──
    virtual void suspend() = 0;
    virtual void resume()  = 0;
    virtual void execute() = 0;

protected:
    QString                m_name;
    int                    m_priority;
    int                    m_workMs; // Work time delay between tests
    int                    m_intervalMs;  // Interval (ms), 0 means external trigger only
    TaskScheduler         *m_scheduler;
    std::atomic&amp;lt;bool&amp;gt;      m_suspended{false};
};

#endif // TASKTHREAD_H&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskThread\TaskThreadLinux.cpp&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776412810557&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;TaskThreadLinux.h&quot;
#include &quot;TaskScheduler.h&quot;

#include &amp;lt;QElapsedTimer&amp;gt;
#include &amp;lt;signal.h&amp;gt;
#include &amp;lt;time.h&amp;gt;
#include &amp;lt;sched.h&amp;gt;
#include &amp;lt;mutex&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;algorithm&amp;gt;

static timespec addMs(const timespec&amp;amp; t, long ms) {
    timespec out = t;
    out.tv_sec += ms / 1000;
    out.tv_nsec += (ms % 1000) * 1000000L;
    if (out.tv_nsec &amp;gt;= 1000000000L) {
        out.tv_sec += 1;
        out.tv_nsec -= 1000000000L;
    }
    return out;
}

// ── Thread-local sem pointers (signal handler에서 접근) ──
static thread_local sem_t *tl_suspendSem   = nullptr;
static thread_local sem_t *tl_suspendedSem = nullptr;

// ── SIGRTMIN+8 handler ──────────
static void preemptHandler(int)
{
    if (!tl_suspendSem || !tl_suspendedSem)
        return;

    sem_post(tl_suspendedSem);
    sem_wait(tl_suspendSem);
}

static void installPreemptHandler()
{
    struct sigaction sa{};
    sa.sa_handler = preemptHandler;
    sigemptyset(&amp;amp;sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGRTMIN + 8, &amp;amp;sa, nullptr);
}

// ── TaskThreadLinux ────────────────────────────────────────────────────────

TaskThreadLinux::TaskThreadLinux(const QString &amp;amp;name,
                                 int priority,
                                 int workMs,
                                 int intervalMs,
                                 TaskScheduler *scheduler)
    : TaskThread(name, priority, workMs, intervalMs, scheduler)
{
    sem_init(&amp;amp;m_executeSem, 0, 0);
    sem_init(&amp;amp;m_suspendSem, 0, 0);
    sem_init(&amp;amp;m_suspendedSem, 0, 0);

    static std::once_flag once;
    std::call_once(once, installPreemptHandler);
}

TaskThreadLinux::~TaskThreadLinux()
{
    stop();
    sem_destroy(&amp;amp;m_executeSem);
    sem_destroy(&amp;amp;m_suspendSem);
    sem_destroy(&amp;amp;m_suspendedSem);
}

void TaskThreadLinux::start()
{
    m_running.store(true);
    pthread_create(&amp;amp;m_thread, nullptr, [](void *arg) -&amp;gt; void * {
        static_cast&amp;lt;TaskThreadLinux *&amp;gt;(arg)-&amp;gt;threadLoop();
        return nullptr;
    }, this);
}

void TaskThreadLinux::stop()
{
    if (!m_running.load())
        return;
    m_running.store(false);
    sem_post(&amp;amp;m_executeSem);
    pthread_join(m_thread, nullptr);
    m_thread = 0;
}

void TaskThreadLinux::threadLoop()
{
    tl_suspendSem   = &amp;amp;m_suspendSem;
    tl_suspendedSem = &amp;amp;m_suspendedSem;

    setRtPriority();
    long long tick = 1;
    timespec startTime = m_scheduler-&amp;gt;getBaseTime();
    
    while (m_running.load()) {
        if (m_intervalMs &amp;gt; 0) {
            const timespec nextTs = addMs(startTime, tick * m_intervalMs);
            int ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &amp;amp;nextTs, nullptr);
            if (ret != 0 &amp;amp;&amp;amp; ret != EINTR) {
                break;
            }
            
            if (!m_running.load())
                break;
            
            m_scheduler-&amp;gt;scheduleTask(this);
            ++tick;
        }
        
        sem_wait(&amp;amp;m_executeSem);
        if (!m_running.load())
            break;

        // ── Execute task ─────────────────────────────────────────────
        m_isExecuting.store(true);

        // Reset accumulated time and start timer
        m_accumulatedMs = 0;
        m_wallTimer.start();

        volatile double acc = 0.0;
        for (long long i = 1; ; ++i) {
            acc += 1.0 / static_cast&amp;lt;double&amp;gt;(i);
            if (i % 10000 == 0) {  // Appropriate check interval
                long long totalWorkTime = m_accumulatedMs + m_wallTimer.elapsed();
                if (totalWorkTime &amp;gt;= m_workMs)
                    break;
            }
        }
        (void)acc;

        // ── Notify task completion ────────────────────────────────────────
        long long finalWorkTime = m_accumulatedMs + m_wallTimer.elapsed();
        m_isExecuting.store(false);
        m_scheduler-&amp;gt;notifyFinished(m_name.toUtf8().constData(), finalWorkTime);        
    }
}

void TaskThreadLinux::setRtPriority()
{
    int rt_priority = std::max(1, std::min(80, 80 - m_priority));
    struct sched_param param;
    param.sched_priority = rt_priority;
    
    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &amp;amp;param) == 0) {
        std::cout &amp;lt;&amp;lt; &quot;[&quot; &amp;lt;&amp;lt; m_name.toStdString() &amp;lt;&amp;lt; &quot;] RT priority: &quot; &amp;lt;&amp;lt; rt_priority &amp;lt;&amp;lt; std::endl;
    }
}

void TaskThreadLinux::execute()
{
    sem_post(&amp;amp;m_executeSem);
}

void TaskThreadLinux::suspend()
{
    m_suspended.store(true);

    while (!m_isExecuting.load())
        sched_yield();

    m_accumulatedMs += m_wallTimer.elapsed();

    pthread_kill(m_thread, SIGRTMIN + 8);  // pthread(8) is Suspend signal

    sem_wait(&amp;amp;m_suspendedSem);
}

void TaskThreadLinux::resume()
{
    m_suspended.store(false);
    m_wallTimer.restart();
    sem_post(&amp;amp;m_suspendSem);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskThread\TaskThreadLinux.h&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776413369198&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef TASKTHREADLINUX_H
#define TASKTHREADLINUX_H

#include &quot;TaskThread.h&quot;
#include &amp;lt;QElapsedTimer&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;semaphore.h&amp;gt;
#include &amp;lt;atomic&amp;gt;

class TaskThreadLinux : public TaskThread
{
public:
    explicit TaskThreadLinux(const QString &amp;amp;name,
                            int priority,
                            int workMs,
                            int intervalMs,
                            TaskScheduler *scheduler);
    ~TaskThreadLinux();

    void start()   override;
    void stop()    override;
    void suspend() override;
    void resume()  override;
    void execute() override;

private:
    void threadLoop();
    void setRtPriority();

    pthread_t              m_thread{0};
    std::atomic&amp;lt;bool&amp;gt;      m_running{false};
    std::atomic&amp;lt;bool&amp;gt;      m_isExecuting{false};
    sem_t                  m_executeSem;
    sem_t                  m_suspendSem;
    sem_t                  m_suspendedSem;
    
    QElapsedTimer          m_wallTimer;
    long long              m_accumulatedMs{0};
};

#endif // TASKTHREADLINUX_H&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskThread\TaskThreadWin.cpp&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776413402686&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;TaskThreadWin.h&quot;
#include &quot;TaskScheduler.h&quot;

#include &amp;lt;QElapsedTimer&amp;gt;
#include &amp;lt;QDebug&amp;gt;

TaskThreadWin::TaskThreadWin(const QString &amp;amp;name,
                             int priority,
                             int workMs,
                             int intervalMs,
                             TaskScheduler *scheduler)
    : TaskThread(name, priority, workMs, intervalMs, scheduler)
{
    m_executeSem   = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    m_suspendSem   = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    m_suspendedSem = CreateEvent(nullptr, FALSE, FALSE, nullptr);
}

TaskThreadWin::~TaskThreadWin()
{
    stop();
    if (m_executeSem)   CloseHandle(m_executeSem);
    if (m_suspendSem)   CloseHandle(m_suspendSem);
    if (m_suspendedSem) CloseHandle(m_suspendedSem);
}

void TaskThreadWin::start()
{
    m_running.store(true);
    m_thread = CreateThread(nullptr, 0, threadProc, this, 0, nullptr);
}

void TaskThreadWin::stop()
{
    if (!m_running.load())
        return;
    m_running.store(false);
    SetEvent(m_executeSem);
    if (m_thread) {
        ResumeThread(m_thread);
        WaitForSingleObject(m_thread, INFINITE);
        CloseHandle(m_thread);
        m_thread = nullptr;
    }
}

DWORD WINAPI TaskThreadWin::threadProc(LPVOID arg)
{
    static_cast&amp;lt;TaskThreadWin *&amp;gt;(arg)-&amp;gt;threadLoop();
    return 0;
}

void TaskThreadWin::threadLoop()
{
    setCpuPriority();
    long long tick = 1;
    QElapsedTimer startTimer;
    startTimer.start();
    
    while (m_running.load()) {
        if (m_intervalMs &amp;gt; 0) {
            long long nextTriggerMs = tick * m_intervalMs;
            long long sleepMs = nextTriggerMs - startTimer.elapsed();
            
            if (sleepMs &amp;gt; 0) {
                Sleep(sleepMs);
            }
            
            if (!m_running.load())
                break;
            
            m_scheduler-&amp;gt;scheduleTask(this);
            ++tick;
        }

        WaitForSingleObject(m_executeSem, INFINITE);
        if (!m_running.load())
            break;

        // ── Execute task ──────────────────────────────────────────────────
        m_isExecuting.store(true);
        
        // Reset accumulated time and start timer
        m_accumulatedMs = 0;
        m_wallTimer.start();

        volatile double acc = 0.0;
        for (long long i = 1; ; ++i) {
            acc += 1.0 / static_cast&amp;lt;double&amp;gt;(i);
            if (i % 10000 == 0) { // Appropriate check interval
                long long totalWorkTime = m_accumulatedMs + m_wallTimer.elapsed();
                if (totalWorkTime &amp;gt;= m_workMs)
                    break;
            }
        }
        (void)acc;

        // ── Notify task completion ────────────────────────────────────────
        long long finalWorkTime = m_accumulatedMs + m_wallTimer.elapsed();
        m_isExecuting.store(false);
        m_scheduler-&amp;gt;notifyFinished(m_name.toUtf8().constData(), finalWorkTime);
    }
}

void TaskThreadWin::setCpuPriority()
{
    if (m_thread) {
        SetThreadPriority(m_thread, THREAD_PRIORITY_TIME_CRITICAL);
    }
}

void TaskThreadWin::execute()
{
    SetEvent(m_executeSem);
}

void TaskThreadWin::suspend()
{
    m_suspended.store(true);

    while (!m_isExecuting.load())
        Sleep(0);

    m_accumulatedMs += m_wallTimer.elapsed();

    SuspendThread(m_thread); // Suspend the thread at OS level
    
    SetEvent(m_suspendedSem);
}

void TaskThreadWin::resume()
{
    m_suspended.store(false);
    m_wallTimer.restart();
    ResumeThread(m_thread);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;TaskThread\TaskThreadWin.h&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1776413424865&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifndef TASKTHREADWIN_H
#define TASKTHREADWIN_H

#include &quot;TaskThread.h&quot;
#include &amp;lt;QElapsedTimer&amp;gt;
#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;atomic&amp;gt;

class TaskThreadWin : public TaskThread
{
public:
    explicit TaskThreadWin(const QString &amp;amp;name,
                          int priority,
                          int workMs,
                          int intervalMs,
                          TaskScheduler *scheduler);
    ~TaskThreadWin();

    void start()   override;
    void stop()    override;
    void suspend() override;
    void resume()  override;
    void execute() override;

private:
    void threadLoop();
    void setCpuPriority();
    static DWORD WINAPI threadProc(LPVOID arg);

    HANDLE                 m_thread{nullptr};
    std::atomic&amp;lt;bool&amp;gt;      m_running{false};
    std::atomic&amp;lt;bool&amp;gt;      m_isExecuting{false};
    HANDLE                 m_executeSem{nullptr};
    HANDLE                 m_suspendSem{nullptr};
    HANDLE                 m_suspendedSem{nullptr};
    
    QElapsedTimer          m_wallTimer;
    long long              m_accumulatedMs{0};
};

#endif // TASKTHREADWIN_H&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Preemptive Task Scheduler 테스트 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 코드에서 보면 알 수 있듯, 스케줄러가 잘 동작하는지 테스트하기 위해 &lt;b&gt;가상의 TestCase&lt;/b&gt;를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rYJXP/dJMb9968Ym7/GaZYgaeqk0W5FojlnBQcIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rYJXP/dJMb9968Ym7/GaZYgaeqk0W5FojlnBQcIk/img.png&quot; data-alt=&quot;TestCase 설계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rYJXP/dJMb9968Ym7/GaZYgaeqk0W5FojlnBQcIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrYJXP%2FdJMb9968Ym7%2FGaZYgaeqk0W5FojlnBQcIk%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;690&quot; height=&quot;142&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TestCase 설계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 OS에서 &lt;span class=&quot;inline-em&quot;&gt;TaskTest&lt;/span&gt;&amp;nbsp;프로그램을 실행하며 실제 스케줄링이 잘 되는지 확인하고 마이크로초(㎲) 단위로 시간을 확인했다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 진행하면서 Thread를 제어하는 OS 트리거 최적화가 안되거나, 로깅지연이 발생해 만들어 비동기 스레드로 전환하는 등의 문제가 있었지만, 최종적으로 &lt;b&gt;테스트 결과는 예상했던 것보다 좋았다&lt;/b&gt;.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;*참고로 Linux PREEMPT_RT의 극단적인 CPU 스트레스 테스트는 지난 PoC에서 검증했으므로,&lt;br /&gt;이번 선점 스케줄러 로직 테스트는 &lt;b&gt;CPU 부하가 없는 기본 유휴 상태에서 수행&lt;/b&gt;했다.&lt;br /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글: 2026.04.06 - [개발] - Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure id=&quot;og_1776435586183&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&quot; data-og-description=&quot;들어가며최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다. SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.따라서 OS에서 프로세스의 &quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/42&quot; data-og-url=&quot;https://prejudice.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xcU28/dJMb85vQLvU/2QsAqf488W7DoyF7kVyTJ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c7agG0/dJMb87NYfao/eor9dLaM7J2Q3F2dGoNQuK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/iXGNj/dJMb9aKGR42/n1jKqL7b32MLvdyIuFESpk/img.png?width=1773&amp;amp;height=452&amp;amp;face=0_0_1773_452&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/42&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xcU28/dJMb85vQLvU/2QsAqf488W7DoyF7kVyTJ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c7agG0/dJMb87NYfao/eor9dLaM7J2Q3F2dGoNQuK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/iXGNj/dJMb9aKGR42/n1jKqL7b32MLvdyIuFESpk/img.png?width=1773&amp;amp;height=452&amp;amp;face=0_0_1773_452');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다. SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.따라서 OS에서 프로세스의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Trigger Latency&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정된 매 1000㎳ 주기마다 OS에서 Task가 Trigger 되기까지 걸리는 시간을 측정.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 122px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 17px; text-align: center;&quot;&gt;Ideal Time&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px; text-align: center;&quot;&gt;Windows&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px; text-align: center;&quot;&gt;WSL Linux(non-Preempt)&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px; text-align: center;&quot;&gt;Linux(PREEMPT_RT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px; text-align: center;&quot;&gt;1000 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;1000.349 ㎳ (+0.349 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;1000.101 ㎳ (+0.101 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;1000.196 ㎳ (+0.196 ㎳ )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px; text-align: center;&quot;&gt;2000 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;2000.779 ㎳ (+0.779 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;2000.100 ㎳ (+0.100 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;2000.198 ㎳ (+0.198 ㎳ )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px; text-align: center;&quot;&gt;3000 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;3001.640 ㎳ (+1.640 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;3000.105 ㎳ (+0.105 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;3000.062 ㎳ (+0.062 ㎳ )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 21px; text-align: center;&quot;&gt;4000 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;4001.218 ㎳ (+1.218 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;4000.127 ㎳ (+0.127&amp;nbsp;㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;4000.091 ㎳ (+0.091 ㎳ )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 21px;&quot;&gt;5000 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;5001.253 ㎳ (+1.253 ㎳)&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;5000.110 ㎳ (+0.110 ㎳ )&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 21px;&quot;&gt;5000.162 ㎳ (+0.162 ㎳ )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Windows: &lt;/b&gt;최대 1.640 ㎳의 Jitter가 발생했다. &lt;br /&gt;예상범위 안쪽이었지만 Linux에 비해 주기 신뢰성이 낮고 프로세스 CPU Affinity 최적화 등을 고려해 볼 수 있을 것 같다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WSL의 Linux (non-Preempt): &lt;/b&gt;약&amp;nbsp;100㎲ 내외로 훨씬 안정적인 Latency를 보여주었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Linux (PREEMPT_RT): &lt;/b&gt;안정적인 성능을 보여줬다. 우선 Windows와 Linux와 다르게 Embedded BoxPC에서 돌린 것을 감안하더라도, 지난 PoC에서 CPU가 저전력 상태에서 600㎲ 까지는 떨어지는 것을 확인했기 때문에, 상당히 좋은 수치였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지난 글에서 Linux(PREEMPT_RT) 기반 PoC를 진행하며 &lt;b&gt;평균 Latency 약 11㎲&lt;/b&gt;라는 매우 우수한 결과를 확인했다.&lt;br /&gt;해당 테스트는 CPU에 지속적인 부하를 가해 클럭이 유지된 상태에서 수행되었으며, 이 경우 안정적인 실시간 성능을 확보할 수 있었다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;반면, CPU 부하가 없는 환경&lt;/b&gt;에서는 Idle State로 진입하면서 절전 모드(C-state)가 활성화되었고, 이로 인해 wake-up latency 가 증가하여 &lt;b&gt;최악의 경우 약 600㎲까지 지연이 발생하는 것을 확인&lt;/b&gt;했다.&lt;br /&gt;&lt;br /&gt;이는 PREEMPT_RT 환경에서도 CPU의 전력 관리 정책에 따라 실시간 성능이 크게 영향을 받을 수 있음을 보여준다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Preemption Latency Comparison (선점 응답성 비교)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 높은 우선순위의 Task가 오거나, Task가 종료된 후 다음 Task가 실행되기까지의 시간을 측정.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;TaskB가 실행중일 때 Primary Task가 실행되는 경우: triggered(&lt;b&gt;P&lt;/b&gt;) &amp;rarr; suspend(&lt;b&gt;B&lt;/b&gt;) &amp;rarr; execute(P)까지의 시간을 측정&lt;/li&gt;
&lt;li&gt;Primary Task 종료 후 유예 중인 TaskB가 실행되는 경우: finisshed(&lt;b&gt;P&lt;/b&gt;) &amp;rarr; resume(B)까지의 시간을 측정&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 29.186%; height: 17px; text-align: center;&quot;&gt;Scheduling&lt;/td&gt;
&lt;td style=&quot;width: 24.8838%; height: 17px; text-align: center;&quot;&gt;Windows&lt;/td&gt;
&lt;td style=&quot;width: 24.186%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #008300; color: #ffffff; text-align: center;&quot;&gt;WSL Linux(non-Preempt)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;background-color: #008300; color: #ffffff; text-align: center;&quot;&gt;Linux(PREEMPT_RT)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 29.186%; height: 17px; text-align: center;&quot;&gt;3000 &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;㎳&lt;/span&gt; ( TaskB &amp;rarr; PrimaryTask )&lt;/td&gt;
&lt;td style=&quot;width: 24.8838%; height: 17px;&quot;&gt;0.024 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 24.186%; height: 17px;&quot;&gt;0.181 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 17px;&quot;&gt;0.028 ㎳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 29.186%; height: 17px; text-align: center;&quot;&gt;3300 &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;㎳&lt;/span&gt; ( PrimaryTask &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;&amp;rarr; TaskB )&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.8838%; height: 17px;&quot;&gt;0.008 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 24.186%; height: 17px;&quot;&gt;0.043 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 17px;&quot;&gt;0.005 ㎳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 29.186%; height: 17px; text-align: center;&quot;&gt;4000 &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;㎳&lt;/span&gt; ( TaskB &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;&amp;rarr; PrimaryTask )&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.8838%; height: 17px;&quot;&gt;0.016 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 24.186%; height: 17px;&quot;&gt;0.115 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 17px;&quot;&gt;0.026 ㎳&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 29.186%; text-align: center;&quot;&gt;4300 &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;㎳&lt;/span&gt; ( PrimaryTask &lt;span style=&quot;background-color: #efefef; color: #333333; text-align: center;&quot;&gt;&amp;rarr; TaskB )&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 24.8838%;&quot;&gt;0.007 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 24.186%;&quot;&gt;0.046 ㎳&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%;&quot;&gt;0.003 ㎳&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Task 전환(Preemption)의 경우 모든 플랫폼에서 뛰어난 응답성&lt;/b&gt;을 보여줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특이하게도 Windows 상에서 성능이 상당히 좋았고, 서로 큰 차이가 안 날 줄 알았던 Linux 간에 의외로 큰 차이를 보였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플랫폼 별 프로그램 실행 터미널 로그&lt;/h3&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;Windows&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;▶ Windows&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&amp;gt; &lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;===&amp;nbsp;TaskManager&amp;nbsp;started&amp;nbsp;=== &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;&amp;nbsp;&amp;nbsp;PrimaryTask&amp;nbsp;:&amp;nbsp;1000ms&amp;nbsp;period&amp;nbsp;&amp;nbsp;priority=0&amp;nbsp;&amp;nbsp;work=300ms &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;&amp;nbsp;&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;2500ms&amp;nbsp;period&amp;nbsp;&amp;nbsp;priority=1&amp;nbsp;&amp;nbsp;work=1300ms &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1000.349ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1000.370ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1300.392ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2000.779ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2000.788ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2300.903ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2501.029ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2501.039ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3001.640ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3001.648ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3001.664ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3301.708ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3301.716ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4001.218ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4001.227ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4001.234ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4301.281ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4301.288ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4402.307ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;wall=1300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5001.253ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5001.262ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5001.310ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5001.312ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5001.319ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5301.415ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5301.423ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;6601.518ms] [Scheduler] finished : TaskB&amp;nbsp;&amp;nbsp;wall=1300ms&amp;nbsp;&amp;nbsp;pending=0&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;WSL Linux (non-preemptRT)&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;▶ WSL Linux (non-preemptRT)&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&amp;gt; &lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[RT]&amp;nbsp;Memory&amp;nbsp;locked&amp;nbsp;(no&amp;nbsp;page&amp;nbsp;faults) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;===&amp;nbsp;TaskManager&amp;nbsp;started&amp;nbsp;=== &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;&amp;nbsp;&amp;nbsp;PrimaryTask&amp;nbsp;:&amp;nbsp;1000ms&amp;nbsp;period&amp;nbsp;&amp;nbsp;priority=0&amp;nbsp;&amp;nbsp;work=300ms &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;&amp;nbsp;&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;2500ms&amp;nbsp;period&amp;nbsp;&amp;nbsp;priority=1&amp;nbsp;&amp;nbsp;work=1300ms &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1000.101ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1000.143ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1300.226ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2000.100ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2000.138ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2300.191ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2500.117ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2500.159ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3000.105ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3000.154ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3000.286ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3300.336ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3300.379ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4000.127ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4000.181ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4000.242ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4300.290ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4300.336ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4400.384ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;wall=1300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.110ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.158ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.276ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.327ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.420ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5300.483ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5300.523ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;6600.570ms] [Scheduler] finished : TaskB&amp;nbsp;&amp;nbsp;wall=1300ms&amp;nbsp;&amp;nbsp;pending=0&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;BoxPC Linux (PREEMPT_RT)&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;▶ BoxPC Linux(PREEMPT_RT)&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&amp;gt; &lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[RT]&amp;nbsp;Memory&amp;nbsp;locked&amp;nbsp;(no&amp;nbsp;page&amp;nbsp;faults) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;===&amp;nbsp;TaskManager&amp;nbsp;started&amp;nbsp;=== &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;&amp;nbsp;&amp;nbsp;PrimaryTask&amp;nbsp;:&amp;nbsp;1000ms&amp;nbsp;period&amp;nbsp;&amp;nbsp;priority=0&amp;nbsp;&amp;nbsp;work=300ms &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;&amp;nbsp;&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;2500ms&amp;nbsp;period&amp;nbsp;&amp;nbsp;priority=1&amp;nbsp;&amp;nbsp;work=1300ms &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[[PrimaryTask]&amp;nbsp;RT&amp;nbsp;priority:&amp;nbsp;80 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;TaskB]&amp;nbsp;RT&amp;nbsp;priority:&amp;nbsp;79 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1000.196ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1000.203ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1300.223ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2000.198ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2000.206ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2300.225ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2500.191ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2500.199ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3000.062ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3000.072ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3000.090ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3300.102ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3300.107ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4000.091ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=TaskB &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4000.097ms]&amp;nbsp;[Scheduler]&amp;nbsp;PREEMPT:&amp;nbsp;suspend&amp;nbsp;TaskB&amp;nbsp;-&amp;gt;&amp;nbsp;run&amp;nbsp;PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4000.117ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4300.151ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4300.154ms]&amp;nbsp;[Scheduler]&amp;nbsp;resume&amp;nbsp;&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;4402.181ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;wall=1300ms&amp;nbsp;&amp;nbsp;pending=0 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.162ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=- &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.172ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;(priority=0) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5000.192ms]&amp;nbsp;[Scheduler]&amp;nbsp;triggered:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;pending=1&amp;nbsp;&amp;nbsp;running=PrimaryTask &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5300.184ms]&amp;nbsp;[Scheduler]&amp;nbsp;finished&amp;nbsp;:&amp;nbsp;PrimaryTask&amp;nbsp;&amp;nbsp;wall=300ms&amp;nbsp;&amp;nbsp;pending=1 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;5300.187ms]&amp;nbsp;[Scheduler]&amp;nbsp;execute&amp;nbsp;:&amp;nbsp;TaskB&amp;nbsp;&amp;nbsp;(priority=1) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #00ff88; text-align: start;&quot;&gt;[t=&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;6600.273ms] [Scheduler] finished : TaskB&amp;nbsp;&amp;nbsp;wall=1300ms&amp;nbsp;&amp;nbsp;pending=0&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;설계 고민&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Task Scheduling 클래스 구조를 설계하면서 많은 고민과 시행착오를 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 고민했던 설계는 QtFramework를 적극 활용하여 Trigger와 TaskExecutor를 디자인 패턴으로 분리하는 구조를 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조의 장점은 Task에 여러 개의 Trigger를 붙일 수 있고, Task가 실행 중이지 않아도 Trigger가 발생하는 구조였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구조는 C++의 세마포어(Semaphore)와 Wait 루프를 이용해 &lt;b&gt;자신의 작업실행 후 다음&lt;/b&gt; &lt;b&gt;Trigger까지 대기하는 구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 순수 C++ 코드로 구조를 단순하고 가볍게 수정하니, 오작동 포인트를 대폭 줄일 수 있었고 복잡성을 줄여 직관적이면서도 빠른 스케줄러를 설계할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;794&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lILQc/dJMcac3Qltu/R1F36cIjkTrlkhnk0s46a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lILQc/dJMcac3Qltu/R1F36cIjkTrlkhnk0s46a1/img.png&quot; data-alt=&quot;Trigger와 TaskExecutor class 를 분리한 초기구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lILQc/dJMcac3Qltu/R1F36cIjkTrlkhnk0s46a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlILQc%2FdJMcac3Qltu%2FR1F36cIjkTrlkhnk0s46a1%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;1349&quot; height=&quot;794&quot; data-origin-width=&quot;1349&quot; data-origin-height=&quot;794&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Trigger와 TaskExecutor class 를 분리한 초기구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 PREEMPT_RT 실시간 성능 PoC 테스트에 이어서, &lt;b&gt;MultiTask 및 Preemptive Scheduler 모듈&lt;/b&gt;을 설계하고 테스트했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 SW &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PLC 프로젝트에 적용하기까지 수많은 예외 처리 및 I/O 제어 등의 과정이 남아있지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;현 단계에서는 만족스러운 결과를 확인할 수 있어 기쁘다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; PREEMPT_RT Kernel에서 마이크로 초(㎲) 단위로 동작하는 프로그램을 개발하면서 C++ 개발에 대한 자부심도 생기고,&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앞으로 RTOS(실시간 운영체제)나 ROS2(로봇 프레임워크) 등의 아키텍처도 깊게 공부해보고 싶어졌다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 관련 글: 2026.04.06 - [개발] - Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776439462495&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&quot; data-og-description=&quot;들어가며최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다. SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.따라서 OS에서 프로세스의 &quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/42&quot; data-og-url=&quot;https://prejudice.tistory.com/42&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xcU28/dJMb85vQLvU/2QsAqf488W7DoyF7kVyTJ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c7agG0/dJMb87NYfao/eor9dLaM7J2Q3F2dGoNQuK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/iXGNj/dJMb9aKGR42/n1jKqL7b32MLvdyIuFESpk/img.png?width=1773&amp;amp;height=452&amp;amp;face=0_0_1773_452&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/42&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xcU28/dJMb85vQLvU/2QsAqf488W7DoyF7kVyTJ0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/c7agG0/dJMb87NYfao/eor9dLaM7J2Q3F2dGoNQuK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/iXGNj/dJMb9aKGR42/n1jKqL7b32MLvdyIuFESpk/img.png?width=1773&amp;amp;height=452&amp;amp;face=0_0_1773_452');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다. SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.따라서 OS에서 프로세스의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;함께보면 좋은 글: 2026.03.19 - [개발] - Real-Time, RTOS, PREEMPT_RT, CPU Isolation 개념 정복 - 실시간 처리&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776439557095&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Real-Time, RTOS, PREEMPT_RT, CPU Isolation 개념 정복 - 실시간 처리&quot; data-og-description=&quot;들어가며'실시간(Real-time)'이라는 용어는 Embedded 및 FactoryAutomation 산업의 핵심이며,현장에서 다양한 관점과 의미로 혼용되곤 한다.또한 자연스럽게 따라오는 '처리 속도'는 모든 SW/HW의 핵심 지표&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/38&quot; data-og-url=&quot;https://prejudice.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/i1nyc/dJMb8PGxS6V/uGGJTjxFB91oD54sm0vWrK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/pKG7R/dJMb8YXNiAQ/tptgTKO2OM4JE54G0yAFy1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/byS0NS/dJMb8PGxS6U/Eae9uZtncRzLKY2oL4fZHK/img.png?width=1710&amp;amp;height=884&amp;amp;face=0_0_1710_884&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/38&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/i1nyc/dJMb8PGxS6V/uGGJTjxFB91oD54sm0vWrK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/pKG7R/dJMb8YXNiAQ/tptgTKO2OM4JE54G0yAFy1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/byS0NS/dJMb8PGxS6U/Eae9uZtncRzLKY2oL4fZHK/img.png?width=1710&amp;amp;height=884&amp;amp;face=0_0_1710_884');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Real-Time, RTOS, PREEMPT_RT, CPU Isolation 개념 정복 - 실시간 처리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며'실시간(Real-time)'이라는 용어는 Embedded 및 FactoryAutomation 산업의 핵심이며,현장에서 다양한 관점과 의미로 혼용되곤 한다.또한 자연스럽게 따라오는 '처리 속도'는 모든 SW/HW의 핵심 지표&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발</category>
      <category>C++</category>
      <category>Linux</category>
      <category>multitasking</category>
      <category>preemptive</category>
      <category>preempt_rt</category>
      <category>RTOS</category>
      <category>Scheduler</category>
      <category>멀티테스킹</category>
      <category>선점형스케줄링</category>
      <category>스케줄러</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/45</guid>
      <comments>https://prejudice.tistory.com/45#entry45comment</comments>
      <pubDate>Sat, 18 Apr 2026 00:26:29 +0900</pubDate>
    </item>
    <item>
      <title>Qt Executor Pattern - moveToThread로 EventLoop 분리하기</title>
      <link>https://prejudice.tistory.com/44</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서 QTimer의 동작 원리를 자세히 살펴보면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Timeout&quot; 이벤트 발화 시점에 EventLoop가 점유되어 발생하는 지연 문제를 테스트 코드로 재현해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.04.09 - [Qt] - QTimer 지연/누락 버그 해결 - EventLoop 점유로 인한 tick 누락 원인과 테스트 코드&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776131087197&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;QTimer 지연/누락 버그 해결 - EventLoop 점유로 인한 tick 누락 원인과 테스트 코드&quot; data-og-description=&quot;들어가며Qt Framework의 최고의 장점은 크로스 플랫폼(Cross-Platform)을 지원하는 풍부한 라이브러리다. 그중에서도 QTimer는 특정 시간마다 반복 실행하거나 시간 관련 기능을 개발할 때 매우 편리하다&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/43&quot; data-og-url=&quot;https://prejudice.tistory.com/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/icqjD/dJMb9bv3DtR/T9FFHaxfFtADNrLsuOkHE1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b7Yr4O/dJMb9effi3W/wPws5ocFXzbUUJCuM4iig0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/icqjD/dJMb9bv3DtR/T9FFHaxfFtADNrLsuOkHE1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b7Yr4O/dJMb9effi3W/wPws5ocFXzbUUJCuM4iig0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;QTimer 지연/누락 버그 해결 - EventLoop 점유로 인한 tick 누락 원인과 테스트 코드&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며Qt Framework의 최고의 장점은 크로스 플랫폼(Cross-Platform)을 지원하는 풍부한 라이브러리다. 그중에서도 QTimer는 특정 시간마다 반복 실행하거나 시간 관련 기능을 개발할 때 매우 편리하다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글의 테스트 코드에서는 EventLoop를 분리하기 위해 &quot;Runner&quot; 인스턴스를 worker 스레드에 넘기는 방법을 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7oF7/dJMcacW1YLd/TtiRkaIZ0NXIY53lqF8MM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7oF7/dJMcacW1YLd/TtiRkaIZ0NXIY53lqF8MM1/img.png&quot; data-alt=&quot;그림1. TestCode UML&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7oF7/dJMcacW1YLd/TtiRkaIZ0NXIY53lqF8MM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7oF7%2FdJMcacW1YLd%2FTtiRkaIZ0NXIY53lqF8MM1%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;442&quot; height=&quot;284&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. TestCode UML&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제는&lt;b&gt; 실제 인스턴스의 위치&lt;/b&gt;(taskManager::runner)와 &lt;b&gt;실행되는 스레드&lt;/b&gt;(workerThread)가 달라 헷갈린다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 클린 코드 작성을 목적으로 &lt;span class=&quot;inline-em&quot;&gt;Executor&lt;/span&gt; 클래스를 도입하여, 분리된 EventLoop 위에서 동작하는 깔끔한 설계를 만들어 보려 한다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Command Pattern&lt;/h2&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;Executor&lt;/span&gt; 클래스를 고려하기 전에, 유사한 개념의 디자인 패턴인 &lt;span class=&quot;inline-em&quot;&gt;Command Pattern&lt;/span&gt;&lt;b&gt; &lt;/b&gt;을 먼저 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 패턴의 &lt;b&gt;첫 번째 장점&lt;/b&gt;은 &quot;행위자&quot;와 &quot;행동&quot;을 분리하여 의존성을 제거하고 재사용성을 높이는 데 있다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCsx9c/dJMcaflV9WI/nYrLfftSLktNSkMMaGJiI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCsx9c/dJMcaflV9WI/nYrLfftSLktNSkMMaGJiI1/img.png&quot; data-alt=&quot;Command / Behavior 의존성 제거&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCsx9c/dJMcaflV9WI/nYrLfftSLktNSkMMaGJiI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCsx9c%2FdJMcaflV9WI%2FnYrLfftSLktNSkMMaGJiI1%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;521&quot; height=&quot;201&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Command / Behavior 의존성 제거&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;b&gt;두 번째 장점&lt;/b&gt;은 행동을 호출하는 객체와 행동의 구현을 완전히 분리하는 것에 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Requester는 CommandA / CommandB의 구현 세부 사항을 알 필요가 없어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZZ7Ex/dJMcabRl0Zo/FStUnt6PxsHi2OpjKWwB60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZZ7Ex/dJMcabRl0Zo/FStUnt6PxsHi2OpjKWwB60/img.png&quot; data-alt=&quot;Command 구현부의 분리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZZ7Ex/dJMcabRl0Zo/FStUnt6PxsHi2OpjKWwB60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZZ7Ex%2FdJMcabRl0Zo%2FFStUnt6PxsHi2OpjKWwB60%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;681&quot; height=&quot;201&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Command 구현부의 분리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; Producer-Consumer Pattern &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;vs&lt;/b&gt; &lt;/span&gt;Command Pattern&lt;br /&gt;설계 패턴 중 프로듀서-컨슈머 패턴이란, 멀티스테드 환경에서 데이터를 생성하는 Producer와 데이터를 처리하는 Consumer를 큐로 분리하여 데이터를 순차적으로 처리하는 패턴이다.&lt;br /&gt;&lt;br /&gt;서로 비슷한 느낌이지만 이 패턴의 핵심 목적은 &lt;b&gt;큐를 통해 비동기 작업을 처리&lt;/b&gt;한다는 것에 있고,&lt;br /&gt;커맨드 패턴은 &lt;b&gt;행동을 캡슐화하여 Requester-Behavior를 분리&lt;/b&gt;한다는 데에 미묘한 차이가 있다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Executor 객체를 이용한 Thread 분리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Executor Pattern&lt;/b&gt;은&amp;nbsp;작업의 정의와 실행 주체를 분리하는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Command Pattern을 예로 들면, Command 역할을 하는 &lt;span class=&quot;inline-em&quot;&gt;Task&lt;/span&gt;&amp;nbsp;객체와 이를 실제로 실행하는 &lt;span class=&quot;inline-em&quot;&gt;Executor&lt;/span&gt; 객체를 분리하는 구조라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1jmlz/dJMcadVUQ4A/romarBYBbjKhKat2ZKPxDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1jmlz/dJMcadVUQ4A/romarBYBbjKhKat2ZKPxDk/img.png&quot; data-alt=&quot;Executor 실행 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1jmlz/dJMcadVUQ4A/romarBYBbjKhKat2ZKPxDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1jmlz%2FdJMcadVUQ4A%2FromarBYBbjKhKat2ZKPxDk%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;651&quot; height=&quot;285&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Executor 실행 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 분리되면 &lt;span class=&quot;inline-em&quot;&gt;QObject::moveToThread()&lt;/span&gt; 를 사용하여 &lt;span class=&quot;inline-em&quot;&gt;Executor&lt;/span&gt; 객체의 thread affinity를 변경함으로써,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 스레드의 EventLoop에서 동작하도록 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;Requester가 속한 스레드와 Executor가 동작하는 스레드를 분리&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조의 장점은 두 가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Request&quot;는 작업 실행을 &quot;Executor&quot;에 위임하므로 &lt;b&gt;두 객체 간의 의존성이 감소&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&quot;Task&quot;는 &quot;Requester&quot; 인스턴스 안에 존재하므로 &lt;b&gt;코드 가독성과 일관성이 향상&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 살펴본 &lt;span class=&quot;inline-em&quot;&gt;그림1. TestCode&lt;/span&gt; 에 적용하면 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exR1wj/dJMcadhlxOD/Fy0jm94IvBIQDkk4u7j2Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exR1wj/dJMcadhlxOD/Fy0jm94IvBIQDkk4u7j2Dk/img.png&quot; data-alt=&quot;TestCode + Executor UML&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exR1wj/dJMcadhlxOD/Fy0jm94IvBIQDkk4u7j2Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexR1wj%2FdJMcadhlxOD%2FFy0jm94IvBIQDkk4u7j2Dk%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;451&quot; height=&quot;412&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TestCode + Executor UML&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 코드를 리팩토링 하며 &lt;b&gt;Executor&lt;/b&gt; 구조를 통해 스레드를 분리하고 안정화하는 작업을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅은 설계를 깔끔하게 하는 것을 위주로 작성했지만, 실제 진행 중인 프로젝트에서는 다른 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 프로젝트 안에서 &lt;b&gt;Task&lt;/b&gt;가 &lt;b&gt;Executor &lt;/b&gt;역할까지 담당하면서 main EventLoop 위에서 동작하고 있었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 시점에 다른 비동기 Slot 처리로 인해 실행 지연이 발생하는 버그를 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 발생한 버그와 Executor 구조는 전혀 다른 내용처럼 보이지만, 버그가 발생한 배경과 EventLoop와의 관계를 공부하다 보니 자연스럽게 &quot;Executor 구조를 통해 의존성을 낮추는 설계&quot;를 주제로 글을 쓰게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅을 준비하면서 개념을 더욱 확실하게 정리할 수 있었고, Command-Behavior 패턴과 Executor 구조는 앞으로도 유용하게 활용할 수 있을 것 같다.&lt;/p&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;함께 볼만한 글 : 2026.01.23 - [Qt] - Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776141228644&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지&quot; data-og-description=&quot;들어가며Qt는 크로스 플랫폼 애플리케이션 개발 프레임워크로,동일한 코드로 Windows, Linux, macOS, Embedded Linux 등 다양한 운영체제에서 동작하는 App을 만들 수 있다. QObject, Signal/Event, QTimer 등을 사용&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/22&quot; data-og-url=&quot;https://prejudice.tistory.com/22&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Fqsc9/dJMb87NXOwu/cLPyedLFdkCu5Z8KoHmK80/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/vEaRW/dJMb89yeWK4/uoZXmMgyVh99p1tFHYRT5k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/22&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Fqsc9/dJMb87NXOwu/cLPyedLFdkCu5Z8KoHmK80/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/vEaRW/dJMb89yeWK4/uoZXmMgyVh99p1tFHYRT5k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며Qt는 크로스 플랫폼 애플리케이션 개발 프레임워크로,동일한 코드로 Windows, Linux, macOS, Embedded Linux 등 다양한 운영체제에서 동작하는 App을 만들 수 있다. QObject, Signal/Event, QTimer 등을 사용&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;함께 볼만한 글 : 2025.12.31 - [개발] - 개발 실무에서 UML을 어디까지 그려야 할까?&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776141265671&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;개발 실무에서 UML을 어디까지 그려야 할까?&quot; data-og-description=&quot;들어가며실무에서 UML을 그려야 하는 상황에 부딪히면 다음과 같은 고민이 생긴다.&amp;quot;이걸 어디까지 그려야 하지?&amp;quot;, &amp;quot;UML을 잘 그리는 방법이 뭐지?&amp;quot;실무에서 내가 선택한 기준과 작성방법을 정리해 &quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/16&quot; data-og-url=&quot;https://prejudice.tistory.com/16&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hudys/dJMb84XZ5LQ/78Q20oYryxzL7OLOYmn7gK/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Mp0BC/dJMb9kmd7SS/XmdGO0JBP6VPoLVAz5iNUk/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rgfZp/dJMb9aKGslE/v0BIpH898c0WVSn0MHILD0/img.png?width=1708&amp;amp;height=774&amp;amp;face=0_0_1708_774&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/16&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hudys/dJMb84XZ5LQ/78Q20oYryxzL7OLOYmn7gK/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Mp0BC/dJMb9kmd7SS/XmdGO0JBP6VPoLVAz5iNUk/img.jpg?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rgfZp/dJMb9aKGslE/v0BIpH898c0WVSn0MHILD0/img.png?width=1708&amp;amp;height=774&amp;amp;face=0_0_1708_774');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;개발 실무에서 UML을 어디까지 그려야 할까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며실무에서 UML을 그려야 하는 상황에 부딪히면 다음과 같은 고민이 생긴다.&quot;이걸 어디까지 그려야 하지?&quot;, &quot;UML을 잘 그리는 방법이 뭐지?&quot;실무에서 내가 선택한 기준과 작성방법을 정리해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;/p&gt;</description>
      <category>Qt</category>
      <category>C++</category>
      <category>command pattern</category>
      <category>eventLoop</category>
      <category>Executor Pattern</category>
      <category>moveToThread</category>
      <category>QT</category>
      <category>QTimer</category>
      <category>thread</category>
      <category>디자인패턴</category>
      <category>멀티스레드</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/44</guid>
      <comments>https://prejudice.tistory.com/44#entry44comment</comments>
      <pubDate>Tue, 14 Apr 2026 14:49:20 +0900</pubDate>
    </item>
    <item>
      <title>QTimer 지연/누락 버그 해결 - EventLoop 점유로 인한 tick 누락 원인과 테스트 코드</title>
      <link>https://prejudice.tistory.com/43</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Qt Framework의 최고의 장점은 크로스 플랫폼(Cross-Platform)을 지원하는 풍부한 라이브러리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서도 QTimer는 &lt;b&gt;특정 시간마다 반복 실행하거나 시간 관련 기능을 개발&lt;/b&gt;할 때 매우 편리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 SW 개발 중 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;시간 관련 기능을 테스트하다 버그를 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머가 100㎳마다 실행되어야 함에도 약 115㎳ 주기로 실행되고 있었고, 결국 &lt;b&gt;실행 횟수 누락&lt;/b&gt;이 발생하는 버그였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 버그를 잡기 위해 공부한 QTimer 심화 내용을 정리하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QTimer에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;횟수 누락이 발생하는 테스트 코드를 만들어 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;QTimer 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QTimer는 &lt;b&gt;QEventLoop&lt;/b&gt; 위에서 동작하며, 객체 간 소통을 위해 &lt;b&gt;Signal/ Slot&lt;/b&gt; 구조를 사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: GungSeo, serif;&quot;&gt;Event Loop의 동작 원리는 이전 글에서 다룬 바 있다.&lt;/span&gt;&lt;br /&gt;&lt;a title=&quot;2026.01.23 - [Qt] - Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지&quot; href=&quot;http://2026.01.23%20- [Qt] - Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.01.23 - [Qt] - Qt Event Loop 동작 원리 정리 ㅡ 타이머, 이벤트, 스레드 까지&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QTimer로 개발하기 쉬운 대표적인 두 가지 사례는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 시간(Interval)마다 반복되는 동작&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b86IDw/dJMcaflSYz6/HzDDnieBs3noThOL3c7Wxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b86IDw/dJMcaflSYz6/HzDDnieBs3noThOL3c7Wxk/img.png&quot; data-alt=&quot;그림1. 100㎳ 간격으로 동작하는 Timer 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b86IDw/dJMcaflSYz6/HzDDnieBs3noThOL3c7Wxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb86IDw%2FdJMcaflSYz6%2FHzDDnieBs3noThOL3c7Wxk%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;542&quot; height=&quot;100&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. 100㎳ 간격으로 동작하는 Timer 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;N초 지연 후 실행&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sFoS2/dJMcaf7eC8m/weSKzsstoXFMrCJ2HQ4oW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sFoS2/dJMcaf7eC8m/weSKzsstoXFMrCJ2HQ4oW0/img.png&quot; data-alt=&quot;그림2. 100㎳ 지연 후 동작하는 Timer 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sFoS2/dJMcaf7eC8m/weSKzsstoXFMrCJ2HQ4oW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsFoS2%2FdJMcaf7eC8m%2FweSKzsstoXFMrCJ2HQ4oW0%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;542&quot; height=&quot;100&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. 100㎳ 지연 후 동작하는 Timer 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;QTimer 동작 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복 타이머를 직접 만들어 본다고 생각하면 더 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림1 상황에 task만 고려할 때, task 종료 시 &lt;span class=&quot;inline-em&quot;&gt;sleep(반복주기 - task 소요시간)&lt;/span&gt; 로 이론적인 타이머를 구현할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;101&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRGkVs/dJMcacCHMqD/JvTDRlhIYJnO4fGfXQtnz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRGkVs/dJMcacCHMqD/JvTDRlhIYJnO4fGfXQtnz0/img.png&quot; data-alt=&quot;그림3. sleep으로 구현한 Interval Tiemr&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRGkVs/dJMcacCHMqD/JvTDRlhIYJnO4fGfXQtnz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRGkVs%2FdJMcacCHMqD%2FJvTDRlhIYJnO4fGfXQtnz0%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;542&quot; height=&quot;101&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;101&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. sleep으로 구현한 Interval Tiemr&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 실제로는 100㎳에 정확히 호출되지 않고 미세한 &lt;span class=&quot;inline-em&quot;&gt;Jitter&lt;/span&gt; 가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 sleep시간을 계산할 때는 &lt;b&gt;예정된 시간을 기준&lt;/b&gt;으로&amp;nbsp; 계산해야 하며, Qt Timer도 이 방식을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TBp82/dJMcacvWJkC/fJgZUpIISHOgCZkX7OL8k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TBp82/dJMcacvWJkC/fJgZUpIISHOgCZkX7OL8k0/img.png&quot; data-alt=&quot;그림4. Jitter를 고려한 Interval Timer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TBp82/dJMcacvWJkC/fJgZUpIISHOgCZkX7OL8k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTBp82%2FdJMcacvWJkC%2FfJgZUpIISHOgCZkX7OL8k0%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;561&quot; height=&quot;100&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. Jitter를 고려한 Interval Timer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QTimer는 여기서 한 가지를 더 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Signal / Slot으로 전달되는 QTimer는 EventLoop까지 포함하면 다음과 같이 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYKxkz/dJMb9962otD/pXbMSgSd4YrAgDXDaIozU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYKxkz/dJMb9962otD/pXbMSgSd4YrAgDXDaIozU1/img.png&quot; data-alt=&quot;그림5. Qt EventLoop가 포함된 QTimer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYKxkz/dJMb9962otD/pXbMSgSd4YrAgDXDaIozU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYKxkz%2FdJMb9962otD%2FpXbMSgSd4YrAgDXDaIozU1%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;561&quot; height=&quot;100&quot; data-origin-width=&quot;561&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. Qt EventLoop가 포함된 QTimer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: GungSeo, serif;&quot;&gt;*차트에서 Jitter와 QEvetnLoop를 크게 표현했지만, 실제로는&amp;nbsp;매우 짧은 시간 동안 발생한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;Qt6 Precise Timer (QChronoTimer)&lt;/h2&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;그림5&lt;/span&gt; 에서 발생할 수 있는 문제는, 짧은 task 응답(㎲단위)을 받기에&lt;b&gt;&amp;nbsp;QEventLoop가&lt;/b&gt;&amp;nbsp;&lt;b&gt;우리 생각처럼 빠르지 않다&lt;/b&gt;는 점에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 QTimer는 밀리초(㎳) 단위의 제어를 목표로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;CPU 전력 효율을 위해 타이머를 묶어서 처리하기 때문에 ㎲(마이크로초) 단위의 오차가 발생한다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;Qt::PreciseTimer&lt;/span&gt; 옵션은 Qt5부터 제공되는 옵션으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머의 &lt;b&gt;전력 효율 최적화를 비활성화해&lt;/b&gt; &lt;b&gt;더 정밀한 EventLoop 호출&lt;/b&gt;을 가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 그럼에도 불구하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;㎳ 단위의 정밀도를 갖는 QTimer로는&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt; 100㎲ Interval 같은 고정밀 동작을 수행하지는 못한다는 한계가&lt;/b&gt; 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 한계를 극복하기 위해 Qt6.8에서 도입된 것이 &lt;/span&gt;&lt;span class=&quot;inline-em&quot;&gt;QChronoTimer&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; Class이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;QChronoTimer는 내부적으로 &lt;b&gt;nanosecond 정밀도를 지원&lt;/b&gt;하므로, 정밀한 타이머 동작이 필요한 경우 이를 사용해야 한다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;QTimer 누적 오차 Trouble Shooting&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 정리한 내용을 바탕으로 재현 가능한 오류 시나리오를 구성해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;구조&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span class=&quot;inline-em&quot;&gt;main Thread&lt;/span&gt; 와 &lt;span class=&quot;inline-em&quot;&gt;m_runner Thread &lt;/span&gt; 로 구성 (2개의 EventLoop)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;main 역할1 :&lt;/b&gt; 100㎳ 마다 &lt;span class=&quot;inline-em&quot;&gt;m_runner::runProcess() &lt;/span&gt; Slot 호출&lt;/li&gt;
&lt;li&gt;&lt;b&gt;main 역할2 :&lt;/b&gt; &lt;span class=&quot;inline-em&quot;&gt;m_runner::taskFinished() &lt;/span&gt; Signal 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;m_runner 역할 :&lt;/b&gt; &lt;span class=&quot;inline-em&quot;&gt;runProcess()&lt;/span&gt; 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그가 발생하도록 각 단계에 지연 시간을 추가한 구조는 다음 이미지와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tHTWR/dJMcacJuhLf/kcWudXIY0S7jkO4yoclKT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tHTWR/dJMcacJuhLf/kcWudXIY0S7jkO4yoclKT0/img.png&quot; data-alt=&quot;지연시간을 추가한 요약구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tHTWR/dJMcacJuhLf/kcWudXIY0S7jkO4yoclKT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtHTWR%2FdJMcacJuhLf%2FkcWudXIY0S7jkO4yoclKT0%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;362&quot; height=&quot;151&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;지연시간을 추가한 요약구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로그램의 문제는 &lt;span class=&quot;inline-em&quot;&gt;onTaskFinished&lt;/span&gt; &lt;b&gt;가 MainThread의 EventLoop를 점유&lt;/b&gt;해 QTimer의 발화를 방해하는 것에 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beyG98/dJMcahKMNNI/ksk7fBwHksnukrBDS5lSSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beyG98/dJMcahKMNNI/ksk7fBwHksnukrBDS5lSSK/img.png&quot; data-alt=&quot;Example Timing Diagram&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beyG98/dJMcahKMNNI/ksk7fBwHksnukrBDS5lSSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeyG98%2FdJMcahKMNNI%2Fksk7fBwHksnukrBDS5lSSK%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;612&quot; height=&quot;170&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Example Timing Diagram&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림처럼 QTimer timeout 처리가 지연되고, 그것이 누적되어 &lt;b&gt;tick 누락&lt;/b&gt;으로 이어질 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;QTimer 누적 오차 결과 및 TestCode&lt;/h2&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;tick&amp;nbsp;&amp;nbsp;elapsed_ms&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;expected_ms&amp;nbsp;&amp;nbsp;&amp;nbsp;drift_ms &lt;br /&gt;----&amp;nbsp;&amp;nbsp;----------&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-----------&amp;nbsp;&amp;nbsp;&amp;nbsp;-------- &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;101&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;100&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;216&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;200&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;16 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;331&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;300&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;31 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;501&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;400&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;101&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;...&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;=== Result ===&lt;br /&gt;Total&amp;nbsp;elapsed&amp;nbsp;:&amp;nbsp;10015&amp;nbsp;ms&lt;br /&gt;Expected&amp;nbsp;ticks:&amp;nbsp;100&lt;br /&gt;Actual&amp;nbsp;ticks&amp;nbsp;&amp;nbsp;:&amp;nbsp;76&lt;br /&gt;Missing&amp;nbsp;ticks&amp;nbsp;:&amp;nbsp;24&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트에서도 실제 QTimer::timeout 처리가 밀리는 것과, timeout이 누락되는 현상을 명확히 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;main.cpp&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1775716080042&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;QCoreApplication&amp;gt;
#include &amp;lt;QElapsedTimer&amp;gt;
#include &amp;lt;QTextStream&amp;gt;
#include &amp;lt;QThread&amp;gt;
#include &amp;lt;QTimer&amp;gt;
#include &amp;lt;QMutex&amp;gt;
#include &amp;lt;QMutexLocker&amp;gt;
#include &amp;lt;QMetaObject&amp;gt;

// ── Worker ─────────────────────────────────────────
class Runner : public QObject
{
    Q_OBJECT
public:
    explicit Runner(QObject *parent = nullptr) : QObject(parent) {}

public slots:
    void runProcess()
    {
        // Simulation (WorkerThread &amp;mdash; no impact on MainThread)
        QElapsedTimer t;
        t.start();
        volatile double acc = 0.0;
        for (long long i = 1; t.elapsed() &amp;lt; 70; ++i) // delay 70ms
            acc += 1.0 / static_cast&amp;lt;double&amp;gt;(i);
        (void)acc;

        emit taskFinished();
    }

signals:
    void taskFinished();
};

// ── Main Thread ─────────────────────────────────────────────────────
class TaskManager : public QObject
{
    Q_OBJECT
public:
    explicit TaskManager(QObject *parent = nullptr)
        : QObject(parent)
        , m_cycleTrigger(new QTimer(this))
        , m_stopTimer(new QTimer(this))
        , m_runner(new Runner())
        , m_tickCount(0)
        , m_busy(false)
    {
        // CycleTrigger: QTimer in MainThread
        m_cycleTrigger-&amp;gt;setInterval(100);
        connect(m_cycleTrigger, SIGNAL(timeout()), this, SLOT(onTaskTriggered()));

        // worker thread
        m_runner-&amp;gt;moveToThread(&amp;amp;m_workerThread);
        connect(&amp;amp;m_workerThread, SIGNAL(finished()), m_runner, SLOT(deleteLater()));
        connect(m_runner, SIGNAL(taskFinished()), this, SLOT(onTaskFinished())); // QueuedConnection

        // 10 seconds after stop
        m_stopTimer-&amp;gt;setSingleShot(true);
        m_stopTimer-&amp;gt;setInterval(10000);
        connect(m_stopTimer, SIGNAL(timeout()), this, SLOT(onStop()));
    }

    void start()
    {
        QTextStream out(stdout);
        out &amp;lt;&amp;lt; &quot;Interval      : 100 ms  (CycleTrigger, MainThread)\n&quot;;
        out &amp;lt;&amp;lt; &quot;Duration      : 10 s\n&quot;;
        out &amp;lt;&amp;lt; &quot;Expected ticks: 100\n&quot;;
        out &amp;lt;&amp;lt; &quot;runProcess    : ~70 ms  (WorkerThread, no impact on MainThread)\n&quot;;
        out &amp;lt;&amp;lt; &quot;onTaskFinished   : ~45 ms  (MainThread blocking)\n&quot;;
        out &amp;lt;&amp;lt; QString(&quot;%1\t%2\t%3\t%4\n&quot;)
                   .arg(&quot;tick&quot;, 4)
                   .arg(&quot;elapsed_ms&quot;, 10)
                   .arg(&quot;expected_ms&quot;, 11)
                   .arg(&quot;drift_ms&quot;,    8);
        out &amp;lt;&amp;lt; &quot;----\t----------\t-----------\t--------\n&quot;;
        out.flush();

        m_workerThread.start();
        m_elapsed.start();
        m_stopTimer-&amp;gt;start();
        m_cycleTrigger-&amp;gt;start();
    }

private slots:
    // ── CycleTrigger (main Thread) ──────────────────────────────────
    void onTaskTriggered()
    {
        if (m_busy) {
            // just skip if the main thread is busy (simulate tick drop)
            return;
        }
        m_busy = true;
        ++m_tickCount;

        const qint64 actualMs   = m_elapsed.elapsed();
        const qint64 expectedMs = static_cast&amp;lt;qint64&amp;gt;(m_tickCount) * 100;
        const qint64 driftMs    = actualMs - expectedMs;

        QTextStream out(stdout);
        out &amp;lt;&amp;lt; QString(&quot;%1\t%2\t%3\t%4\n&quot;)
                   .arg(m_tickCount, 4)
                   .arg(actualMs,    10)
                   .arg(expectedMs,  11)
                   .arg(driftMs,     8);
        out.flush();

        // RunProcess is executed in the worker thread (no impact on main timer)
        QMetaObject::invokeMethod(m_runner, &quot;runProcess&quot;, Qt::QueuedConnection);
    }

    // ── Worker finished (main Thread, QueuedConnection) ───────────────────
    void onTaskFinished()
    {
        // syncOutputs + monitor update: main thread blocking
        syncOutputs();
        updateMonitor();

        m_busy = false;
    }

    void onStop()
    {
        m_cycleTrigger-&amp;gt;stop();

        const qint64 totalMs = m_elapsed.elapsed();
        QTextStream out(stdout);
        out &amp;lt;&amp;lt; &quot;\n=== Result ===\n&quot;;
        out &amp;lt;&amp;lt; &quot;Total elapsed : &quot; &amp;lt;&amp;lt; totalMs     &amp;lt;&amp;lt; &quot; ms\n&quot;;
        out &amp;lt;&amp;lt; &quot;Expected ticks: 100\n&quot;;
        out &amp;lt;&amp;lt; &quot;Actual ticks  : &quot; &amp;lt;&amp;lt; m_tickCount &amp;lt;&amp;lt; &quot;\n&quot;;
        out &amp;lt;&amp;lt; &quot;Missing ticks : &quot; &amp;lt;&amp;lt; (100 - m_tickCount) &amp;lt;&amp;lt; &quot;\n&quot;;
        out.flush();

        m_workerThread.quit();
        m_workerThread.wait();
        QCoreApplication::quit();
    }

private:
    void syncOutputs()
    {
        QElapsedTimer t; t.start();
        volatile double acc = 0.0;
        for (long long i = 1; t.elapsed() &amp;lt; 40; ++i)
            acc += 1.0 / static_cast&amp;lt;double&amp;gt;(i);
        (void)acc;
    }

    void updateMonitor()
    {
        // monitor update (lightweight task)
        QElapsedTimer t; t.start();
        volatile double acc = 0.0;
        for (long long i = 1; t.elapsed() &amp;lt; 5; ++i)
            acc += 1.0 / static_cast&amp;lt;double&amp;gt;(i);
        (void)acc;
    }

    QTimer        *m_cycleTrigger;
    QTimer        *m_stopTimer;
    Runner        *m_runner;
    QThread        m_workerThread;
    QElapsedTimer  m_elapsed;
    int            m_tickCount;
    bool           m_busy;
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    TaskManager manager;
    manager.start();

    return app.exec();
}

#include &quot;main.moc&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 버그는 테스트 간 시간 측정을 비교하는 과정에서 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QTimer 관련 버그를 만났다면, 대부분 수많은 Signal/Slot이 얽혀 있고 아키텍처가 이미 커진 상황일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;규모가 커진 프로젝트는 디버깅이 어렵기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동일한 문제를 재현하는 작은 테스트 프로젝트를 별도로 만드는 것이 오히려 더 빠를 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 트터블슈팅을 통해 QEventLoop 사용 시 고려사항을 되짚어 볼 수 있었고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEventLoop와 QTimer의 동작 원리, 나아가 Qt6.8의 QChronoTimer까지 배울 수 있어서 재미있게 풀 수 있었다.&lt;/p&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;</description>
      <category>Qt</category>
      <category>QChronoTimer</category>
      <category>QEventLoop란</category>
      <category>QT</category>
      <category>Qt6.8</category>
      <category>Qt::PreciseTimer</category>
      <category>QTimer</category>
      <category>QTimer timeout 안됨</category>
      <category>QTimer 누락</category>
      <category>QTimer 동작 원리</category>
      <category>QTimer 오차</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/43</guid>
      <comments>https://prejudice.tistory.com/43#entry43comment</comments>
      <pubDate>Thu, 9 Apr 2026 16:46:25 +0900</pubDate>
    </item>
    <item>
      <title>Linux PREEMPT_RT 실시간성(Jitter) 측정 및 성능 비교 - RTOS</title>
      <link>https://prejudice.tistory.com/42</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 SW PLC에서 관심 갖고 개발 중인 내용은 Linux PREEMPT_RT 커널을 이용해 실시간성을 보장하는 작업이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SW PLC Runtime은 특정 Task를 매 주기마다 실행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;OS에서 프로세스의 실행을 보장받지 못하거나, 처리 속도가 들쭉날쭉 하지 않도록 Jitter를 관리&lt;/b&gt;하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PREEMPT_RT 커널로 실시간성을 확보하고 테스트하는 일련의 과정을 정리해 보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;용어 및 개념정리 : 2026.03.19 - [개발] - Real-Time, RTOS, PREEMPT_RT, CPU Isolation 개념 정복 - 실시간 처리&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775450232349&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Real-Time, RTOS, PREEMPT_RT, CPU Isolation 개념 정복 - 실시간 처리&quot; data-og-description=&quot;들어가며'실시간(Real-time)'이라는 용어는 Embedded 및 FactoryAutomation 산업의 핵심이며,현장에서 다양한 관점과 의미로 혼용되곤 한다.또한 자연스럽게 따라오는 '처리 속도'는 모든 SW/HW의 핵심 지표&quot; data-og-host=&quot;prejudice.tistory.com&quot; data-og-source-url=&quot;https://prejudice.tistory.com/38&quot; data-og-url=&quot;https://prejudice.tistory.com/38&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g1ntC/dJMb82eNFXn/KssX2wSqIkSzysdjSfA7Kk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/0G39g/dJMb83Sjt9G/skrxkdyLV7oZaTq3AdMTqK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cECuOp/dJMb83Sjt9F/rvaLX1XyukP1debffM0kZ0/img.png?width=1710&amp;amp;height=884&amp;amp;face=0_0_1710_884&quot;&gt;&lt;a href=&quot;https://prejudice.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://prejudice.tistory.com/38&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g1ntC/dJMb82eNFXn/KssX2wSqIkSzysdjSfA7Kk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/0G39g/dJMb83Sjt9G/skrxkdyLV7oZaTq3AdMTqK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cECuOp/dJMb83Sjt9F/rvaLX1XyukP1debffM0kZ0/img.png?width=1710&amp;amp;height=884&amp;amp;face=0_0_1710_884');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Real-Time, RTOS, PREEMPT_RT, CPU Isolation 개념 정복 - 실시간 처리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가며'실시간(Real-time)'이라는 용어는 Embedded 및 FactoryAutomation 산업의 핵심이며,현장에서 다양한 관점과 의미로 혼용되곤 한다.또한 자연스럽게 따라오는 '처리 속도'는 모든 SW/HW의 핵심 지표&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;prejudice.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;테스트 시나리오 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;㎱(나노초), ㎲(마이크로초) 단위로 처리하는 Real-Time을 제대로 구현하고 평가하기 위해 테스트 설계 과정이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 커널단에서 처리하는 과정은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p55Tv/dJMb990gwQN/MkNn5E0pNEl0E2oHvL9KU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p55Tv/dJMb990gwQN/MkNn5E0pNEl0E2oHvL9KU0/img.png&quot; data-alt=&quot;Linux 커널 처리 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p55Tv/dJMb990gwQN/MkNn5E0pNEl0E2oHvL9KU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp55Tv%2FdJMb990gwQN%2FMkNn5E0pNEl0E2oHvL9KU0%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;612&quot; height=&quot;90&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Linux 커널 처리 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHZYRA/dJMcahcS9AS/h9sUfqGklY1WCKxdvFvmBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHZYRA/dJMcahcS9AS/h9sUfqGklY1WCKxdvFvmBK/img.png&quot; data-alt=&quot;PREEMPT_RT 커널 처리 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHZYRA/dJMcahcS9AS/h9sUfqGklY1WCKxdvFvmBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHZYRA%2FdJMcahcS9AS%2Fh9sUfqGklY1WCKxdvFvmBK%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;602&quot; height=&quot;90&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;PREEMPT_RT 커널 처리 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-alt=&quot;PREEMPT_RT Kernel의 Inturrupt 처리 타임라인&quot; data-phocus=&quot;https://blog.kakaocdn.net/dna/q1HCE/dJMcag5Tluo/AAAAAAAAAAAAAAAAAAAAAAMcPDZoMFb_JstXaTR1iyLYQFk_O_2s40hsKO3R6TMc/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1777561199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=AzniDFR5x1gCA8rHcxdQ0Zq7mhs%3D&quot; data-url=&quot;https://blog.kakaocdn.net/dna/q1HCE/dJMcag5Tluo/AAAAAAAAAAAAAAAAAAAAAAMcPDZoMFb_JstXaTR1iyLYQFk_O_2s40hsKO3R6TMc/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1777561199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=AzniDFR5x1gCA8rHcxdQ0Zq7mhs%3D&quot;&gt;&lt;/span&gt;테스트 목표는 &lt;b&gt;Interrupt가 발생한 시점부터 Test Program(=TaskB) 이 호출될 때까지의 Jitter를 측정하고 비교하는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 달성을 위해 다음과 같이 테스트를 설계했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;HW&lt;/b&gt; &lt;b&gt;: &lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Linux PREEMPT_RT 커널이 올라간 BoxPC&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;측정 방법 :&lt;/b&gt; TaskB를 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;10㎳ 주기로 &lt;/span&gt;10,000회 반복 호출하며 깨어날 때의 시간을 정밀 측정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;측정 환경 :&lt;/b&gt; ① stress-ng 툴로 CPU와 I/O에 강제 부하 인가&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;② QTimer, non-RT Kernel, PREEMPT_RT Kernel 각각의 조건에서 실행 후 비교&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예상 결과 :&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;☐ QTimer는 내부의 QEventLoop 구조로 인해 Linux Basic Kernel 보다 Jitter가 클 것이다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;☐ 강력한 시스템 부하로 인해 QTimer와 non-RT Kernel의 Jitter는 변동폭이 클 것이다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;☐ 반면 PREEMPT_RT Kernel의 Jitter는 극한의 상황에서도 안정적인 수준을 유지할 것이다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;QTimer 프로그램 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Qt6를 설치하고 QTimer를 이용해 10㎳마다 timeout 되도록 프로그램을 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;Qt6.8에 도입된 &lt;/b&gt;Qt::PreciseTimer&lt;b&gt; 를 사용&lt;/b&gt;해 Jitter 특성을 최소화했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(*Precise Timer : &lt;a href=&quot;https://doc.qt.io/qt-6/ko/qtimer.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://doc.qt.io/qt-6/ko/qtimer.html&lt;/a&gt;)&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1775453547092&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;QCoreApplication&amp;gt;
#include &amp;lt;QFile&amp;gt;
#include &amp;lt;QTextStream&amp;gt;
#include &amp;lt;QTimer&amp;gt;
#include &amp;lt;chrono&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

struct LogEntry {
    long long tick;
    double actualRunTimeMs;
    double predictMs;
    double gitterMs;
};

int main(int argc, char* argv[]) {
    QCoreApplication app(argc, argv);

    constexpr double periodMs = 10.0;
    constexpr long long totalTicks = 10000;

    // 로그 버퍼 사전 할당
    std::vector&amp;lt;LogEntry&amp;gt; logBuffer;
    logBuffer.reserve(totalTicks);

    long long tick = 0;
    const auto startTime = std::chrono::steady_clock::now();

    QTimer timer;
    timer.setTimerType(Qt::PreciseTimer);
    timer.setInterval(static_cast&amp;lt;int&amp;gt;(periodMs));

    QObject::connect(&amp;amp;timer, &amp;amp;QTimer::timeout, [&amp;amp;]() {
        ++tick;

        const auto now = std::chrono::steady_clock::now();
        const std::chrono::duration&amp;lt;double, std::milli&amp;gt; elapsed = now - startTime;

        const double actualRunTimeMs = elapsed.count();
        const double predictMs = tick * periodMs;
        const double gitterMs = actualRunTimeMs - predictMs;

        // I/O 없이 메모리 버퍼에만 기록
        logBuffer.push_back({tick, actualRunTimeMs, predictMs, gitterMs});

        if (tick &amp;gt;= totalTicks) {
            timer.stop();
            app.quit();
        }
    });

    timer.start();
    std::cout &amp;lt;&amp;lt; &quot;Started 10ms periodic timer. Logging to timer_log.csv&quot; &amp;lt;&amp;lt; std::endl;

    app.exec();

    // 타이머 루프 종료 후 한 번에 파일 기록
    std::cout &amp;lt;&amp;lt; &quot;Timer stopped after &quot; &amp;lt;&amp;lt; tick &amp;lt;&amp;lt; &quot; ticks. Writing log...&quot; &amp;lt;&amp;lt; std::endl;

    QFile logFile(&quot;timer_log.csv&quot;);
    if (!logFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
        std::cerr &amp;lt;&amp;lt; &quot;Failed to open timer_log.csv&quot; &amp;lt;&amp;lt; std::endl;
        return 1;
    }

    QTextStream stream(&amp;amp;logFile);
    stream &amp;lt;&amp;lt; &quot;index,actual_run_time_ms,predict_ms,gitter_ms\n&quot;;
    for (const auto&amp;amp; e : logBuffer) {
        stream &amp;lt;&amp;lt; e.tick &amp;lt;&amp;lt; &quot;,&quot;
               &amp;lt;&amp;lt; QString::number(e.actualRunTimeMs, 'f', 3) &amp;lt;&amp;lt; &quot;,&quot;
               &amp;lt;&amp;lt; QString::number(e.predictMs, 'f', 3) &amp;lt;&amp;lt; &quot;,&quot;
               &amp;lt;&amp;lt; QString::number(e.gitterMs, 'f', 3) &amp;lt;&amp;lt; &quot;\n&quot;;
    }

    std::cout &amp;lt;&amp;lt; &quot;Log written to timer_log.csv (&quot; &amp;lt;&amp;lt; logBuffer.size() &amp;lt;&amp;lt; &quot; entries).&quot; &amp;lt;&amp;lt; std::endl;
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;PREEMPT_RT 프로그램 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PREEMPT_RT 프로그램도 QTimer와 동일하게,&lt;br /&gt;&lt;b&gt;10㎳ 마다 RT-kernel의 시스템 타이머 인터럽트를 받아 task가 깨어나도록 구현&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램의 main 함수 내 while문에서 &lt;span class=&quot;inline-em&quot;&gt;clock_nanosleep&lt;/span&gt; 함수에서 OS호출을 대기하며 블록킹 되는데,&lt;br /&gt;다음과 같은 상황에서는 실시간성을 보장받지 못하고 &lt;b&gt;일반 프로세서와 동일하게 동작&lt;/b&gt;한다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Linux kernel이 PREEMPT_RT 커널이 아닌 경우&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 시 관리자(root) 권한으로 실행되지 않은 경우&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 조건을 역이용하여 동일하게 빌드된 단일 프로그램을 통해 non-RT 커널과 PREEMPT-RT 커널 테스트를 모두 진행했다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1775453950641&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;cerrno&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;fstream&amp;gt;
#include &amp;lt;iomanip&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;sched.h&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;time.h&amp;gt;
#include &amp;lt;vector&amp;gt;

namespace {

timespec addMs(const timespec&amp;amp; t, long ms) {
    timespec out = t;
    out.tv_sec += ms / 1000;
    out.tv_nsec += (ms % 1000) * 1000000L;
    if (out.tv_nsec &amp;gt;= 1000000000L) {
        out.tv_sec += 1;
        out.tv_nsec -= 1000000000L;
    }
    return out;
}

double diffMs(const timespec&amp;amp; a, const timespec&amp;amp; b) {
    const long sec = a.tv_sec - b.tv_sec;
    const long nsec = a.tv_nsec - b.tv_nsec;
    return static_cast&amp;lt;double&amp;gt;(sec) * 1000.0 + static_cast&amp;lt;double&amp;gt;(nsec) / 1000000.0;
}

struct LogEntry {
    long long tick;
    double actualRunTimeMs;
    double predictMs;
    double gitterMs;
};

}  // namespace

int main(int argc, char* argv[]) {
    constexpr long periodMs = 10;
    constexpr long long totalTicks = 10000;
    int rtPriority = 80;

    if (argc &amp;gt;= 2) {
        rtPriority = std::stoi(argv[1]);
    }

    // 메모리 페이지 폴트 방지: 모든 메모리를 RAM에 고정
    if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
        std::cerr &amp;lt;&amp;lt; &quot;Warning: mlockall failed (&quot; &amp;lt;&amp;lt; std::strerror(errno)
                  &amp;lt;&amp;lt; &quot;). Page faults may cause jitter.&quot; &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;mlockall: memory locked.&quot; &amp;lt;&amp;lt; std::endl;
    }

    // 로그 버퍼 사전 할당 (루프 전에 메모리 확보)
    std::vector&amp;lt;LogEntry&amp;gt; logBuffer;
    logBuffer.reserve(totalTicks);

    sched_param schParam{};
    schParam.sched_priority = rtPriority;

    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &amp;amp;schParam) != 0) {
        std::cerr &amp;lt;&amp;lt; &quot;Warning: failed to set SCHED_FIFO priority=&quot; &amp;lt;&amp;lt; rtPriority
                  &amp;lt;&amp;lt; &quot; (&quot; &amp;lt;&amp;lt; std::strerror(errno)
                  &amp;lt;&amp;lt; &quot;). Continue with current scheduler.&quot; &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;SCHED_FIFO enabled with priority=&quot; &amp;lt;&amp;lt; rtPriority &amp;lt;&amp;lt; std::endl;
    }

    timespec startTs{};
    if (clock_gettime(CLOCK_MONOTONIC, &amp;amp;startTs) != 0) {
        std::cerr &amp;lt;&amp;lt; &quot;clock_gettime failed&quot; &amp;lt;&amp;lt; std::endl;
        return 1;
    }

    std::cout &amp;lt;&amp;lt; &quot;Started RT periodic task at 10ms. Logging to rt_task_log.csv&quot; &amp;lt;&amp;lt; std::endl;

    long long tick = 0;

    // RT 루프: I/O 없이 메모리 버퍼에만 기록
    while (tick &amp;lt; totalTicks) {
        ++tick;

        const timespec targetTs = addMs(startTs, static_cast&amp;lt;long&amp;gt;(tick * periodMs));

        const int sleepRet = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &amp;amp;targetTs, nullptr);
        if (sleepRet != 0 &amp;amp;&amp;amp; sleepRet != EINTR) {
            std::cerr &amp;lt;&amp;lt; &quot;clock_nanosleep failed: &quot; &amp;lt;&amp;lt; std::strerror(sleepRet) &amp;lt;&amp;lt; std::endl;
            break;
        }

        timespec now{};
        clock_gettime(CLOCK_MONOTONIC, &amp;amp;now);

        logBuffer.push_back({
            tick,
            diffMs(now, startTs),
            static_cast&amp;lt;double&amp;gt;(tick * periodMs),
            diffMs(now, addMs(startTs, static_cast&amp;lt;long&amp;gt;(tick * periodMs)))
        });
    }

    // RT 루프 종료 후 한 번에 파일 기록
    std::cout &amp;lt;&amp;lt; &quot;task stopped after &quot; &amp;lt;&amp;lt; tick &amp;lt;&amp;lt; &quot; ticks. Writing log...&quot; &amp;lt;&amp;lt; std::endl;

    std::ofstream logFile(&quot;rt_task_log.csv&quot;, std::ios::trunc);
    if (!logFile.is_open()) {
        std::cerr &amp;lt;&amp;lt; &quot;Failed to open rt_task_log.csv&quot; &amp;lt;&amp;lt; std::endl;
        return 1;
    }

    logFile &amp;lt;&amp;lt; std::fixed &amp;lt;&amp;lt; std::setprecision(3);
    logFile &amp;lt;&amp;lt; &quot;index,actual_run_time_ms,predict_ms,gitter_ms\n&quot;;

    for (const auto&amp;amp; e : logBuffer) {
        logFile &amp;lt;&amp;lt; e.tick &amp;lt;&amp;lt; &quot;,&quot;
                &amp;lt;&amp;lt; e.actualRunTimeMs &amp;lt;&amp;lt; &quot;,&quot;
                &amp;lt;&amp;lt; e.predictMs &amp;lt;&amp;lt; &quot;,&quot;
                &amp;lt;&amp;lt; e.gitterMs &amp;lt;&amp;lt; &quot;\n&quot;;
    }

    std::cout &amp;lt;&amp;lt; &quot;Log written to rt_task_log.csv (&quot; &amp;lt;&amp;lt; logBuffer.size() &amp;lt;&amp;lt; &quot; entries).&quot; &amp;lt;&amp;lt; std::endl;
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;⚠️트러블 슈팅 : 로깅 방식으로 인한 지연&lt;br /&gt;&lt;/b&gt;위 예제 코드를 보면 vector::logBuffer에 결과를 담아두었다가 측정(10,000회)이 완전히 종료된 후 일관적으로 파일에 쓰도록 구현했다.&lt;br /&gt;Real-Time 프로그램 원칙 중 &lt;b&gt;disk I/O 작업을 지양해야 한다&lt;/b&gt;는 룰이 있다.&lt;br /&gt;㎛ 수준으로 즉각 동작해야 하는 시스템 특성상, 순간적인 파일 입출력 병목이 발생하면 수 ㎳ 이상의 스레드 지연을 초래할 수 있기 때문이다.&lt;br /&gt;&lt;br /&gt;초기에 측정 데이터를 실시간으로 보겠다는 마음으로 &lt;b&gt;while 반복문 안에서 매 틱마다 로깅을 수행했다가 지연이 발생했고&lt;/b&gt;,&lt;br /&gt;&lt;b&gt;마치 Jitter가 발생한 것처럼 보이는 착시를 겪은 후 메모리 버퍼 방식으로 변경했다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;성능 비교 환경 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 BoxPC에 &lt;b&gt;PREEMPT_RT Linux kernel을 활성화&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux 6.12 버전부터는 PREEMPT_RT가 공식 커널에 합쳐졌기 때문에, 최신 배포판을 사용 중이라면 쉽게 활성화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 내가 사용한 Ubuntu 24.04의 커널은 6.8 버전으로 공식적으로 통합되기 이전의 빌드였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 커널 소스와 해당하는 버전의 RT 패치를 다운로드한 뒤 직접 빌드하여 커널을 올려주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스트를 위한 전체적인 OS 환경 세팅은 AI 도구들을 사용하면 훨씬 수월하게 구축할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 비교에 앞서 정확한 측정을 위해 &lt;b&gt;두 가지 환경 설정&lt;/b&gt;이 더 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째, 스트레스 테스트 도구인 &lt;/b&gt;&lt;span class=&quot;inline-em&quot;&gt;stress-ng&lt;/span&gt;&lt;b&gt;를 이용해&lt;/b&gt; 시스템 자원 전체에 고의적인 부하를 걸고 측정했다.&lt;br /&gt;&lt;b&gt;Jitter가 튀기 가장 좋은 가혹 환경&lt;/b&gt;을 만들기 위해 아래 명령을 사용했다.&lt;/p&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;stress-ng&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--cpu&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--vm&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--vm-bytes&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;1G&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--io&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--timer&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--switch&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b5cea8;&quot;&gt;4&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;--timeout&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;120s&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b4b4b4;&quot;&gt;&amp;amp;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 99.7676%; height: 101px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 23.2541%; height: 21px;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%; height: 21px;&quot;&gt;효과&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%; height: 21px;&quot;&gt;&lt;span style=&quot;background-color: #008300; color: #ffffff; text-align: start;&quot;&gt;RT jitter 유발 메커니즘&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 23.2541%; height: 21px;&quot;&gt;--cpu 0&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%; height: 21px;&quot;&gt;모든 코어 CPU 100% 연산&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%; height: 21px;&quot;&gt;☑ 컨텍스트 스위치 유발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 23.2541%; height: 21px;&quot;&gt;--vm 4 --vm-bytes 1G&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%; height: 21px;&quot;&gt;메모리 할당/해제 반복&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%; height: 21px;&quot;&gt;☑ 페이지 폴트, TLB flush 유발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 23.2541%; height: 21px;&quot;&gt;--io 4&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%; height: 21px;&quot;&gt;디스크 read/write 반복&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%; height: 21px;&quot;&gt;☑ I/O IRQ 유발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.2541%; height: 17px;&quot;&gt;--timer 4&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%; height: 17px;&quot;&gt;타이머 인터럽트 생성&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%; height: 17px;&quot;&gt;☑ 하드웨어 타이머 ㅁ인터럽트 경합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.2541%;&quot;&gt;--switch 4&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%;&quot;&gt;컨텍스트 스위치 폭탄&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%;&quot;&gt;☑ 프로세스 스케줄링 간섭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.2541%;&quot;&gt;--timeout 120s&lt;/td&gt;
&lt;td style=&quot;width: 26.4849%;&quot;&gt;120초간 지속&lt;/td&gt;
&lt;td style=&quot;width: 78.1537%;&quot;&gt;☑ 테스트 기간(약 100초 = 10㎳ * 10,000) 동안 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째,&lt;/b&gt; CPU 동적 클럭 제어 비활성화 PREEMPT_RT에서 clock_nanosleep함수로 깨어나는 순간,&lt;br /&gt;절전 모드 등 cpu 코어의 동작 클럭이 낮아져 있다면 다시 연산 클럭을 끌어올리는 과정에서 미세한 지연 시간(Latency)이 발생할 수 있다. 따라서 CPU Governor 설정을 &lt;b&gt;performance 모드로 변경해, 항상 최대 클럭이 유지되도록 강제했다&lt;/b&gt;.&lt;/p&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;# CPU Performance 모드로 변경&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;echo&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;performance&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #b4b4b4;&quot;&gt;|&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #dcdcaa;&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;tee&lt;/span&gt;&lt;span style=&quot;color: #dadada;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;/sys/devices/system/cpu/cpu&lt;/span&gt;&lt;span style=&quot;color: #569cd6;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #ce9178;&quot;&gt;/cpufreq/scaling_governor&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;QTimer&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;vs&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;PREEMPT_RT 성능 비교&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;☑ QTimer는&amp;nbsp;내부의&amp;nbsp;QEventLoop&amp;nbsp;구조로&amp;nbsp;인해&amp;nbsp;Linux&amp;nbsp;Basic&amp;nbsp;Kernel&amp;nbsp;보다&amp;nbsp;Jitter가&amp;nbsp;클&amp;nbsp;것이다.&lt;/h4&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;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKkTIn/dJMcaarjBBV/QmFBTKK1Sjf2QL5Cff5BBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKkTIn/dJMcaarjBBV/QmFBTKK1Sjf2QL5Cff5BBk/img.png&quot; data-alt=&quot;QTimer 3회 측정 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKkTIn/dJMcaarjBBV/QmFBTKK1Sjf2QL5Cff5BBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKkTIn%2FdJMcaarjBBV%2FQmFBTKK1Sjf2QL5Cff5BBk%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;1773&quot; height=&quot;452&quot; data-origin-width=&quot;1773&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;QTimer 3회 측정 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xg6d2/dJMcabKx7io/rxRHH9IV9EfLpmKTtwTsp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xg6d2/dJMcabKx7io/rxRHH9IV9EfLpmKTtwTsp0/img.png&quot; data-alt=&quot;Linux non-RT kernel 3회 측정 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xg6d2/dJMcabKx7io/rxRHH9IV9EfLpmKTtwTsp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXg6d2%2FdJMcabKx7io%2FrxRHH9IV9EfLpmKTtwTsp0%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;1772&quot; height=&quot;451&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Linux non-RT kernel 3회 측정 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 호출 방식의 결과는 예상대로 압도적인 차이가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상치에 해당하는 Jitter를 제외(상하위 10%를 제외)한 평균시간에서도 약 50배 가까이 차이가 확인되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;QTimer 평균 지연&lt;/b&gt; = 0.576987&amp;nbsp; ㎳&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Linux non-RT Kernel 평균 지연&lt;/b&gt;&amp;nbsp;= 0.011389 ㎳&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;☑ CPU부하로 인해 QTimer와 non-RT Kernel의 Jitter는 들쭉날쭉할 것이다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 non-RT kernel 환경의 테스트 결과를 보면, 시스템 컨디션에 따라 Jitter가 발생하는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 측의 경우 운이 좋게도 OS 스케줄러 간섭 없이 매끄럽게 통과했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 측정 결과(3rd)에서는 최대 0.65 ms까지 값이 튀는 것을 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 &lt;b&gt;Inturrupt 발생 시점부터 Task 실행까지 운이 좋으면 11㎲, 운이 나쁠 때는 650㎲까지 지연될 수 있음&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;☑ RT Kernel의 Jitter는 시스템 부하에서도 안정적이어야 한다.&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEBGSw/dJMcag58eDK/5m9F0x5XygwiEQq2OhMUhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEBGSw/dJMcag58eDK/5m9F0x5XygwiEQq2OhMUhK/img.png&quot; data-alt=&quot;Linux PREEMPT_RT Kernel 3회 측정 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEBGSw/dJMcag58eDK/5m9F0x5XygwiEQq2OhMUhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEBGSw%2FdJMcag58eDK%2F5m9F0x5XygwiEQq2OhMUhK%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;1772&quot; height=&quot;451&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Linux PREEMPT_RT Kernel 3회 측정 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 목적인 &lt;b&gt;PREEMPT_RT Kernel에서는 Jitter가 확실하게 잡혀 안정적으로 프로그램이 실행&lt;/b&gt;되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/scWvd/dJMcab4Ogob/Kie2J5QOIYAbGcoy4ruI3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/scWvd/dJMcab4Ogob/Kie2J5QOIYAbGcoy4ruI3k/img.png&quot; data-alt=&quot;Linux PREEMPT_RT kKernel 3번째 측정 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/scWvd/dJMcab4Ogob/Kie2J5QOIYAbGcoy4ruI3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FscWvd%2FdJMcab4Ogob%2FKie2J5QOIYAbGcoy4ruI3k%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;591&quot; height=&quot;451&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Linux PREEMPT_RT kKernel 3번째 측정 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;non-RT 환경에서 실행 시간의 변동성이 가장 컸던 세 번째 케이스를 PREEMPT_RT 위에서 돌렸을 때,&lt;br /&gt;&lt;b&gt;평균 11㎲ 최악의 경우 70㎲ 실행속도를 측정했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순간적으로 650㎲ 까지 지연됐던 범용 커널에 비해 훨씬 신뢰도 높고 예측 가능한 움직임을 보여주었다.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 처리 성능(Throughput)이 중요한 비전(Vision) 프로그램을 다룰 때 주로 테스트 설계를 고민해 왔었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 이렇게 마이크로초(㎲) 단위의 응답성을 다투는 정밀도 테스트를 설계하고 검증해 보니 시스템 코어 개발마의 색다른 재미를 느낄 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내심 &quot;나노초 단위까지 제어할 수 있는 결과를 볼 수 있지 않을까?&quot;라는 기대와는 달라 아쉬운 점도 있었지만,&lt;br /&gt;무거운 범용 운영체제인 Linux에서 RTOS가 아님에도 커널 패치로 이 정도의 성능을 끌어낸다는 점에서 PREEMPT_RT 기술의 우수성을 엿볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU Isloation과 IRQ가 RT-CPU를 방해하지 않도록 하는 IRQ Affinity 조정방법을 통해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;실행시간을 좀 더 단축할 수 있겠지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트의 PoC 차원에서 진행된 테스트 프로그램의 결과물로는 이 정도로 만족스럽다.&lt;/p&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;</description>
      <category>개발</category>
      <category>jitter</category>
      <category>Linux</category>
      <category>MuLiN</category>
      <category>preempt_rt</category>
      <category>Real-time</category>
      <category>RTOS</category>
      <category>SW_PLC</category>
      <category>실시간운영체제</category>
      <category>지터</category>
      <category>트러블슈팅</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/42</guid>
      <comments>https://prejudice.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 6 Apr 2026 21:35:34 +0900</pubDate>
    </item>
    <item>
      <title>Windows 한글 계정명에서 발생하는 CMake 빌드 에러 원인과 해결 방법</title>
      <link>https://prejudice.tistory.com/41</link>
      <description>&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 CMake를 C++ 프로젝트를 빌드하면서 &lt;b&gt;특정 Windows PC에서만 빌드 에러가 발생&lt;/b&gt;하는 흥미로운 버그를 만났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향후 비슷한 문제를 마주했을 때 빠르게 원인을 파악하고 대처할 수 있도록,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 트러블 슈팅 과정에서 배운 버그 발생 원인과 해결방법을 정리해 보려 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 빌드 에러는 다음과 같은 특정 환경에서만 발생했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OS:&lt;/b&gt; Windos&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Windows 사용자 계정:&lt;/b&gt; 한글 이름 사용 (예: C:\Users\전인학\...)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OS 설정:&lt;/b&gt; &lt;b&gt;&quot;시간 및 언어 &amp;gt; 언어 및 지역 &amp;gt; Beta: ...Unicode UTF-8&quot;&lt;/b&gt; 옵션이 꺼져 있는 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;원인 분석: 빌드 도구 간의 인코딩 불일치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 계정명이 한글일 때 오류가 발생하는 원인을 조사해 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빌드 파이프라인에 동원되는 각 도구별로 사용하는 인코딩(코드 페이지)이 달라서 발생하는 문제&lt;/b&gt;라는 걸 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, MinGW를 이용해 컴파일 및 링킹을 수행하는 과정은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span class=&quot;inline-em&quot;&gt;CMake&lt;/span&gt; &amp;rarr; &lt;span class=&quot;inline-em&quot;&gt;mingw32-make&lt;/span&gt; &amp;rarr; &lt;span class=&quot;inline-em&quot;&gt;g++&lt;/span&gt; &amp;rarr; &lt;span class=&quot;inline-em&quot;&gt;ld&lt;/span&gt; &amp;rarr; &lt;span class=&quot;inline-em&quot;&gt;실행파일(.exe) / 라이브러리(.dll)&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제는, 이 도구들이 문자열(경로, 인자 등)을 주고받을 때 작동하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;*인코딩 형식은 대부분 툴이 빌드될 때 결정되며, 최신 도구들은 자체적으로 UTF-8을 기본으로 사용하는 경우가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;231&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhwc2s/dJMcai3Nad6/jbzKROsYRyST18p0yELCsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhwc2s/dJMcai3Nad6/jbzKROsYRyST18p0yELCsk/img.png&quot; data-alt=&quot;MinGW를 이용한 빌드과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhwc2s/dJMcai3Nad6/jbzKROsYRyST18p0yELCsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdhwc2s%2FdJMcai3Nad6%2FjbzKROsYRyST18p0yELCsk%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;231&quot; height=&quot;432&quot; data-origin-width=&quot;231&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MinGW를 이용한 빌드과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어 알파벳, 숫자, 범용 특수문자(/, \, _, - 등)와 같은 &lt;b&gt;ASCII 문자&lt;/b&gt;로만 이루어진 경로라면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽는 쪽과 보내는 쪽이 UTF-8이든 UTF-16이든 문자가 깨질 일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 경로 중간에 &lt;b&gt;한글&lt;/b&gt;이 섞여 있다면 어느 한 구간에서라도 양쪽 도구 간 파싱하는 인코딩이 어긋날 경우 텍스트가 깨지고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 찾지 못하는 치명적인 빌드 에러로 직결된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;버그 검증 및 재현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 먼저 Windows OS의 활성 코드 페이지 설정을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows11 기준으로, cmd창에서 &lt;span class=&quot;inline-em&quot;&gt;chcp &lt;/span&gt;&amp;nbsp;명령어를 입력하면 UTF-8 옵션 활성화 여부에 따라 코드페이지가 달라지는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWjBMi/dJMcagEZyCC/cGRcKjNDcE4ILBPSPi64Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWjBMi/dJMcagEZyCC/cGRcKjNDcE4ILBPSPi64Y1/img.png&quot; data-alt=&quot;Windows11 OS UTF-8 Codepage사용 옵션&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWjBMi/dJMcagEZyCC/cGRcKjNDcE4ILBPSPi64Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWjBMi%2FdJMcagEZyCC%2FcGRcKjNDcE4ILBPSPi64Y1%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;655&quot; height=&quot;238&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Windows11 OS UTF-8 Codepage사용 옵션&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMLFnV/dJMcaaY2uip/h9u8YkOWvzde1cIqGvmJV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMLFnV/dJMcaaY2uip/h9u8YkOWvzde1cIqGvmJV0/img.png&quot; data-alt=&quot;옵션 체크 시 (65501=UTF-8)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMLFnV/dJMcaaY2uip/h9u8YkOWvzde1cIqGvmJV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMLFnV%2FdJMcaaY2uip%2Fh9u8YkOWvzde1cIqGvmJV0%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;223&quot; height=&quot;20&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;옵션 체크 시 (65501=UTF-8)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;199&quot; data-origin-height=&quot;25&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvbsZO/dJMcahjBGqs/xXSYef5oOVBLFtiIqG43Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvbsZO/dJMcahjBGqs/xXSYef5oOVBLFtiIqG43Xk/img.png&quot; data-alt=&quot;옵션 해제 시 (949=EUC-KR 확장)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvbsZO/dJMcahjBGqs/xXSYef5oOVBLFtiIqG43Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvbsZO%2FdJMcahjBGqs%2FxXSYef5oOVBLFtiIqG43Xk%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;199&quot; height=&quot;25&quot; data-origin-width=&quot;199&quot; data-origin-height=&quot;25&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;옵션 해제 시 (949=EUC-KR 확장)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인 파악 후, Windows에 한글 유저 계정을 추가하고 간단한 C++ HelloWorld 프로젝트를 구성하여 버그를 재현할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;background-color: #fafafa; color: #383a42; text-align: start;&quot;&gt; &lt;/span&gt; CMakeLists.txt&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1774860372164&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cmake_minimum_required(VERSION 3.15)
project(TinyClassTest LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(tiny_test
    src/main.cpp
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;b&gt;&lt;span style=&quot;background-color: #fafafa; color: #383a42; text-align: start;&quot;&gt; &lt;/span&gt;&lt;/b&gt; src/main.cpp&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1774860594748&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;

int main() {
    std::cout &amp;lt;&amp;lt; &quot;Hello, World!&quot; &amp;lt;&amp;lt; std::endl;
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 생성 후 다음 명령어로 빌드를 수행해 보면, &lt;b&gt;옵션 활성화 여부에 에러 양상이 갈리는 것을 확인&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;div style=&quot;margin: 30px 0; background: #1e1e1e; color: #00ff88; padding: 20px; border-radius: 10px; font-family: Consolas, monospace;&quot;&gt;
&lt;div style=&quot;color: #ffffff; font-weight: bold; margin-bottom: 10px;&quot;&gt;▶ cmake -S . -B build -G &quot;MinGW Makefiles&quot;&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;--&amp;nbsp;The&amp;nbsp;CXX&amp;nbsp;compiler&amp;nbsp;identification&amp;nbsp;is&amp;nbsp;GNU&amp;nbsp;8.1.0 &lt;br /&gt;--&amp;nbsp;Detecting&amp;nbsp;CXX&amp;nbsp;compiler&amp;nbsp;ABI&amp;nbsp;info &lt;br /&gt;--&amp;nbsp;Detecting&amp;nbsp;CXX&amp;nbsp;compiler&amp;nbsp;ABI&amp;nbsp;info&amp;nbsp;-&amp;nbsp;done &lt;br /&gt;--&amp;nbsp;Check&amp;nbsp;for&amp;nbsp;working&amp;nbsp;CXX&amp;nbsp;compiler:&amp;nbsp;C:/Qt/Tools/mingw810_32/bin/c++.exe&amp;nbsp;-&amp;nbsp;skipped &lt;br /&gt;--&amp;nbsp;Detecting&amp;nbsp;CXX&amp;nbsp;compile&amp;nbsp;features &lt;br /&gt;--&amp;nbsp;Detecting&amp;nbsp;CXX&amp;nbsp;compile&amp;nbsp;features&amp;nbsp;-&amp;nbsp;done &lt;br /&gt;--&amp;nbsp;Configuring&amp;nbsp;done&amp;nbsp;(1.0s) &lt;br /&gt;--&amp;nbsp;Generating&amp;nbsp;done&amp;nbsp;(0.0s) &lt;br /&gt;--&amp;nbsp;Build&amp;nbsp;files&amp;nbsp;have&amp;nbsp;been&amp;nbsp;written&amp;nbsp;to:&amp;nbsp;C:/Users/전인학/Desktop/cmake-utf16-test/build&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;&lt;span style=&quot;background-color: #1e1e1e; color: #ffffff; text-align: start;&quot;&gt;▶ cmake --build build&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;white-space: pre-wrap;&quot;&gt;CMake&amp;nbsp;Error:&amp;nbsp;Target&amp;nbsp;DependInfo.cmake&amp;nbsp;file&amp;nbsp;not&amp;nbsp;found &lt;br /&gt;mingw32-make.exe[2]:&amp;nbsp;***&amp;nbsp;No&amp;nbsp;rule&amp;nbsp;to&amp;nbsp;make&amp;nbsp;target&amp;nbsp;'C:/Users/전인학/Desktop/cmake-utf16-test/src/main.cpp',&amp;nbsp;needed&amp;nbsp;by&amp;nbsp;'CMakeFiles/tiny_test.dir/src/main.cpp.obj'.&amp;nbsp;&amp;nbsp;Stop. &lt;br /&gt;mingw32-make.exe[1]:&amp;nbsp;***&amp;nbsp;[CMakeFiles\Makefile2:82:&amp;nbsp;CMakeFiles/tiny_test.dir/all]&amp;nbsp;Error&amp;nbsp;2 &lt;br /&gt;mingw32-make.exe:&amp;nbsp;***&amp;nbsp;[Makefile:90:&amp;nbsp;all]&amp;nbsp;Error&amp;nbsp;2&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 고민해 본 몇 가지 방법들이 있고, 나는 &lt;b&gt;①번 방식&lt;/b&gt;으로 버그를 해결할 수 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① ASCII 경로만 사용하기 (공용 폴더 활용 / ASCII Staging)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 프로젝트 경로와 인자에 한글이 포함되지 않는 &lt;b&gt;ASCII 문자열만 사용&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식을 채택할 땐 경로의 &lt;b&gt;접근 권한이 사용자 고유 권한인지 범용 공유 권한인지 잘 고려&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 98.7212%; height: 54px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style2&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.9335%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;예시 경로&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 103.666%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25.9335%; height: 20px;&quot;&gt;C:\Users\전인학\...&lt;/td&gt;
&lt;td style=&quot;width: 103.666%; height: 20px;&quot;&gt;'전인학'&amp;nbsp;사용자의&amp;nbsp;고유&amp;nbsp;위치로&amp;nbsp;인식되어&amp;nbsp;보안상&amp;nbsp;좋지만,&amp;nbsp;한글&amp;nbsp;경로&amp;nbsp;문제가&amp;nbsp;생길&amp;nbsp;수&amp;nbsp;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.9335%; height: 17px;&quot;&gt;C:\ProgramData\TempStage\&lt;/td&gt;
&lt;td style=&quot;width: 103.666%; height: 17px;&quot;&gt;여러 사용자가 공유하는 범용 위치로 인식되며 경로에 한글이 없어 인코딩 충돌을 우회할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Windows의 subst 명령어 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows 커맨드인 subst를 사용하여 기존의 긴 경로를 가상의 논리 드라이브로 간단하게 매핑할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예: 커맨드 라인에 &lt;span class=&quot;inline-em&quot;&gt;subst M: C:\Users\전인학\...&lt;/span&gt; 등록&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;이후 빌드 툴에는 &quot;M:\TempStage&quot;처럼 ASCII로만 이루어진 맵핑 경로만 전달되므로 문제를 회피할 수 있음.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ &lt;span style=&quot;color: #666666;&quot;&gt;빌드 도구 인코딩 강제 일치시키기&lt;/span&gt;&amp;nbsp;(비추)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 빌드툴의 인코딩 방식을 강제로 일치시켜 해결하는 방법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 MinGW 대신 Ninja로 툴을 전환한다거나, 내부 구현을 알 수 없는 서드파티 툴이 파이프라인에 섞일 경우 제어가 불가능해지기 때문에 현실적인 문제가 많아 옳은 방식은 아닌 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;border-left: 6px solid #525FE1; padding-left: 14px; margin: 1.2em 0 1.2em; color: #2c3e50; font-family: 'Noto Sans KR', 'Noto Sans'; font-weight: bold; line-height: 1.3;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;style&gt;.inline-em{  display:inline-block; padding:0.08em 0.45em; border-radius:0.35em;  background:#2b2f36; color:#fff;  border:1px solid rgba(255,255,255,0.14);  font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,&quot;Courier New&quot;,monospace;  font-size:0.95em; line-height:1.35;}&lt;/style&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 트러블 슈팅 과정에서 조금 어려웠던 점은,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트의 경우 &lt;b&gt;빌드가 사용자 PC환경에서 실행&lt;/b&gt;되고 &lt;b&gt;빌드 툴 로그를 별도로 수집&lt;/b&gt;하지 않아 디버깅이 까다로웠다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 결과적으로 &lt;b&gt;Windows 환경에서는 사용자 계정 명에 한글이 자유롭게 쓰일 수 있다는 예외 상황&lt;/b&gt;을 경험해 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 더해 빌드 툴들의 인코딩 처리 메커니즘, OS의 코드 페이지 동작 방식, 그리고 subst 나 공용 폴더 격리 같은 실용적인 회피 기법까지 다양하게 배울 수 있는 계기가 되었다.&lt;/p&gt;
&lt;div style=&quot;margin: 3em 0 1.5em; padding: 1.1em 1.3em; background: #F9F7F6; border-top: 2px solid #525FE1; border-bottom: 2px solid #E5E7EB; font-family: 'Noto Sans KR', 'Noto Sans'; line-height: 1.6; color: #374151; font-size: 15px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;부족한 부분이나 더 궁금한 주제가 있다면 댓글로 남겨주세요.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;직접 공부해서 다음 글로 정리해 보려고 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발</category>
      <category>CMake</category>
      <category>MinGW</category>
      <category>No rule to mak target</category>
      <category>troubleshooting</category>
      <category>UTF8</category>
      <category>Windows11</category>
      <category>빌드에러</category>
      <category>인코딩</category>
      <category>코드페이지</category>
      <category>한글경로</category>
      <author>편견장</author>
      <guid isPermaLink="true">https://prejudice.tistory.com/41</guid>
      <comments>https://prejudice.tistory.com/41#entry41comment</comments>
      <pubDate>Tue, 31 Mar 2026 10:59:32 +0900</pubDate>
    </item>
  </channel>
</rss>