<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SJ CODE</title>
    <link>https://1000sj.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 16 Jul 2026 16:28:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>1000sj</managingEditor>
    <image>
      <title>SJ CODE</title>
      <url>https://tistory1.daumcdn.net/tistory/3752905/attach/5d32a00a844b4935a706b702e3e4940c</url>
      <link>https://1000sj.tistory.com</link>
    </image>
    <item>
      <title>QEMU 코드 분석 #9 블록 디바이스 가상화 &amp;mdash; QEMU 백엔드 (virtio-blk 클래스 생성 &amp;amp; 디스크 열기)</title>
      <link>https://1000sj.tistory.com/725</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU에서&amp;nbsp;블록&amp;nbsp;디바이스(virtio-blk)가&amp;nbsp;가상화되는&amp;nbsp;과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 장치 가상화 방식(전/반/하드웨어 보조)과 virtio 원리,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU 초기화 단계의 virtio-blk 클래스 생성&amp;middot;인스턴스화&amp;middot;realize,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU 시작 과정에서 `-drive` 옵션이 실제 디스크 이미지 파일을 여는 데까지(drive_new &amp;rarr; blockdev_init &amp;rarr; bdrv_open &amp;rarr; qcow2/raw)를 추적&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) 디스크 장치 가상화 방식과 virtio 원리&lt;br /&gt;2) QEMU 초기화 단계: virtio-blk 클래스 생성과 realize&lt;br /&gt;3) QEMU 시작 과정: -drive 파싱과 drive_new&lt;br /&gt;4) 디스크 이미지 열기 (bdrv_open &amp;rarr; qcow2/raw)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 디스크 장치 가상화 방식과 virtio 원리&lt;/h3&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;디스크 장치 가상화 방식&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ1evm/dJMcadCgffa/JUPzZlEDKFJM8sqnDeZkfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ1evm/dJMcadCgffa/JUPzZlEDKFJM8sqnDeZkfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ1evm/dJMcadCgffa/JUPzZlEDKFJM8sqnDeZkfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ1evm%2FdJMcadCgffa%2FJUPzZlEDKFJM8sqnDeZkfK%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;599&quot; height=&quot;398&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 전가상화(Full Virtualization)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전가상화는 완전 가상화라고도 부르는데, 호스트 운영체제에 가상화 소프트웨어 계층(즉 가상머신 모니터 VMM 또는 Hypervisor)을 설치하여, 물리 하드웨어 자원을 완전히 가상화해서 여러 개의 독립된 가상머신(VM)으로 만든다. 각 가상머신은 모두 독립적으로 자신의 운영체제와 응용 프로그램을 실행할 수 있고, 운영체제를 수정할 필요가 없다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호환성 강함: 수정되지 않은 운영체제를 실행할 수 있고, 다양한 유형의 소프트웨어와 호환된다.&lt;/li&gt;
&lt;li&gt;격리성 좋음: 가상머신 간에 서로 격리되어 보안성과 안정성을 보장한다.&lt;/li&gt;
&lt;li&gt;유연성 높음: 여러 다른 운영체제를 실행해야 하는 환경에 적합하다.&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;클라우드 서비스 제공업체&lt;/li&gt;
&lt;li&gt;높은 격리성과 보안성이 필요한 응용 시나리오&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전가상화 방식의 가장 큰 문제(단점)는, QEMU가 시뮬레이션하는 장치가 &quot;번역관&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;&lt;b&gt;(2) 반가상화(Paravirtualization)&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;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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능 우수: 가상화 계층의 부담을 줄여서 성능을 향상시킨다.&lt;/li&gt;
&lt;li&gt;타깃성 강함: 특정 운영체제와 환경에 적합하다. 예를 들면 전용 서버 환경이다.&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;호환성 요구가 높지 않은 특정 운영체제 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반가상화의 단점은 가상화가 충분히 완전하지 않고 철저하지 않다는 점이다.&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;(3) 하드웨어 보조 가상화(Hardware-Assisted Virtualization)&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;프로세서에는 가상머신 관리 프로그램(Hypervisor)을 지원하기 위한 특정 명령어 집합이 통합되어 있어서, 가상화 부담을 대폭 줄였다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능이 네이티브에 근접: 하드웨어 지원 덕분에 가상머신의 성능이 베어메탈에 가깝다.&lt;/li&gt;
&lt;li&gt;보안성 높음: 하드웨어 특성이 가상머신의 보안성을 강화한다.&lt;/li&gt;
&lt;li&gt;배포 용이: 운영체제를 수정할 필요가 없어서 배포와 유지보수의 난이도가 간소화된다.&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;빅데이터 처리&lt;/li&gt;
&lt;li&gt;서버 통합&lt;/li&gt;
&lt;li&gt;고성능과 고보안성이 필요한 응용 시나리오&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MSfay/dJMcahdIRVQ/pCoKpN7O8qp23dRx5kDWxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MSfay/dJMcahdIRVQ/pCoKpN7O8qp23dRx5kDWxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MSfay/dJMcahdIRVQ/pCoKpN7O8qp23dRx5kDWxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMSfay%2FdJMcahdIRVQ%2FpCoKpN7O8qp23dRx5kDWxk%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;492&quot; height=&quot;281&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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;/li&gt;
&lt;li&gt;반가상화: 가상화 계층의 부담을 줄여서 성능이 전가상화보다 우수하다.&lt;/li&gt;
&lt;li&gt;하드웨어 보조 가상화: 하드웨어 지원 덕분에 성능이 네이티브 하드웨어에 근접한다.&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;반가상화: 운영체제의 지원과 수정이 필요하므로 호환성이 비교적 떨어진다.&lt;/li&gt;
&lt;li&gt;하드웨어 보조 가상화: 운영체제를 수정할 필요가 없어서 호환성이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;virtio의 기본 원리&lt;/h4&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;즉 가상머신(모니터) 안의 운영체제가 자신이 가상머신 안에서 실행되고 있다는 사실을 알고, 가상머신은 호스트 운영체제와 협력하여 함께 가상화 과정을 완성해야 한다.&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;/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;mdash;&amp;mdash; virtio의 기본 원리이다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되니 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크 장치(및 네트워크 장치 등)가 너무 많아서, 드라이버가 어지럽게 많아졌다. 이러한 &quot;각자 싸우는&quot; 상황은 한편으로는 유지보수에 불리하고, 다른 한편으로는 코드가 마구잡이로 자라는 추세를 보이게 만들었다.&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;p data-ke-size=&quot;size16&quot;&gt;그래서 virtio가 탄생했다. virtio의 전체 명칭은 Virtual IO로, 가상화 I/O라는 뜻이다. 이전에 언급한 것처럼, 디스크와 네트워크 등 장치는 모두 외부 장치에 속하고, 여기서는 IO의 범주에 속한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ukBpT/dJMcahdIRWS/iF9cl3XcEkkk92D3rj7Lw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ukBpT/dJMcahdIRWS/iF9cl3XcEkkk92D3rj7Lw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ukBpT/dJMcahdIRWS/iF9cl3XcEkkk92D3rj7Lw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FukBpT%2FdJMcahdIRWS%2FiF9cl3XcEkkk92D3rj7Lw1%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;773&quot; height=&quot;222&quot; data-origin-width=&quot;1574&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio는 가상머신에 통일된 인터페이스를 제공하는 역할을 한다. 즉, 가상머신 안의 운영체제가 로드하는 드라이버가 이전에는 천차만별이었는데, 이후에는 모두 통일해서 virtio를 로드하게 되었다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio의 기본 아키텍처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpgb2q/dJMcahdIRW0/bWlisJGe4sgmR4sfNWClH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpgb2q/dJMcahdIRW0/bWlisJGe4sgmR4sfNWClH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpgb2q/dJMcahdIRW0/bWlisJGe4sgmR4sfNWClH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcpgb2q%2FdJMcahdIRW0%2FbWlisJGe4sgmR4sfNWClH1%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;316&quot; height=&quot;512&quot; data-origin-width=&quot;456&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이에 대응하여, 호스트에서는 서로 다른 virtio 백엔드를 구현해서 서로 다른 물리 장치에 맞출 수 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio는 일종의 프론트엔드-백엔드 아키텍처로, 프론트엔드 드라이버(Front-End Driver)와 백엔드 장치(Back-End Device) 그리고 자체 정의된 전송 프로토콜을 포함한다.&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;전송 프로토콜을 통해서, virtio는 QEMU/KVM 방식에 사용될 수 있을 뿐만 아니라 다른 가상화 방식에도 사용될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 가상머신은 꼭 QEMU일 필요는 없고, 다른 유형의 가상머신일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드도 꼭 QEMU에서 구현될 필요는 없고, 커널에서도 구현될 수 있다(이것이 실제로 vhost 방식이다).&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;virtio 아키텍처 계층화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio는 3개 층으로 나눌 수 있다.&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;(1) 프론트엔드 드라이버(Front-end drivers)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 드라이버는 즉 가상머신 내부의 virtio가 시뮬레이션하는 장치에 대응되는 드라이버로, 각 프론트엔드 장치마다 모두 대응되는 드라이버가 있어야 정상적으로 실행될 수 있다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자 모드의 요청을 수신한다.&lt;/li&gt;
&lt;li&gt;그 다음 전송 프로토콜에 따라 이 요청들을 캡슐화한다.&lt;/li&gt;
&lt;li&gt;다시 I/O 포트를 읽고 쓴다.&lt;/li&gt;
&lt;li&gt;알림을 보낸다. QEMU의 백엔드 장치로.&lt;/li&gt;
&lt;/ol&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;(2) 백엔드 장치/백엔드 드라이버(Back-end devices/drivers)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 장치는 QEMU 안에 있고, 프론트엔드 드라이버가 보낸 I/O 요청을 수신하고, 수신된 데이터에서 전송 프로토콜의 형식에 따라 분석을 진행하는 데 사용된다.&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;virtio 큐(virtio queue, virtqueue)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 프론트엔드와 백엔드 드라이버의 데이터 전송은 virtio 큐(virtio queue, virtqueue)를 통해 완성되는데, 한 장치는 여러 개의 virtio 큐를 등록하고, 각 큐는 서로 다른 데이터 전송을 처리하는 책임을 진다.&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;virtqueue 내부 구조&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9YJhk/dJMcacXLan9/6Dfv1X2yggSq8oAsbQuom0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9YJhk/dJMcacXLan9/6Dfv1X2yggSq8oAsbQuom0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9YJhk/dJMcacXLan9/6Dfv1X2yggSq8oAsbQuom0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9YJhk%2FdJMcacXLan9%2F6Dfv1X2yggSq8oAsbQuom0%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;255&quot; height=&quot;464&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtqueue는 vring을 통해 구현되고, vrtqueue와 vring을 두 층으로 나누는 분층 방법도 있어서, 아래와 같이 전체적으로 virtio를 4층으로 나눈다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/837zF/dJMcabR2TGf/QekZuRhk0Po8vKdEIjgKI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/837zF/dJMcabR2TGf/QekZuRhk0Po8vKdEIjgKI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/837zF/dJMcabR2TGf/QekZuRhk0Po8vKdEIjgKI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F837zF%2FdJMcabR2TGf%2FQekZuRhk0Po8vKdEIjgKI1%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;178&quot; height=&quot;616&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vring은 가상머신과 QEMU 간에 공유되는 하나의 링 버퍼이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;가상머신이 QEMU에 요청을 보내야 할 때 데이터를 미리 준비해두고, 데이터를 vring에 기술해 넣고, I/O 포트 하나에 쓴다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그러면 QEMU는 vring에서 데이터 정보를 읽어낼 수 있고, 더 나아가 메모리에서 데이터를 읽어낸다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU가 요청을 완성한 후에도, 데이터 구조를 vring에 넣어두면, 프론트엔드 드라이버도 vring에서 데이터를 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUCmYi/dJMcagZ7n6q/2XkwnqiKa6ldxyfWW50UKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUCmYi/dJMcagZ7n6q/2XkwnqiKa6ldxyfWW50UKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUCmYi/dJMcagZ7n6q/2XkwnqiKa6ldxyfWW50UKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUCmYi%2FdJMcagZ7n6q%2F2XkwnqiKa6ldxyfWW50UKk%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;622&quot; height=&quot;375&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) QEMU 초기화 단계: virtio-blk 클래스 생성과 realize&lt;/h3&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;QEMU 초기화 단계의 블록 장치 가상화&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이전 글을 봤다면 QEMU에서 C 언어를 사용하여 객체 지향 사상을 구현한 메커니즘에 대해 낯설지 않을 것이다. 디스크 즉 블록 장치에 대해서, 메커니즘도 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781525416582&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const TypeInfo virtio_blk_info = {
    .name = TYPE_VIRTIO_BLK,
    .parent = TYPE_VIRTIO_DEVICE,
    .instance_size = sizeof(VirtIOBlock),
    .instance_init = virtio_blk_instance_init,
    .class_init = virtio_blk_class_init,
};

static void virtio_register_types(void)
{
    type_register_static(&amp;amp;virtio_blk_info);
}

type_init(virtio_register_types)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio block device라는 클래스의 정의는 다층의 상속 관계가 있다. TYPE_VIRTIO_BLK의 부모 클래스는 TYPE_VIRTIO_DEVICE이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그러면 TYPE_VIRTIO_DEVICE의 부모 클래스는 또 무엇일까? hw/virtio/virtio.c 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781525436106&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const TypeInfo virtio_device_info = {
    .name = TYPE_VIRTIO_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(VirtIODevice),
    .class_init = virtio_device_class_init,
    .instance_finalize = virtio_device_instance_finalize,
    .abstract = true,
    .class_size = sizeof(VirtioDeviceClass),
};

static void virtio_register_types(void)
{
    type_register_static(&amp;amp;virtio_device_info);
}

type_init(virtio_register_types)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;보다시피, TYPE_VIRTIO_DEVICE의 부모 클래스는 TYPE_DEVICE이다. 그리고 TYPE_DEVICE의 부모 클래스는 TYPE_OBJECT이다. hw/core/qdev.c의 코드를 참조하자.&lt;/p&gt;
&lt;pre id=&quot;code_1781525458217&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const TypeInfo device_type_info = {
    .name = TYPE_DEVICE,
    .parent = TYPE_OBJECT,
    .instance_size = sizeof(DeviceState),
    .instance_init = device_initfn,
    .instance_post_init = device_post_init,
    .instance_finalize = device_finalize,
    .class_base_init = device_class_base_init,
    .class_init = device_class_init,
    .abstract = true,
    .class_size = sizeof(DeviceClass),
    .interfaces = (InterfaceInfo[]) {
        { TYPE_VMSTATE_IF },
        { TYPE_RESETTABLE_INTERFACE },
    }
};

static void qdev_register_types(void)
{
    type_register_static(&amp;amp;device_type_info);
}

type_init(qdev_register_types)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;TYPE_OBJECT까지 가면 끝이다. 이것은 &quot;최상위 루트&quot;이고, 부모 클래스가 없으며, 모든 클래스는 그것의 자손이다. hw/core/object.c의 코드를 참조하자.&lt;/p&gt;
&lt;pre id=&quot;code_1781525484279&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void register_types(void)
{
    static TypeInfo interface_info = {
        .name = TYPE_INTERFACE,
        .class_size = sizeof(InterfaceClass),
        .abstract = true,
    };

    static const TypeInfo object_info = {
        .name = TYPE_OBJECT,
        .instance_size = sizeof(Object),
        .class_init = object_class_init,
        .abstract = true,
    };

    type_interface = type_register_internal(&amp;amp;interface_info);
    type_register_internal(&amp;amp;object_info);
}

type_init(register_types)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio block device로 돌아가서, 상속 관계는 다음과 같다&lt;/p&gt;
&lt;pre id=&quot;code_1781525530074&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TYPE_OBJECT
    ---&amp;gt; TYPE_DEVICE
        ---&amp;gt; TYPE_VIRTIO_DEVICE
            ---&amp;gt; TYPE_VIRTIO_BLK&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7h6cr/dJMcahx12Q9/w4xY39rHFrso5skFOyHKuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7h6cr/dJMcahx12Q9/w4xY39rHFrso5skFOyHKuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7h6cr/dJMcahx12Q9/w4xY39rHFrso5skFOyHKuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7h6cr%2FdJMcahx12Q9%2Fw4xY39rHFrso5skFOyHKuk%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;190&quot; height=&quot;593&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;type_init 함수는 클래스 등록에 사용된다. 각 층마다 class_init이 있고, TypeImpl에서 xxxClass를 생성하는 데 사용된다. TYPE_OBJECT의 class_init은 object_class_init이고,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;TYPE_DEVICE의 class_init은 device_class_init이며,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;TYPE_VIRTIO_DEVICE의 class_init은 virtio_device_class_init이고,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;TYPE_VIRTIO_BLK의 class_init은 virtio_blk_class_init이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 계층은 또한 instance_init을 가질 수 있다(없을 수도 있다). xxxClass를 인스턴스로 초기화하는 데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TYPE_DEVICE의 instance_init은 device_initfn이고, TYPE_VIRTIO_BLK의 instance_init은 virtio_blk_instance_init이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;각 계층마다 type_init 함수가 하나 있고, type_init 함수는 클래스 등록에 사용된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;동시에 각 계층마다 class_init도 하나 있고, TypeImpl에서 xxxClass를 생성하는 데 사용된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 객체 지향(C++과 Java같은)에서의 명백한 사상이 C 언어에서 이식된 표현이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TypeImpl에서 xxxClass 생성 - 템플릿에서 클래스 생성과 클래스의 인스턴스화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 템플릿은 키워드 template를 사용해서 클래스 템플릿을 도입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 템플릿은 클래스를 만드는 모델로, 템플릿 매개변수를 제공해서 구현한다. 예를 들어 Point&amp;lt;int&amp;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;즉, Point&amp;lt;int&amp;gt;::moveTo는 한 함수이고, Point&amp;lt;double&amp;gt;::moveTo는 또 다른 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 바로 이러한 함수들을 수동으로 작성하면 발생하는 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 두 개의 다른 소스 파일에서 모두 Point&amp;lt;int&amp;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;클래스 템플릿은 정의 함수와 멤버 변수의 데이터 타입을 템플릿 매개변수 typename T 또는 class T로 허용하는데, 이렇게 하면 필요에 따라 서로 다른 데이터 타입을 사용해서 클래스를 만들 수 있다.&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;간단히 말하면, 클래스 템플릿은 T를 클래스의 멤버 변수와 멤버 함수 매개변수의 데이터 타입의 자리 표시자로 사용한다. 템플릿 클래스는 자리 표시자를 실제 데이터 타입(int, float 등)으로 교체한 후의 클래스이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bodG1C/dJMcaayViRL/HkhEVRvgUYpune9rZkpkC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bodG1C/dJMcaayViRL/HkhEVRvgUYpune9rZkpkC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bodG1C/dJMcaayViRL/HkhEVRvgUYpune9rZkpkC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbodG1C%2FdJMcaayViRL%2FHkhEVRvgUYpune9rZkpkC1%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;394&quot; height=&quot;251&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++의 Template 중 많은 곳에서 typename과 class라는 두 키워드가 사용되는데, 어떤 때는 이 둘을 서로 바꿔서 쓸 수 있다. 그렇다면 이 두 키워드는 완전히 같을까?&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는 클래스를 정의하는 데 사용된다. 템플릿에 c++ 후를 도입한 후, 처음 템플릿을 정의하는 방법은 template&amp;lt;class T&amp;gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 class 키워드는 T가 일종의 타입이라는 것을 나타내는데, 나중에 class가 이 두 곳에서 사용되면 사람을 혼란스럽게 할 수 있다는 점을 피하기 위해서, typename이라는 키워드를 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 작용은 class와 마찬가지로 뒷면의 부호가 일종의 타입임을 나타낸다. 이렇게 하면 템플릿을 정의할 때 다음 방식을 사용할 수 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍에서, 보통 클래스를 사용해서 객체를 만드는 과정을 인스턴스화라고 부른다. 예를 들어 Date date = new Date();은 바로 Date 클래스를 사용해서 한 날짜 객체를 만든 것이고, 객체의 인스턴스화라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 언어에서, 한 객체를 인스턴스화한다는 것은 객체를 위해 메모리 공간을 할당하는 것이거나, 변수 선언 없이 new 생성자명을 직접 사용해서 임시 객체 하나를 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;type_init과 type_register_static&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적으로 QEMU에서 템플릿에서 클래스를 생성하고 클래스를 인스턴스화하는 관련 코드를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU 모듈 하나를 정의하면 type_init을 호출하는데, 그것은 일종의 매크로이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781525870827&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define type_init(function) module_init(function, MODULE_INIT_QOM)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;type_init 매크로의 정의에서 볼 수 있듯이, type_init() 안의 매개변수는 한 함수이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서, type_init(virtio_register_types)를 호출하는 것은 module_init(virtio_register_types, MODULE_INIT_QOM)을 호출하는 것과 같다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MODULE_INIT_QOM은 일종의 열거값이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781525895968&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef enum {
    MODULE_INIT_MIGRATION,
    MODULE_INIT_BLOCK,
    MODULE_INIT_OPTS,
    MODULE_INIT_QOM,
    MODULE_INIT_TRACE,
    MODULE_INIT_XEN_BACKEND,
    MODULE_INIT_LIBQOS,
    MODULE_INIT_FUZZ_TARGET,
    MODULE_INIT_MAX
} module_init_type;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;module_init도 한 매크로이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781525943625&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#ifdef BUILD_DSO
&amp;hellip;&amp;hellip;
#else
/* This should not be used directly.  Use block_init etc. instead.  */
#define module_init(function, type)                                       \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)  \
{                                                                         \
    register_module_init(function, type);                                 \
}
#endif&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781525963431&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void register_module_init(void (*fn)(void), module_init_type type)
{
    ModuleEntry *e;
    ModuleTypeList *l;

    e = g_malloc0(sizeof(*e));
    e-&amp;gt;init = fn;
    e-&amp;gt;type = type;

    l = find_type(type);

    QTAILQ_INSERT_TAIL(l, e, node);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;type_init 호출 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pXjbj/dJMcaijogIN/pQKBjJGNpLZBT42SDSbbn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pXjbj/dJMcaijogIN/pQKBjJGNpLZBT42SDSbbn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pXjbj/dJMcaijogIN/pQKBjJGNpLZBT42SDSbbn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpXjbj%2FdJMcaijogIN%2FpQKBjJGNpLZBT42SDSbbn1%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;784&quot; height=&quot;324&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;MODULE_INIT_QOM이라는 유형에 속하는 것은,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;Module 목록 ModuleTypeList가 하나 있고,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;목록 안에는 한 항목 한 항목씩 ModuleEntry가 있는데,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;각 항목의 init 함수를 매개변수 fn 즉 function으로 초기화한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 virtio_register_types이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, MODULE_INIT_QOM이라는 유형에는 많은 module이 있을 것이다. type_init()를 호출하는 모든 곳에서 MODULE_INIT_QOM 유형의 Module 하나를 등록했다.&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;각 Module이 어떤 (종류의) 장치를 시뮬레이션해야 한다면, 이러한 장치를 표시하는 TypeImpl이라는 유형을 정의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 사실 일종의 객체 지향 프로그래밍 사상이다. 다만 QEMU에서 사용한 것은 순수 C 언어의 구현일 뿐이다. 그래서 클래스와 객체를 변상해서 구현해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781526187011&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const TypeInfo virtio_blk_info = {
    .name          = TYPE_VIRTIO_BLK,
    .parent        = TYPE_VIRTIO_DEVICE,
    .instance_size = sizeof(VirtIOBlock),
    .instance_init = virtio_blk_instance_init,
    .class_init    = virtio_blk_class_init,
};

static void virtio_register_types(void)
{
    type_register_static(&amp;amp;virtio_blk_info);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_register_types 함수는 type_register_static 함수를 호출해서 virtio_blk_info를 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 동적으로 한 클래스를 정의했다고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 클래스의 이름은 TYPE_VIRTIO_BLK(즉 &quot;virtio-blk-device&quot;)이고, 그 부모 클래스는 TYPE_VIRTIO_DEVICE(즉 &quot;virtio-device&quot;)이며, 클래스 초기화 함수는 virtio_blk_class_init이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 클래스를 사용해서 한 객체를 선언하면, 객체의 크기는 instance_size 즉 virtio_blk_instance_init이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;class_init이 XXXClass 생성&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 QEMU가 템플릿(TypeImpl)으로부터 클래스를 생성하고 인스턴스화하는 관련 코드를 구체적으로 살펴보자&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;DEVICE_CLASS 매크로&lt;/p&gt;
&lt;pre id=&quot;code_1781526806940&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define TYPE_DEVICE &quot;device&quot;
OBJECT_DECLARE_TYPE(DeviceState, DeviceClass, DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DEVICE_CLASS 매크로 전개 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dguJij/dJMcagTqz3K/RYRqx7lO3rMvMxlsnpbkK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dguJij/dJMcagTqz3K/RYRqx7lO3rMvMxlsnpbkK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dguJij/dJMcagTqz3K/RYRqx7lO3rMvMxlsnpbkK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdguJij%2FdJMcagTqz3K%2FRYRqx7lO3rMvMxlsnpbkK1%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;784&quot; height=&quot;280&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;OBJECT_DECLARE_TYPE의 관련 코드는 include/qom/object.h 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526829503&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * OBJECT_DECLARE_TYPE:
 * @InstanceType: instance struct name
 * @ClassType: class struct name
 * @MODULE_OBJ_NAME: the object name in uppercase with underscore separators
 *
 * This macro is typically used in a header file, and will:
 *
 *   - create the typedefs for the object and class structs
 *   - register the type for use with g_autoptr
 *   - provide three standard type cast functions
 *
 * The object struct and class struct need to be declared manually.
 */
#define OBJECT_DECLARE_TYPE(InstanceType, ClassType, MODULE_OBJ_NAME) \
    typedef struct InstanceType InstanceType; \
    typedef struct ClassType ClassType; \
    \
    G_DEFINE_AUTOPTR_CLEANUP_FUNC(InstanceType, object_unref) \
    \
    DECLARE_OBJ_CHECKERS(InstanceType, ClassType, \
                         MODULE_OBJ_NAME, TYPE_##MODULE_OBJ_NAME)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 실제 값을 대입하면&lt;/p&gt;
&lt;pre id=&quot;code_1781526852999&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;OBJECT_DECLARE_TYPE(DeviceState, DeviceClass, DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음을 얻는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526863801&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef struct DeviceState DeviceState;
typedef struct DeviceClass DeviceClass;

G_DEFINE_AUTOPTR_CLEANUP_FUNC(DeviceState, object_unref)

DECLARE_OBJ_CHECKERS(DeviceState, DeviceClass, DEVICE, TYPE_DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DECLARE_OBJ_CHECKERS 매크로의 정의는 include/qom/object.h 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526883586&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * DECLARE_OBJ_CHECKERS:
 * @InstanceType: instance struct name
 * @ClassType: class struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the three standard type cast functions for a
 * QOM type.
 */
#define DECLARE_OBJ_CHECKERS(InstanceType, ClassType, OBJ_NAME, TYPENAME) \
    DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    \
    DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DECLARE_INSTANCE_CHECKER 매크로의 정의도 같은 파일(include/qom/object.h) 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526900061&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * DECLARE_INSTANCE_CHECKER:
 * @InstanceType: instance struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the instance type cast functions for a
 * QOM type.
 */
#define DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    static inline G_GNUC_UNUSED InstanceType * \
    OBJ_NAME(const void *obj) \
    { return OBJECT_CHECK(InstanceType, obj, TYPENAME); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DECLARE_CLASS_CHECKERS 매크로의 정의는 당연히 include/qom/object.h 안에 있고, 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526915947&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * DECLARE_CLASS_CHECKERS:
 * @ClassType: class struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the class type cast functions for a
 * QOM type.
 */
#define DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME) \
    static inline G_GNUC_UNUSED ClassType * \
    OBJ_NAME##_GET_CLASS(const void *obj) \
    { return OBJECT_GET_CLASS(ClassType, obj, TYPENAME); } \
    \
    static inline G_GNUC_UNUSED ClassType * \
    OBJ_NAME##_CLASS(const void *klass) \
    { return OBJECT_CLASS_CHECK(ClassType, klass, TYPENAME); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게&lt;/p&gt;
&lt;pre id=&quot;code_1781526925773&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define DECLARE_OBJ_CHECKERS(InstanceType, ClassType, OBJ_NAME, TYPENAME) \
    DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    \
    DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526936625&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED InstanceType *OBJ_NAME(const void *obj)
{
    return OBJECT_CHECK(InstanceType, obj, TYPENAME);
}

static inline G_GNUC_UNUSED ClassType *OBJ_NAME##_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(ClassType, obj, TYPENAME);
}

static inline G_GNUC_UNUSED ClassType *OBJ_NAME##_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(ClassType, klass, TYPENAME);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;더 나아가, DECLARE_OBJ_CHECKERS()를 호출할 때 전달된 실제 값을 대입하면,&lt;/p&gt;
&lt;pre id=&quot;code_1781526952714&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DECLARE_OBJ_CHECKERS(DeviceState, DeviceClass, DEVICE, TYPE_DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781526960010&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define DECLARE_OBJ_CHECKERS(InstanceType, ClassType, OBJ_NAME, TYPENAME) \
    DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    \
    DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 얻는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526971006&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DECLARE_INSTANCE_CHECKER(DeviceState, DEVICE, TYPE_DEVICE)

DECLARE_CLASS_CHECKERS(DeviceClass, DEVICE, TYPE_DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가 다음을 얻는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781526981296&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED DeviceState *DEVICE(const void *obj)
{
    return OBJECT_CHECK(DeviceState, obj, TYPE_DEVICE);
}

static inline G_GNUC_UNUSED DeviceClass *DEVICE_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
}

static inline G_GNUC_UNUSED DeviceClass *DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(DeviceClass, klass, TYPE_DEVICE);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 이렇게 얻게된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781527023281&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED DeviceState *DEVICE(const void *obj)
{
    return OBJECT_CHECK(DeviceState, obj, &quot;device&quot;);
}

static inline G_GNUC_UNUSED DeviceClass *DEVICE_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(DeviceClass, obj, &quot;device&quot;);
}

static inline G_GNUC_UNUSED DeviceClass *DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(DeviceClass, klass, &quot;device&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DEVICE_CLASS의 정의를 명확히 한 후, 메인 코드로 돌아가자.&lt;/p&gt;
&lt;pre id=&quot;code_1781527038162&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_blk_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
    &amp;hellip;&amp;hellip;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안의&lt;/p&gt;
&lt;pre id=&quot;code_1781527046406&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DeviceClass *dc = DEVICE_CLASS(klass);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은 바로&lt;/p&gt;
&lt;pre id=&quot;code_1781527056200&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED DeviceClass *DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(DeviceClass, klass, &quot;device&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781527063964&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, klass, &quot;device&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio block device 전체 정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;1254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tRWUF/dJMcagTqz3O/LnuqNzECrNiwRODzS4F3Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tRWUF/dJMcagTqz3O/LnuqNzECrNiwRODzS4F3Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tRWUF/dJMcagTqz3O/LnuqNzECrNiwRODzS4F3Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtRWUF%2FdJMcagTqz3O%2FLnuqNzECrNiwRODzS4F3Lk%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;400&quot; height=&quot;716&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;1254&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DEVICE_CLASS 매크로 전개&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;918&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WuGxu/dJMcahSg1SZ/uoopJsEncjbWbcHmKqO3IK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WuGxu/dJMcahSg1SZ/uoopJsEncjbWbcHmKqO3IK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WuGxu/dJMcahSg1SZ/uoopJsEncjbWbcHmKqO3IK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWuGxu%2FdJMcahSg1SZ%2FuoopJsEncjbWbcHmKqO3IK%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;259&quot; height=&quot;646&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;918&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU의 객체 지향 핵심 함수&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVU4x7/dJMcaftqeNt/d4MoERPVsOwJYNEeNO1J8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVU4x7/dJMcaftqeNt/d4MoERPVsOwJYNEeNO1J8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVU4x7/dJMcaftqeNt/d4MoERPVsOwJYNEeNO1J8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVU4x7%2FdJMcaftqeNt%2Fd4MoERPVsOwJYNEeNO1J8K%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;494&quot; height=&quot;255&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;virtio_blk_class_init&amp;middot;instance_init&amp;middot;device_realize&lt;/h4&gt;
&lt;pre id=&quot;code_1781527834000&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_blk_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
    ......
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781527845160&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781527853740&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED VirtioDeviceClass *VIRTIO_DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(VirtioDeviceClass, klass, &quot;virtio-device&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781527864181&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtioDeviceClass *vdc = OBJECT_CLASS_CHECK(VirtioDeviceClass, klass, &quot;virtio-device&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 virtio_blk_class_init 에서 DeviceClass와 VirtioDeviceClass 생성된다.&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;&lt;/p&gt;
&lt;pre id=&quot;code_1781527935126&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type_init()
    ---&amp;gt; type_register_static()
        ---&amp;gt; type_register()
            ---&amp;gt; type_register_internal()
                ---&amp;gt; type_new()
                ---&amp;gt; type_table_add()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chSYJw/dJMcahEKd22/5DYoJkUD8nkgVJ8O2v27OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chSYJw/dJMcahEKd22/5DYoJkUD8nkgVJ8O2v27OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chSYJw/dJMcahEKd22/5DYoJkUD8nkgVJ8O2v27OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchSYJw%2FdJMcahEKd22%2F5DYoJkUD8nkgVJ8O2v27OK%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;323&quot; height=&quot;475&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&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;중점적으로 봐야 할 건 type_register_internal 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781528084648&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static TypeImpl *type_register_internal(const TypeInfo *info)
{
    TypeImpl *ti;
    ti = type_new(info);

    type_table_add(ti);
    return ti;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-sourcepos=&quot;1153:1-1153:147;37278-37424&quot; data-ke-size=&quot;size16&quot;&gt;type_register_internal 함수에서는, virtio_blk_info라는 TypeInfo를 기반으로, TypeImpl 하나를 만들어서 이 새로 등록된 클래스를 표현한다. 바꿔 말하면, TypeImpl이야말로 우리가 진짜로 선언하고 싶은 그 클래스다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;1153:1-1153:147;37278-37424&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;1155:1-1155:276;37426-37701&quot; data-ke-size=&quot;size16&quot;&gt;QEMU에는 전역 해시 테이블 type_table이 있어서, 정의된 모든 클래스를 저장하는 데 쓴다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;1155:1-1155:276;37426-37701&quot; data-ke-size=&quot;size16&quot;&gt;type_register_internal 함수에서는, 먼저 type_new 함수를 호출해서, 전역 테이블에서 이름(이 예에서는 TypeInfo virtio_blk_info)에 따라 이 클래스를 찾는다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;1155:1-1155:276;37426-37701&quot; data-ke-size=&quot;size16&quot;&gt;찾으면, 이 클래스가 이미 등록된 적이 있다는 뜻이니까, 에러를 낸다;&lt;/p&gt;
&lt;p data-sourcepos=&quot;1155:1-1155:276;37426-37701&quot; data-ke-size=&quot;size16&quot;&gt;못 찾으면, 이 클래스는 새로운 클래스라는 뜻이니까, TypeInfo 안의 정보를 TypeImpl 안에 채워 넣는다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;1155:1-1155:276;37426-37701&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;1157:1-1157:98;37703-37800&quot; data-ke-size=&quot;size16&quot;&gt;이 단계까지 오면, 클래스 초기화 class_init(이 예에서는 virtio_blk_class_init) 함수는 아직 호출되지 않았고, 이 클래스는 아직 정의 상태에 있다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;1157:1-1157:98;37703-37800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781528126400&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static TypeImpl *type_new(const TypeInfo *info)
{
    TypeImpl *ti = g_malloc0(sizeof(*ti));
    int i;

    g_assert(info-&amp;gt;name != NULL);

    if (type_table_lookup(info-&amp;gt;name) != NULL) {
        fprintf(stderr, &quot;Registering `%s' which already exists\n&quot;, info-&amp;gt;name);
        abort();
    }

    ti-&amp;gt;name = g_strdup(info-&amp;gt;name);
    ti-&amp;gt;parent = g_strdup(info-&amp;gt;parent);

    ti-&amp;gt;class_size = info-&amp;gt;class_size;
    ti-&amp;gt;instance_size = info-&amp;gt;instance_size;
    ti-&amp;gt;instance_align = info-&amp;gt;instance_align;

    ti-&amp;gt;class_init = info-&amp;gt;class_init;
    ti-&amp;gt;class_base_init = info-&amp;gt;class_base_init;
    ti-&amp;gt;class_data = info-&amp;gt;class_data;

    ti-&amp;gt;instance_init = info-&amp;gt;instance_init;
    ti-&amp;gt;instance_post_init = info-&amp;gt;instance_post_init;
    ti-&amp;gt;instance_finalize = info-&amp;gt;instance_finalize;

    ti-&amp;gt;abstract = info-&amp;gt;abstract;

    for (i = 0; info-&amp;gt;interfaces &amp;amp;&amp;amp; info-&amp;gt;interfaces[i].type; i++) {
        ti-&amp;gt;interfaces[i].typename = g_strdup(info-&amp;gt;interfaces[i].type);
    }
    ti-&amp;gt;num_interfaces = i;

    return ti;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;type_new 함수의 동작 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;1124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/erPCij/dJMcabYUxQJ/8guwiN9nK5jTwWXUBFjykk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/erPCij/dJMcabYUxQJ/8guwiN9nK5jTwWXUBFjykk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/erPCij/dJMcabYUxQJ/8guwiN9nK5jTwWXUBFjykk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FerPCij%2FdJMcabYUxQJ%2F8guwiN9nK5jTwWXUBFjykk%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;365&quot; height=&quot;776&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;1124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;type_new 함수는 시작하자마자 먼저 sizeof(TypeImpl) 크기의 공간을 할당해서, TyptImpl *ti가 이 공간을 가리키도록 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781528209000&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TypeImpl *ti = g_malloc0(sizeof(*ti));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그 다음에는 전달된 함수 파라미터 TypeInfo *info 안의 name 멤버가 비어 있으면 안 된다는 걸 보장해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781528226941&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;g_assert(info-&amp;gt;name != NULL);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 조건은 일반적으로 만족시킬 수 있다. 예를 들면 virtio_blk_info 안의 name 멤버는 TYPE_VIRTIO_BLK, 즉 &quot;virtio-blk-device&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;다음으로 오는 게 type_new 함수에서 가장 핵심적인 코드다.&lt;/p&gt;
&lt;pre id=&quot;code_1781528280078&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (type_table_lookup(info-&amp;gt;name) != NULL) {
        fprintf(stderr, &quot;Registering `%s' which already exists\n&quot;, info-&amp;gt;name);
        abort();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이게 바로 위에서 말한 거다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;먼저 type_new 함수를 호출해서, 전역 테이블에서 이름(이 예에서는 TypeInfo virtio_blk_info)에 따라 이 클래스를 찾는다. 찾으면, 이 클래스가 이미 등록된 적이 있다는 뜻이니까, 에러를 낸다;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type_table_lookup 함수도 qom/object.c 안에(바로 type_new 함수 위에) 있고, 코드는 다음과 같다&lt;/p&gt;
&lt;pre id=&quot;code_1781528311017&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static TypeImpl *type_table_lookup(const char *name)
{
    return g_hash_table_lookup(type_table_get(), name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;type_table_get 함수도 qom/object.c 안에 있고, 코드는 다음과 같다&lt;/p&gt;
&lt;pre id=&quot;code_1781528329683&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static GHashTable *type_table_get(void)
{
    static GHashTable *type_table;

    if (type_table == NULL) {
        type_table = g_hash_table_new(g_str_hash, g_str_equal);
    }

    return type_table;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 GHashTable *type_table이 바로 위에서 언급한 전역(해시) 테이블이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781528352666&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    ti-&amp;gt;name = g_strdup(info-&amp;gt;name);
    ti-&amp;gt;parent = g_strdup(info-&amp;gt;parent);

    ti-&amp;gt;class_size = info-&amp;gt;class_size;
    ti-&amp;gt;instance_size = info-&amp;gt;instance_size;
    ti-&amp;gt;instance_align = info-&amp;gt;instance_align;

    ti-&amp;gt;class_init = info-&amp;gt;class_init;
    ti-&amp;gt;class_base_init = info-&amp;gt;class_base_init;
    ti-&amp;gt;class_data = info-&amp;gt;class_data;

    ti-&amp;gt;instance_init = info-&amp;gt;instance_init;
    ti-&amp;gt;instance_post_init = info-&amp;gt;instance_post_init;
    ti-&amp;gt;instance_finalize = info-&amp;gt;instance_finalize;

    ti-&amp;gt;abstract = info-&amp;gt;abstract;

    for (i = 0; info-&amp;gt;interfaces &amp;amp;&amp;amp; info-&amp;gt;interfaces[i].type; i++) {
        ti-&amp;gt;interfaces[i].typename = g_strdup(info-&amp;gt;interfaces[i].type);
    }
    ti-&amp;gt;num_interfaces = i;

    return ti;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이게 바로 위에서 말한 것에 대응된다&lt;/p&gt;
&lt;blockquote data-pm-slice=&quot;0 0 []&quot; data-ke-style=&quot;style2&quot;&gt;못 찾으면, 이 클래스는 새로운 클래스라는 뜻이니까, TypeInfo 안의 정보를 TypeImpl 안에 채워 넣는다.&lt;/blockquote&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 해서, type_new 함수 해석이 끝났다.&lt;/p&gt;
&lt;pre id=&quot;code_1781528428167&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static TypeImpl *type_register_internal(const TypeInfo *info)
{
    TypeImpl *ti;
    ti = type_new(info);

    type_table_add(ti);
    return ti;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;앞에서 이미 설명했었다&lt;/p&gt;
&lt;blockquote data-sourcepos=&quot;1310:3-1310:167;40911-41075&quot; data-ke-style=&quot;style2&quot;&gt;type_register_internal 함수(qom/object.c 안) 안에서는, virtio_blk_info라는 TypeInfo를 기반으로, &lt;br /&gt;TypeImpl 하나를 만들어서 이 새로 등록된 클래스를 표현한다. 바꿔 말하면, TypeImpl이야말로 우리가 진짜로 선언하고 싶은 그 클래스다. &lt;br /&gt;QEMU에는 전역 해시 테이블 type_table이 있어서, 정의된 모든 클래스를 저장하는 데 쓴다. &lt;br /&gt;type_register_internal 함수에서는, 먼저 type_new 함수를 호출해서, 전역 테이블에서 이름(이 예에서는 TypeInfo virtio_blk_info)에 따라 이 클래스를 찾는다. &lt;br /&gt;찾으면, 이 클래스가 이미 등록된 적이 있다는 뜻이니까, 에러를 낸다; 못 찾으면, 이 클래스는 새로운 클래스라는 뜻이니까, TypeInfo 안의 정보를 TypeImpl 안에 채워 넣는다.&lt;/blockquote&gt;
&lt;p data-sourcepos=&quot;1312:3-1312:278;41080-41355&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;type_new 함수는 이미 TypeInfo virtio_blk_info 안의 정보를 함수 안에서 새로 만든(g_malloc0()을 호출해서 동적으로 만든) TypeImpl 객체에 할당했고, ti가 이 메모리를 가리키게 했다. 다음으로 오는 게 type_table_add 함수를 호출해서, 이 객체를 전역 테이블 type_table에 추가(삽입)하는 거다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeInfo &amp;rarr; TypeImpl &amp;rarr; 전역 해시 테이블 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p9xSR/dJMcacpYCOf/dTnOUSHKE1HXvn8p8R3P91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p9xSR/dJMcacpYCOf/dTnOUSHKE1HXvn8p8R3P91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p9xSR/dJMcacpYCOf/dTnOUSHKE1HXvn8p8R3P91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp9xSR%2FdJMcacpYCOf%2FdTnOUSHKE1HXvn8p8R3P91%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;632&quot; height=&quot;162&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;201&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781530084308&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void type_table_add(TypeImpl *ti)
{
    assert(!enumerating_types);
    g_hash_table_insert(type_table_get(), (void *)ti-&amp;gt;name, ti);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781530093091&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static GHashTable *type_table_get(void)
{
    static GHashTable *type_table;

    if (type_table == NULL) {
        type_table = g_hash_table_new(g_str_hash, g_str_equal);
    }

    return type_table;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;type_table_get 함수는 비교적 간단하고 이해하기 쉽다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;만약 type_table이 NULL이면 즉 전에 만들어진 적이 없으면, g_hash_table_new 함수를 호출해서 만든다. 그 다음에 다시 이 함수를 호출할 때는, 직접 만들어진 type_teble을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;g_hash_table_new 함수는 hash 테이블 하나를 만드는 데 쓰이고; g_hash_table_insert 함수는 (만들어진) hash 테이블 안에 항목을 삽입하는 데 쓰이며, 삽입되는 항목은 반드시 하나의 key-value 쌍이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781530116828&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;gboolean
g_hash_table_insert (GHashTable *hash_table,
                     gpointer    key,
                     gpointer    value);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;GHashTable 안에 새로운 key와 value를 삽입하는데, 만약 오래된 key가 존재하면, value 값이 새로운 value 값으로 덮어쓰여진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, key에 대응되는 건 (void *)ti-&amp;gt;name이고, value에 대응되는 건 ti 자기 자신이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 해서, TypeImpl로 이미 클래스를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 클래스 초기화 class_init(이 예에서는 virtio_blk_class_init) 함수는 아직 호출되지 않았고, 이 클래스는 아직 정의 상태에 있다(type_init 함수는 클래스를 등록하는 데 쓰이고, 각 계층마다 class_init이 있어서, TypeImpl로 xxxClass를 생성하는 데 쓴다).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_blk_device_realize와 VirtIODevice 구조&lt;/h4&gt;
&lt;p data-sourcepos=&quot;1412:1-1412:110;44838-44947&quot; data-ke-size=&quot;size16&quot;&gt;앞에서 QEMU 클래스 메커니즘이 템플릿(TypeImpl)으로부터 클래스와 인스턴스를 생성하는 흐름을 설명했다. 이번부터 시작해서, 계속 아래로 블록 디바이스 가상화의 메커니즘과 흐름을 설명한다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;1414:1-1414:78;44949-45026&quot; data-ke-size=&quot;size16&quot;&gt;다시 한번 TYPE_VIRTIO_BLK 계층의 클래스 선언을 돌아보자,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781530264462&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const TypeInfo virtio_blk_info = {
    .name = TYPE_VIRTIO_BLK,
    .parent = TYPE_VIRTIO_DEVICE,
    .instance_size = sizeof(VirtIOBlock),
    .instance_init = virtio_blk_instance_init,
    .class_init = virtio_blk_class_init,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-sourcepos=&quot;1414:1-1414:78;44949-45026&quot; data-ke-size=&quot;size16&quot;&gt;virtio_blk_info 안의 class_init이 가리키는 함수는 virtio_blk_class_init이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530279994&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_blk_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);

    device_class_set_props(dc, virtio_blk_properties);
    dc-&amp;gt;vmsd = &amp;amp;vmstate_virtio_blk;
    set_bit(DEVICE_CATEGORY_STORAGE, dc-&amp;gt;categories);
    vdc-&amp;gt;realize = virtio_blk_device_realize;
    vdc-&amp;gt;unrealize = virtio_blk_device_unrealize;
    vdc-&amp;gt;get_config = virtio_blk_update_config;
    vdc-&amp;gt;set_config = virtio_blk_set_config;
    vdc-&amp;gt;get_features = virtio_blk_get_features;
    vdc-&amp;gt;set_status = virtio_blk_set_status;
    vdc-&amp;gt;reset = virtio_blk_reset;
    vdc-&amp;gt;save = virtio_blk_save_device;
    vdc-&amp;gt;load = virtio_blk_load_device;
    vdc-&amp;gt;start_ioeventfd = virtio_blk_data_plane_start;
    vdc-&amp;gt;stop_ioeventfd = virtio_blk_data_plane_stop;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_blk_class_init 함수 안에서, VirtioDeviceClass의 realize 함수를 virtio_blk_device_realize()로 정의한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530232848&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
    VirtIOBlock *s = VIRTIO_BLK(dev);
    VirtIOBlkConf *conf = &amp;amp;s-&amp;gt;conf;
    Error *err = NULL;
    unsigned i;

    if (!conf-&amp;gt;conf.blk) {
        error_setg(errp, &quot;drive property not set&quot;);
        return;
    }
    if (!blk_is_inserted(conf-&amp;gt;conf.blk)) {
        error_setg(errp, &quot;Device needs media, but drive is empty&quot;);
        return;
    }
    if (conf-&amp;gt;num_queues == VIRTIO_BLK_AUTO_NUM_QUEUES) {
        conf-&amp;gt;num_queues = 1;
    }
    if (!conf-&amp;gt;num_queues) {
        error_setg(errp, &quot;num-queues property must be larger than 0&quot;);
        return;
    }
    if (conf-&amp;gt;queue_size &amp;lt;= 2) {
        error_setg(errp, &quot;invalid queue-size property (%&quot; PRIu16 &quot;), &quot;
                   &quot;must be &amp;gt; 2&quot;, conf-&amp;gt;queue_size);
        return;
    }
    if (!is_power_of_2(conf-&amp;gt;queue_size) ||
        conf-&amp;gt;queue_size &amp;gt; VIRTQUEUE_MAX_SIZE) {
        error_setg(errp, &quot;invalid queue-size property (%&quot; PRIu16 &quot;), &quot;
                   &quot;must be a power of 2 (max %d)&quot;,
                   conf-&amp;gt;queue_size, VIRTQUEUE_MAX_SIZE);
        return;
    }

    if (!blkconf_apply_backend_options(&amp;amp;conf-&amp;gt;conf,
                                       !blk_supports_write_perm(conf-&amp;gt;conf.blk),
                                       true, errp)) {
        return;
    }
    s-&amp;gt;original_wce = blk_enable_write_cache(conf-&amp;gt;conf.blk);
    if (!blkconf_geometry(&amp;amp;conf-&amp;gt;conf, NULL, 65535, 255, 255, errp)) {
        return;
    }

    if (!blkconf_blocksizes(&amp;amp;conf-&amp;gt;conf, errp)) {
        return;
    }

    BlockDriverState *bs = blk_bs(conf-&amp;gt;conf.blk);
    if (bs-&amp;gt;bl.zoned != BLK_Z_NONE) {
        virtio_add_feature(&amp;amp;s-&amp;gt;host_features, VIRTIO_BLK_F_ZONED);
        if (bs-&amp;gt;bl.zoned == BLK_Z_HM) {
            virtio_clear_feature(&amp;amp;s-&amp;gt;host_features, VIRTIO_BLK_F_DISCARD);
        }
    }

    if (virtio_has_feature(s-&amp;gt;host_features, VIRTIO_BLK_F_DISCARD) &amp;amp;&amp;amp;
        (!conf-&amp;gt;max_discard_sectors ||
         conf-&amp;gt;max_discard_sectors &amp;gt; BDRV_REQUEST_MAX_SECTORS)) {
        error_setg(errp, &quot;invalid max-discard-sectors property (%&quot; PRIu32 &quot;)&quot;
                   &quot;, must be between 1 and %d&quot;,
                   conf-&amp;gt;max_discard_sectors, (int)BDRV_REQUEST_MAX_SECTORS);
        return;
    }

    if (virtio_has_feature(s-&amp;gt;host_features, VIRTIO_BLK_F_WRITE_ZEROES) &amp;amp;&amp;amp;
        (!conf-&amp;gt;max_write_zeroes_sectors ||
         conf-&amp;gt;max_write_zeroes_sectors &amp;gt; BDRV_REQUEST_MAX_SECTORS)) {
        error_setg(errp, &quot;invalid max-write-zeroes-sectors property (%&quot; PRIu32
                   &quot;), must be between 1 and %d&quot;,
                   conf-&amp;gt;max_write_zeroes_sectors,
                   (int)BDRV_REQUEST_MAX_SECTORS);
        return;
    }

    s-&amp;gt;config_size = virtio_get_config_size(&amp;amp;virtio_blk_cfg_size_params,
                                            s-&amp;gt;host_features);
    virtio_init(vdev, VIRTIO_ID_BLOCK, s-&amp;gt;config_size);

    s-&amp;gt;blk = conf-&amp;gt;conf.blk;
    s-&amp;gt;rq = NULL;
    s-&amp;gt;sector_mask = (s-&amp;gt;conf.conf.logical_block_size / BDRV_SECTOR_SIZE) - 1;

    for (i = 0; i &amp;lt; conf-&amp;gt;num_queues; i++) {
        virtio_add_queue(vdev, conf-&amp;gt;queue_size, virtio_blk_handle_output);
    }
    qemu_coroutine_inc_pool_size(conf-&amp;gt;num_queues * conf-&amp;gt;queue_size / 2);
    virtio_blk_data_plane_create(vdev, conf, &amp;amp;s-&amp;gt;dataplane, &amp;amp;err);
    if (err != NULL) {
        error_propagate(errp, err);
        for (i = 0; i &amp;lt; conf-&amp;gt;num_queues; i++) {
            virtio_del_queue(vdev, i);
        }
        virtio_cleanup(vdev);
        return;
    }

    /*
     * This must be after virtio_init() so virtio_blk_dma_restart_cb() gets
     * called after -&amp;gt;start_ioeventfd() has already set blk's AioContext.
     */
    s-&amp;gt;change =
        qdev_add_vm_change_state_handler(dev, virtio_blk_dma_restart_cb, s);

    blk_ram_registrar_init(&amp;amp;s-&amp;gt;blk_ram_registrar, s-&amp;gt;blk);
    blk_set_dev_ops(s-&amp;gt;blk, &amp;amp;virtio_block_ops, s);

    blk_iostatus_enable(s-&amp;gt;blk);

    add_boot_device_lchs(dev, &quot;/disk@0,0&quot;,
                         conf-&amp;gt;conf.lcyls,
                         conf-&amp;gt;conf.lheads,
                         conf-&amp;gt;conf.lsecs);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_blk_device_realize 함수의 주요 동작&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;1112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBe12i/dJMcaglylWC/NoP4SviRVRpyE0SFvInWBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBe12i/dJMcaglylWC/NoP4SviRVRpyE0SFvInWBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBe12i/dJMcaglylWC/NoP4SviRVRpyE0SFvInWBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBe12i%2FdJMcaglylWC%2FNoP4SviRVRpyE0SFvInWBk%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;230&quot; height=&quot;797&quot; data-origin-width=&quot;321&quot; data-origin-height=&quot;1112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_blk_device_realize 함수 안에서는, 먼저 virtio_init 함수를 통해 VirtIODevice 구조를 초기화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530324934&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_init(vdev, VIRTIO_ID_BLOCK, s-&amp;gt;config_size);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781530333545&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void virtio_init(VirtIODevice *vdev, uint16_t device_id, size_t config_size)
{
    BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
    VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
    int i;
    int nvectors = k-&amp;gt;query_nvectors ? k-&amp;gt;query_nvectors(qbus-&amp;gt;parent) : 0;

    if (nvectors) {
        vdev-&amp;gt;vector_queues =
            g_malloc0(sizeof(*vdev-&amp;gt;vector_queues) * nvectors);
    }

    vdev-&amp;gt;start_on_kick = false;
    vdev-&amp;gt;started = false;
    vdev-&amp;gt;vhost_started = false;
    vdev-&amp;gt;device_id = device_id;
    vdev-&amp;gt;status = 0;
    qatomic_set(&amp;amp;vdev-&amp;gt;isr, 0);
    vdev-&amp;gt;queue_sel = 0;
    vdev-&amp;gt;config_vector = VIRTIO_NO_VECTOR;
    vdev-&amp;gt;vq = g_new0(VirtQueue, VIRTIO_QUEUE_MAX);
    vdev-&amp;gt;vm_running = runstate_is_running();
    vdev-&amp;gt;broken = false;
    for (i = 0; i &amp;lt; VIRTIO_QUEUE_MAX; i++) {
        vdev-&amp;gt;vq[i].vector = VIRTIO_NO_VECTOR;
        vdev-&amp;gt;vq[i].vdev = vdev;
        vdev-&amp;gt;vq[i].queue_index = i;
        vdev-&amp;gt;vq[i].host_notifier_enabled = false;
    }

    vdev-&amp;gt;name = virtio_id_to_name(device_id);
    vdev-&amp;gt;config_len = config_size;
    if (vdev-&amp;gt;config_len) {
        vdev-&amp;gt;config = g_malloc0(config_size);
    } else {
        vdev-&amp;gt;config = NULL;
    }
    vdev-&amp;gt;vmstate = qdev_add_vm_change_state_handler(DEVICE(vdev),
            virtio_vmstate_change, vdev);
    vdev-&amp;gt;device_endian = virtio_default_endian();
    vdev-&amp;gt;use_guest_notifier_mask = true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_init 함수 코드에서 볼 수 있듯이, VirtIODevice 구조(VirtIODevice *vdev) 안에는 VirtQueue 배열이 하나 있는데, 이게 바로 virtio 안에서 프론트엔드와 백엔드가 서로 데이터를 전송하는 큐이고, 최대 VIRTIO_QUEUE_MAX개까지 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530354493&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vdev-&amp;gt;vq = g_new0(VirtQueue, VIRTIO_QUEUE_MAX);
    vdev-&amp;gt;vm_running = runstate_is_running();
    vdev-&amp;gt;broken = false;
    for (i = 0; i &amp;lt; VIRTIO_QUEUE_MAX; i++) {
        vdev-&amp;gt;vq[i].vector = VIRTIO_NO_VECTOR;
        vdev-&amp;gt;vq[i].vdev = vdev;
        vdev-&amp;gt;vq[i].queue_index = i;
        vdev-&amp;gt;vq[i].host_notifier_enabled = false;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtIODevice와 VirtQueue 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7bLxp/dJMcafmH1aB/jMilBkTfkBwksbe9rPYL60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7bLxp/dJMcafmH1aB/jMilBkTfkBwksbe9rPYL60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7bLxp/dJMcafmH1aB/jMilBkTfkBwksbe9rPYL60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7bLxp%2FdJMcafmH1aB%2FjMilBkTfkBwksbe9rPYL60%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;667&quot; height=&quot;227&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 점은 VirtIODevice 구조의 정의에서도 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530389481&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * struct VirtIODevice - common VirtIO structure
 * @name: name of the device
 * @status: VirtIO Device Status field
 *
 */
struct VirtIODevice
{
    DeviceState parent_obj;
    const char *name;
    uint8_t status;
    uint8_t isr;
    uint16_t queue_sel;
    /**
     * These fields represent a set of VirtIO features at various
     * levels of the stack. @host_features indicates the complete
     * feature set the VirtIO device can offer to the driver.
     * @guest_features indicates which features the VirtIO driver has
     * selected by writing to the feature register. Finally
     * @backend_features represents everything supported by the
     * backend (e.g. vhost) and could potentially be a subset of the
     * total feature set offered by QEMU.
     */
    uint64_t host_features;
    uint64_t guest_features;
    uint64_t backend_features;

    size_t config_len;
    void *config;
    uint16_t config_vector;
    uint32_t generation;
    int nvectors;
    VirtQueue *vq;
    MemoryListener listener;
    uint16_t device_id;
    /* @vm_running: current VM running state via virtio_vmstate_change() */
    bool vm_running;
    bool broken; /* device in invalid state, needs reset */
    bool use_disabled_flag; /* allow use of 'disable' flag when needed */
    bool disabled; /* device in temporarily disabled state */
    /**
     * @use_started: true if the @started flag should be used to check the
     * current state of the VirtIO device. Otherwise status bits
     * should be checked for a current status of the device.
     * @use_started is only set via QMP and defaults to true for all
     * modern machines (since 4.1).
     */
    bool use_started;
    bool started;
    bool start_on_kick; /* when virtio 1.0 feature has not been negotiated */
    bool disable_legacy_check;
    bool vhost_started;
    VMChangeStateEntry *vmstate;
    char *bus_name;
    uint8_t device_endian;
    /**
     * @user_guest_notifier_mask: gate usage of -&amp;gt;guest_notifier_mask() callback.
     * This is used to suppress the masking of guest updates for
     * vhost-user devices which are asynchronous by design.
     */
    bool use_guest_notifier_mask;
    AddressSpace *dma_as;
    QLIST_HEAD(, VirtQueue) vector_queues;
    QTAILQ_ENTRY(VirtIODevice) next;
    /**
     * @config_notifier: the event notifier that handles config events
     */
    EventNotifier config_notifier;
    bool device_iotlb_enabled;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그 중에서 중점적으로 다음 한 줄의 정의에 주목해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781530412311&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtQueue *vq;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VIRTIO_QUEUE_MAX는 매크로 하나인데, 그 정의는 include/hw/virtio/virtio.h 안에 있고, 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530429247&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define VIRTIO_QUEUE_MAX 1024&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;즉, virtio 안에서 프론트엔드와 백엔드가 서로 데이터를 전송하는 큐는 최대 1024개까지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지의 모든 흐름을 시간 순서대로 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eCVVSE/dJMcaalj0o9/kNejysdasuAgvnSV4dfgZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eCVVSE/dJMcaalj0o9/kNejysdasuAgvnSV4dfgZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eCVVSE/dJMcaalj0o9/kNejysdasuAgvnSV4dfgZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeCVVSE%2FdJMcaalj0o9%2FkNejysdasuAgvnSV4dfgZk%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;784&quot; height=&quot;553&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;1728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgL4H0/dJMcabR3KgO/hxYFFERA85YGvAh7s12s4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgL4H0/dJMcabR3KgO/hxYFFERA85YGvAh7s12s4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgL4H0/dJMcabR3KgO/hxYFFERA85YGvAh7s12s4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgL4H0%2FdJMcabR3KgO%2FhxYFFERA85YGvAh7s12s4K%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;333&quot; height=&quot;996&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;1728&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;1918:1-1918:153;61067-61219&quot;&gt;&lt;b&gt;정의 상태&lt;/b&gt;: TypeInfo로 정적 정의 &amp;rarr; type_init &amp;rarr; type_register_static &amp;rarr; type_new &amp;rarr; type_table_add &amp;rarr; 전역 해시 테이블 type_table에 TypeImpl로 등록된다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;1920:1-1920:166;61221-61386&quot;&gt;&lt;b&gt;Class 객체 생성&lt;/b&gt;: class_init (예: virtio_blk_class_init)이 호출되면서 DEVICE_CLASS, VIRTIO_DEVICE_CLASS 같은 매크로를 통해 DeviceClass, VirtioDeviceClass가 생성된다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;1922:1-1922:111;61388-61498&quot;&gt;&lt;b&gt;Instance 객체 생성&lt;/b&gt;: instance_init (예: virtio_blk_instance_init)이 호출되면서 실제 객체 VirtIOBlock이 생성된다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;1924:1-1924:193;61500-61692&quot;&gt;&lt;b&gt;Realize 단계&lt;/b&gt;: virtio_blk_device_realize 함수가 호출되면서 virtio_init을 통해 VirtIODevice 구조가 초기화되고, 최대 1024개까지의 VirtQueue가 할당된다. 이게 바로 virtio의 프론트엔드(Guest)와 백엔드(Host/QEMU) 사이에 데이터를 주고받는 큐다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;num_queues 할당과 virtio_add_queue&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;먼저 이 conf-&amp;gt;num_queues가 언제, 어디서 값이 할당되는지 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_blk_device_realize 함수 안에서, 관련된 코드는 딱 한 군데뿐인데, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530543721&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_blk_device_realize(DeviceState *dev, Error **errp)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
    VirtIOBlock *s = VIRTIO_BLK(dev);
    VirtIOBlkConf *conf = &amp;amp;s-&amp;gt;conf;
    Error *err = NULL;
    unsigned i;

    if (!conf-&amp;gt;conf.blk) {
        error_setg(errp, &quot;drive property not set&quot;);
        return;
    }
    if (!blk_is_inserted(conf-&amp;gt;conf.blk)) {
        error_setg(errp, &quot;Device needs media, but drive is empty&quot;);
        return;
    }
    if (conf-&amp;gt;num_queues == VIRTIO_BLK_AUTO_NUM_QUEUES) {  // &amp;larr; 여기 주목
        conf-&amp;gt;num_queues = 1;                              // &amp;larr; 여기 주목
    }
    if (!conf-&amp;gt;num_queues) {
        error_setg(errp, &quot;num-queues property must be larger than 0&quot;);
        return;
    }&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;conf는 s-&amp;gt;conf에서 온 거고, s는 DeviceState *dev를 통해 얻어진 거다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530562142&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtIOBlock *s = VIRTIO_BLK(dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lKfXT/dJMcaaFIvJj/KeopRKuW5UyGjpLGWqNxqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lKfXT/dJMcaaFIvJj/KeopRKuW5UyGjpLGWqNxqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lKfXT/dJMcaaFIvJj/KeopRKuW5UyGjpLGWqNxqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlKfXT%2FdJMcaaFIvJj%2FKeopRKuW5UyGjpLGWqNxqk%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;784&quot; height=&quot;63&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VIRTIO_BLK() 정의 관련 코드는 include/hw/virtio/virtio-blk.h에 있고, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530592276&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define TYPE_VIRTIO_BLK &quot;virtio-blk-device&quot;
OBJECT_DECLARE_SIMPLE_TYPE(VirtIOBlock, VIRTIO_BLK)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VIRTIO_BLK()의 의미는 이전 글에서 다룬 DEVICE_CLASS(), VIRTIO_DEVICE_CLASS()와 기본적으로 똑같다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtIOBlkConf의 정의도 include/hw/virtio/virtio-blk.h에 있고, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530641478&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct VirtIOBlkConf
{
    BlockConf conf;
    IOThread *iothread;
    char *serial;
    uint32_t request_merging;
    uint16_t num_queues;
    uint16_t queue_size;
    bool seg_max_adjust;
    bool report_discard_granularity;
    uint32_t max_discard_sectors;
    uint32_t max_write_zeroes_sectors;
    bool x_enable_wce_if_config_wce;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 본 s-&amp;gt;conf.num_queues 값이 정해지는 경로는 다음과 같다(QEMU 8.1.3 기준).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 hw/block/virtio-blk.c에서 QOM 프로퍼티로 등록되어 있다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;DEFINE_PROP_UINT16(&quot;num-queues&quot;, VirtIOBlock, conf.num_queues, VIRTIO_BLK_AUTO_NUM_QUEUES)&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;즉 기본값은 VIRTIO_BLK_AUTO_NUM_QUEUES(=UINT16_MAX)이고, 사용자가 -device virtio-blk-pci,num-queues=N으로 지정하면 그 값이 들어간다.&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;값을 지정하지 않아 AUTO 상태로 남으면, hw/virtio/virtio-blk-pci.c의 virtio_blk_pci_realize 함수에서 virtio_pci_optimal_num_queues()로 vCPU 수에 맞는 최적값을 채운다.&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;그 뒤 virtio_blk_device_realize에서 혹시 아직 AUTO이면 최종적으로 1로 설정한다. 코드는 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781530719786&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_blk_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
{
    VirtIOBlkPCI *dev = VIRTIO_BLK_PCI(vpci_dev);
    DeviceState *vdev = DEVICE(&amp;amp;dev-&amp;gt;vdev);
    VirtIOBlkConf *conf = &amp;amp;dev-&amp;gt;vdev.conf;

    if (conf-&amp;gt;num_queues == VIRTIO_BLK_AUTO_NUM_QUEUES) {
        conf-&amp;gt;num_queues = virtio_pci_optimal_num_queues(0);
    }

    if (vpci_dev-&amp;gt;nvectors == DEV_NVECTORS_UNSPECIFIED) {
        vpci_dev-&amp;gt;nvectors = conf-&amp;gt;num_queues + 1;
    }

    qdev_realize(vdev, BUS(&amp;amp;vpci_dev-&amp;gt;bus), errp);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;num_queues 할당 두 가지 경로&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;1400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOVumU/dJMcafNNwiy/2bhHBRYK1fkT1DjIvwDVDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOVumU/dJMcafNNwiy/2bhHBRYK1fkT1DjIvwDVDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOVumU/dJMcafNNwiy/2bhHBRYK1fkT1DjIvwDVDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOVumU%2FdJMcafNNwiy%2F2bhHBRYK1fkT1DjIvwDVDk%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;316&quot; height=&quot;977&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;1400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_add_queue 함수 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_blk_device_realize 함수 안에 있는 virtio_add_queue 함수를 분석한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530834613&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; conf-&amp;gt;num_queues; i++) {
    virtio_add_queue(vdev, conf-&amp;gt;queue_size, virtio_blk_handle_output);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_add_queue 함수는 hw/virtio/virtio.c에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530849163&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
                            VirtIOHandleOutput handle_output)
{
    int i;

    for (i = 0; i &amp;lt; VIRTIO_QUEUE_MAX; i++) {
        if (vdev-&amp;gt;vq[i].vring.num == 0)
            break;
    }

    if (i == VIRTIO_QUEUE_MAX || queue_size &amp;gt; VIRTQUEUE_MAX_SIZE)
        abort();

    vdev-&amp;gt;vq[i].vring.num = queue_size;
    vdev-&amp;gt;vq[i].vring.num_default = queue_size;
    vdev-&amp;gt;vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN;
    vdev-&amp;gt;vq[i].handle_output = handle_output;
    vdev-&amp;gt;vq[i].used_elems = g_new0(VirtQueueElement, queue_size);

    return &amp;amp;vdev-&amp;gt;vq[i];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_add_queue 동작 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;1612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKe9Ni/dJMcacpYCO2/o4G2TRUQFDYMD0P6Qxwxs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKe9Ni/dJMcacpYCO2/o4G2TRUQFDYMD0P6Qxwxs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKe9Ni/dJMcacpYCO2/o4G2TRUQFDYMD0P6Qxwxs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKe9Ni%2FdJMcacpYCO2%2Fo4G2TRUQFDYMD0P6Qxwxs0%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;354&quot; height=&quot;1087&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;1612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;각 VirtQueue 안에는 vring이 하나씩 있는데, 이 큐 안의 데이터를 관리하는 데 쓰인다. 이 점은 VirtQueue 구조체의 정의에서도 확인할 수 있다. VirtQueue 구조체의 정의는 hw/virtio/virtio.c에 있고, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530879757&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct VirtQueue
{
    VRing vring;
    VirtQueueElement *used_elems;

    /* Next head to pop */
    uint16_t last_avail_idx;
    bool last_avail_wrap_counter;

    /* Last avail_idx read from VQ. */
    uint16_t shadow_avail_idx;
    bool shadow_avail_wrap_counter;

    uint16_t used_idx;
    bool used_wrap_counter;

    /* Last used index value we have signalled on */
    uint16_t signalled_used;

    /* Last used index value we have signalled on */
    bool signalled_used_valid;

    /* Notification enabled? */
    bool notification;

    uint16_t queue_index;

    unsigned int inuse;

    uint16_t vector;
    VirtIOHandleOutput handle_output;
    VirtIODevice *vdev;
    EventNotifier guest_notifier;
    EventNotifier host_notifier;
    bool host_notifier_enabled;
    QLIST_ENTRY(VirtQueue) node;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;보다시피, VirtQueue의 첫 번째 멤버가 바로 VRing vring이다. VRing 구조체의 정의는 hw/virtio/virtio.c에 있고, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530897067&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef struct VRing
{
    unsigned int num;
    unsigned int num_default;
    unsigned int align;
    hwaddr desc;
    hwaddr avail;
    hwaddr used;
    VRingMemoryRegionCaches *caches;
} VRing;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중점적으로 봐야 할 건 struct VirtQueue의 멤버 VirtIOHandleOutput handle_output이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이게 대응하는 실제 인자가 virtio_blk_handle_output이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;즉 handle_output이 가리키는 게 virtio_blk_handle_output 함수다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 일단 복선만 깔아두고, 나중에 다른 글에서 이 virtio_blk_handle_output 함수를 다시 보게 된다. 지금은 데이터 쓰기를 처리하는 데 쓰인다는 정도만 알고 있으면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781530921622&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; conf-&amp;gt;num_queues; i++) {
    virtio_add_queue(vdev, conf-&amp;gt;queue_size, virtio_blk_handle_output);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtIODevice, VirtQueue, VRing 사이의 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8qM9L/dJMcacDwBiF/QkLFG76hl4gU46cLUF1DMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8qM9L/dJMcacDwBiF/QkLFG76hl4gU46cLUF1DMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8qM9L/dJMcacDwBiF/QkLFG76hl4gU46cLUF1DMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8qM9L%2FdJMcacDwBiF%2FQkLFG76hl4gU46cLUF1DMK%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;784&quot; height=&quot;543&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) QEMU 시작 과정: -drive 파싱과 drive_new&lt;/h3&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;qemu-system-x86_64는 QEMU를 기반으로 한 명령줄 도구로, x86 아키텍처의 컴퓨터 시스템을 시뮬레이션하는 데 쓰인다. 아래는 자주 쓰이는 qemu-system-x86_64 명령과 그 사용법이다.&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;1. 가상머신 하나 시작하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781530976839&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qemu-system-x86_64 -m 2048 -hda /path/to/disk_image.qcow2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;-m 파라미터는 가상머신의 메모리 크기를 지정하고, -hda 파라미터는 디스크 이미지 파일의 경로를 지정한다.&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;2. VNC로 가상머신에 원격 접속하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781530997650&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qemu-system-x86_64 -m 2048 -hda /path/to/disk_image.qcow2 -vnc :1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;-vnc 파라미터는 VNC 프로토콜로 가상머신에 접속하도록 지정하고, 접속 포트는 5901로 지정한다.&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;3. KVM 가속 활성화&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781531016671&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qemu-system-x86_64 -enable-kvm -m 2048 -hda /path/to/disk_image.qcow2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;-enable-kvm 파라미터는 KVM 하드웨어 가속을 활성화하도록 지정한다.&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;4. 가상머신의 네트워크 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781531034424&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qemu-system-x86_64 -m 2048 -hda /path/to/disk_image.qcow2 -net nic -net user,hostfwd=tcp::2222-:22&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;-net 파라미터는 가상머신의 네트워크 설정을 지정하고, -nic 파라미터는 가상머신이 사용할 네트워크 카드 타입을 지정한다. -net user 파라미터는 가상머신이 유저 모드 네트워크를 사용하도록 지정하고, hostfwd 파라미터는 호스트의 2222 포트를 가상머신의 22 포트로 매핑한다.&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;5. ISO 이미지 파일 로드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781531059227&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qemu-system-x86_64 -m 2048 -cdrom /path/to/iso_image.iso -boot d&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;-cdrom 파라미터는 ISO 이미지 파일을 로드하도록 지정하고, -boot 파라미터는 광학 드라이브로 부팅하도록 지정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n1J2u/dJMcadoMmNc/K7lGkaRy6BKxueCdqKscgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n1J2u/dJMcadoMmNc/K7lGkaRy6BKxueCdqKscgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n1J2u/dJMcadoMmNc/K7lGkaRy6BKxueCdqKscgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn1J2u%2FdJMcadoMmNc%2FK7lGkaRy6BKxueCdqKscgk%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;784&quot; height=&quot;146&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU는 다양한 블록 장치 시뮬레이션을 제공하는데, IDE 장치, SCSI 장치, 플로피, USB, virtio 디스크 등이 포함된다. QEMU 명령줄 도구에서, 게스트의 스토리지를 설정하는 데 주로 아래와 같은 파라미터들이 쓰인다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) -hda &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 이미지 파일을 게스트의 첫 번째 IDE 장치(번호 0)로 쓴다. 게스트 안에서는 /dev/hda 장치(게스트가 PIIX_IDE 드라이버를 쓸 때)나 /dev/sda 장치(게스트가 ata_piix 드라이버를 쓸 때)로 나타난다. 만약 -hda나 -hdb 같은 파라미터를 지정하지 않으면, 기본값은 -hda 파라미터다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에, 호스트의 하드디스크(예: /dev/sda)를 -hda의 file 파라미터로 써서, 전체 디스크를 게스트의 첫 번째 IDE 장치로 시뮬레이션할 수도 있다.&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;(2) -hdb &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file을 게스트의 두 번째 IDE 장치(번호 1)로 쓴다. 게스트 안에서는 /dev/hdb나 /dev/sdb 장치로 나타난다.&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;(3) -hdc &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file을 게스트의 세 번째 IDE 장치(번호 2)로 쓴다. 게스트 안에서는 /dev/hdc나 /dev/sdc 장치로 나타난다.&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;(4) -hdd &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file을 게스트의 네 번째 IDE 장치(번호 3)로 쓴다. 게스트 안에서는 /dev/hdd나 /dev/sdd 장치로 나타난다.&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;(5) -fda &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file을 게스트의 첫 번째 플로피(floppy) 장치(번호 0)로 쓴다. 게스트 안에서는 /dev/fd0 장치로 나타난다. 호스트의 플로피 드라이브(/dev/fd0)를 -fda의 file로 써도 된다.&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;(6) -fdb &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file을 게스트의 두 번째 플로피(floppy) 장치(번호 1)로 쓴다. 게스트 안에서는 /dev/fd1 장치로 나타난다.&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;(7) -cdrom &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file을 게스트의 CD-ROM으로 쓴다. 게스트 안에서는 보통 /dev/cdrom 장치로 나타난다. 호스트의 광학 드라이브(/dev/cdrom)를 -cdrom의 file 파라미터로 써도 된다. 물리 광학 드라이브의 디스크나 디스크 안의 ISO 이미지 파일로 게스트 운영체제를 설치할 때는 보통 &quot;-cdrom&quot; 파라미터를 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-cdrom 파라미터는 -hdc 파라미터와 동시에 쓸 수 없는데, &quot;-cdrom&quot;이 바로 게스트의 세 번째 IDE 장치이기 때문이다.&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;(8) -mtdblock &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 파일을 게스트가 자체적으로 갖춘 Flash 저장 장치(보통 말하는 플래시)로 쓴다.&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;(9) -sd &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 파일을 게스트의 SD 카드(Secure Digital Card)로 쓴다.&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;(10) -pflash &amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 파일을 게스트의 병렬 Flash 저장 장치(Parallel Flash Memory)로 쓴다.&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;harr; 게스트 장치 매핑 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY9lA9/dJMcaip9nqP/eQlM4ukzkUjzXPJPnPHR71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY9lA9/dJMcaip9nqP/eQlM4ukzkUjzXPJPnPHR71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY9lA9/dJMcaip9nqP/eQlM4ukzkUjzXPJPnPHR71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY9lA9%2FdJMcaip9nqP%2FeQlM4ukzkUjzXPJPnPHR71%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;443&quot; height=&quot;460&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;-drive 파라미터로 스토리지 드라이버 자세히 설정하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 설명한 스토리지의 기본 설정 옵션 외에, QEMU는 &quot;-driver&quot; 파라미터를 하나 더 제공하는데, 이걸로 스토리지 드라이버 하나를 자세히 정의할 수 있다. 이 파라미터의 구체적인 형식은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531198783&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-drive option [, option [, option [,...]]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;게스트에 새로운 드라이버 하나를 정의해주는 거고, 다음과 같은 옵션(option)들이 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) file=&amp;lt;file&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;file 파일을 이미지 파일로 써서, 게스트의 드라이버에 로드한다.&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;(2) if=&amp;lt;interface&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이버의 인터페이스 타입을 지정한다. 사용 가능한 타입에는 ide, scsi, sd, mtd, floppy, pflash, virtio 등이 있다.&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;(3) bus=&amp;lt;bus&amp;gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) index=&amp;lt;index&amp;gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) media=&amp;lt;media&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이버에서 미디어의 타입을 설정하는데, 선택 가능한 값은 &quot;disk&quot; 또는 &quot;cdrom&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;&lt;b&gt;(6) snapshot=&amp;lt;snapshot&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;-snapshot&quot; 옵션을 활성화할지 설정하는데, 선택 가능한 값은 &quot;on&quot; 또는 &quot;off&quot;다. snapshot이 활성화되면, QEMU는 디스크 데이터의 변경을 이미지 파일에 다시 쓰지 않고, 임시 파일에 쓴다.&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;(7) cache=&amp;lt;cache&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트가 블록 장치 데이터에 접근할 때의 cache 상황을 설정하는데, &quot;none&quot;(또는 &quot;off&quot;), &quot;writeback&quot;, &quot;writethrough&quot; 등으로 설정할 수 있다. 기본값은 &quot;writethrough&quot;, 즉 &quot;직접 쓰기 모드(write-through 모드)&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;&quot;writethrough&quot;(즉 직접 쓰기 모드)는 write를 호출해서 데이터를 쓸 때, 데이터를 디스크 캐시(disk cache)와 백엔드 블록 장치(block device)에 동시에 쓰는 거다. 장점은 작업이 간단하다는 거고, 단점은 데이터 쓰기 속도가 비교적 느리다는 거다.&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;writeback&quot;(즉 라이트백 모드)는 write를 호출해서 데이터를 쓸 때, 데이터를 디스크 캐시에 쓰고 바로 리턴하는 거고, 캐시에서 밀려나는 데이터가 있을 때만 변경된 데이터를 백엔드 블록 장치에 쓴다. 장점은 쓰기 속도가 빠르다는 거고, 단점은 데이터 업데이트 후 백엔드 스토리지에 쓰기 전에 시스템이 다운되면 데이터를 복구할 방법이 없다는 거다.&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;writethrough&quot;와 &quot;writeback&quot;은 데이터에 접근할 때 모두 캐시를 최대한 활용한다. 만약 &quot;cache=none&quot;으로 설정했다면, 즉 캐시를 끄는 방식이면, QEMU는 이미지 파일을 열 때 open 시스템 콜을 호출하면서 &quot;O_DIRECT&quot; 플래그를 쓰기 때문에, 데이터를 읽고 쓸 때 캐시를 우회해서 블록 장치에서 직접 읽고 쓴다. 몇몇 블록 장치 파일(qcow2 형식 파일 같은 거)은 &quot;writethrough&quot; 모드에서 성능이 매우 나쁘다, 만약 이때 성능이 정확성보다 더 요구된다면, &quot;writeback&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;캐시 모드 3가지 비교&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;45&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1tcKd/dJMcag6VHWr/RHAYyYXsGvpGkek81ARnk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1tcKd/dJMcag6VHWr/RHAYyYXsGvpGkek81ARnk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1tcKd/dJMcag6VHWr/RHAYyYXsGvpGkek81ARnk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1tcKd%2FdJMcag6VHWr%2FRHAYyYXsGvpGkek81ARnk0%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;784&quot; height=&quot;45&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;45&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(8) aio=&amp;lt;aio&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 IO(Asynchronous IO) 방식을 선택하는데, &quot;threads&quot;와 &quot;native&quot; 두 가지 값을 고를 수 있다. 기본값은 &quot;threads&quot;인데, 즉 스레드 풀로 비동기 IO를 처리하게 하는 거다. 반면 &quot;native&quot;는 &quot;cache=none&quot;인 경우에만 쓰이는데, 바로 Linux 네이티브 AIO를 쓰는 거다.&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;(9) format=&amp;lt;format&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 디스크 포맷을 지정한다. 기본적으로 QEMU가 자동으로 디스크 포맷을 감지한다.&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;(10) serial=&amp;lt;serial&amp;gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(11) addr=&amp;lt;addr&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드라이버 컨트롤러에 할당되는 PCI 주소다. 이 옵션은 virtio 인터페이스를 쓸 때만 적용된다.&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;(12) id=&amp;lt;name&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 드라이버의 ID를 설정한다. 이 ID는 QEMU monitor에서 &quot;info block&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;&lt;b&gt;(13) readonly=on|off&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;QEMU 시작 과정의 블록 장치 가상화 - 함수 호출 흐름&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 &quot;스토리지의 기본 설정 옵션&quot;과 &quot;스토리지 드라이버를 자세히 설정하는 -drive 파라미터&quot;를 확인했는데, 목적은 QEMU 시작 과정의 블록 장치 가상화를 더 잘 분석하기 위해서다.&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;하드디스크의 가상화에 대해, QEMU의 시작 파라미터에서 관련된 (일반적으로 있을 수 있는) 건 다음 두 줄이다&lt;/p&gt;
&lt;pre id=&quot;code_1781531340829&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-drive file=/var/lib/nova/instances/1f8e6f7e-5a70-4780-89c1-464dc0e7f308/disk,if=none,id=drive-virtio-disk0,
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,bootindex=1&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781531359562&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-drive file=/var/lib/nova/instances/1f8e6f7e-5a70-4780-89c1-464dc0e7f308/disk,if=none,id=drive-virtio-disk0,format=qcow2,cache=none

-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,bootindex=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위 코드의 첫 번째 줄은 호스트 하드디스크 위에 있는 파일 하나를 지정했는데, 파일의 포맷은 qcow2다. 호스트의 파일 하나에 대해, QEMU가 게스트의 하드디스크 하나로 시뮬레이션할 수 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-drive와 -device 두 줄이 하는 일&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc0vmU/dJMcag6VHWw/HLh6lg2bbMONjD4cE34Yu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc0vmU/dJMcag6VHWw/HLh6lg2bbMONjD4cE34Yu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc0vmU/dJMcag6VHWw/HLh6lg2bbMONjD4cE34Yu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc0vmU%2FdJMcag6VHWw%2FHLh6lg2bbMONjD4cE34Yu0%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;344&quot; height=&quot;355&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-pm-slice=&quot;1 1 []&quot; data-ke-style=&quot;style2&quot;&gt;스토리지 드라이버를 자세히 설정하는 -drive 파라미터&lt;br /&gt;지난 회차에서 설명한 스토리지의 기본 설정 옵션 외에, QEMU는 &quot;-driver&quot; 파라미터를 하나 더 제공하는데, 이걸로 스토리지 드라이버 하나를 자세히 정의할 수 있다. 이 파라미터의 구체적인 형식은 아래와 같다.&lt;br /&gt;
&lt;pre id=&quot;code_1781531419797&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-drive option [, option [, option [,...]]]​&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게스트에 새로운 드라이버 하나를 정의해주는 거고, 다음과 같은 옵션(option)들이 있다&lt;/p&gt;
(1) file=&amp;lt;file&amp;gt;&lt;br /&gt;file 파일을 이미지 파일로 써서, 게스트의 드라이버에 로드한다.&lt;br /&gt;(2) if=&amp;lt;interface&amp;gt;&lt;br /&gt;드라이버의 인터페이스 타입을 지정한다. 사용 가능한 타입에는 ide, scsi, sd, mtd, floppy, pflash, virtio 등이 있다.&lt;br /&gt;(9) format=&amp;lt;format&amp;gt;&lt;br /&gt;사용하는 디스크 포맷을 지정한다. 기본적으로 QEMU가 자동으로 디스크 포맷을 감지한다.&lt;/blockquote&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그리고 위 코드의 두 번째 줄에서는 사용하는 드라이버가 virtio-blk 드라이버라고 명시했다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU 시작의 main 함수 안에서 (새 버전(8.1.3/8.1.4)에서는 이미 qemu_create_early_backends()로 옮겨졌고, 이 함수는 qemu_init 함수에서 호출된다), 블록 장치 초기화는 configure_blockdev 함수를 통해 시작된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531464752&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void qemu_create_early_backends(void)
    if (dpy.has_gl &amp;amp;&amp;amp; dpy.gl != DISPLAYGL_MODE_OFF &amp;amp;&amp;amp; display_opengl == 0) {

    object_option_foreach_add(object_create_early);

    /* spice needs the timers to be initialized by this point */
    /* spice must initialize before audio as it changes the default audiodev */
    /* spice must initialize before chardevs (for spicevmc and spiceport) */
    qemu_spice.init();

    qemu_opts_foreach(qemu_find_opts(&quot;chardev&quot;),
                      chardev_init_func, NULL, &amp;amp;error_fatal);

#ifdef CONFIG_VIRTFS
    qemu_opts_foreach(qemu_find_opts(&quot;fsdev&quot;),
                      fsdev_init_func, NULL, &amp;amp;error_fatal);
#endif

    /*
     * Note: we need to create audio and block backends before
     * setting machine properties, so they can be referred to.
     */
    configure_blockdev(&amp;amp;bdo_queue, machine_class, snapshot);
    if (!audio_init_audiodevs()) {
        exit(1);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;configure_blockdev 함수도 softmmu/vl.c에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531486007&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void configure_blockdev(BlockdevOptionsQueue *bdo_queue,
                               MachineClass *machine_class, int snapshot)
{
    /*
     * If the currently selected machine wishes to override the
     * units-per-bus property of its default HBA interface type, do so
     * now.
     */
    if (machine_class-&amp;gt;units_per_default_bus) {
        override_max_devs(machine_class-&amp;gt;block_default_type,
                          machine_class-&amp;gt;units_per_default_bus);
    }

    /* open the virtual block devices */
    while (!QSIMPLEQ_EMPTY(bdo_queue)) {
        BlockdevOptionsQueueEntry *bdo = QSIMPLEQ_FIRST(bdo_queue);

        QSIMPLEQ_REMOVE_HEAD(bdo_queue, entry);
        loc_push_restore(&amp;amp;bdo-&amp;gt;loc);
        qmp_blockdev_add(bdo-&amp;gt;bdo, &amp;amp;error_fatal);
        loc_pop(&amp;amp;bdo-&amp;gt;loc);
        qapi_free_BlockdevOptions(bdo-&amp;gt;bdo);
        g_free(bdo);
    }
    if (snapshot) {
        qemu_opts_foreach(qemu_find_opts(&quot;drive&quot;), drive_enable_snapshot,
                          NULL, NULL);
    }
    if (qemu_opts_foreach(qemu_find_opts(&quot;drive&quot;), drive_init_func,
                          &amp;amp;machine_class-&amp;gt;block_default_type, &amp;amp;error_fatal)) {
        /* We printed help */
        exit(0);
    }

    default_drive(default_cdrom, snapshot, machine_class-&amp;gt;block_default_type, 2,
                  CDROM_OPTS);
    default_drive(default_floppy, snapshot, IF_FLOPPY, 0, FD_OPTS);
    default_drive(default_sdcard, snapshot, IF_SD, 0, SD_OPTS);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;configure_blockdev 함수 안에서, -drive 이 파라미터에 대한 파싱을 볼 수 있고, 또 이 장치를 초기화하기 위해 drive_init_func 함수를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531503986&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (qemu_opts_foreach(qemu_find_opts(&quot;drive&quot;), drive_init_func,
                      &amp;amp;machine_class-&amp;gt;block_default_type, &amp;amp;error_fatal)) {
    /* We printed help */
    exit(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;drive_init_func 함수는 softmmu/vl.c에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531517215&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int drive_init_func(void *opaque, QemuOpts *opts, Error **errp)
{
    BlockInterfaceType *block_default_type = opaque;

    return drive_new(opts, *block_default_type, errp) == NULL;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;drive_init_func 함수 안에서, drive_new 함수를 호출해서 장치 하나를 만든다. drive_new 함수는 blockdev.c에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531532177&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DriveInfo *drive_new(QemuOpts *all_opts, BlockInterfaceType block_default_type,
                     Error **errp)
{
    const char *value;
    BlockBackend *blk;
    DriveInfo *dinfo = NULL;
    QDict *bs_opts;
    QemuOpts *legacy_opts;
    DriveMediaType media = MEDIA_DISK;
    BlockInterfaceType type;
    int max_devs, bus_id, unit_id, index;
    const char *werror, *rerror;
    bool read_only = false;
    bool copy_on_read;
    const char *filename;
    int i;

    GLOBAL_STATE_CODE();

    /* Change legacy command line options into QMP ones */
    static const struct {
        const char *from;
        const char *to;
    } opt_renames[] = {
        { &quot;iops&quot;,           &quot;throttling.iops-total&quot; },
        { &quot;iops_rd&quot;,        &quot;throttling.iops-read&quot; },
        { &quot;iops_wr&quot;,        &quot;throttling.iops-write&quot; },

        { &quot;bps&quot;,            &quot;throttling.bps-total&quot; },
        { &quot;bps_rd&quot;,         &quot;throttling.bps-read&quot; },
        { &quot;bps_wr&quot;,         &quot;throttling.bps-write&quot; },

        { &quot;iops_max&quot;,       &quot;throttling.iops-total-max&quot; },
        { &quot;iops_rd_max&quot;,    &quot;throttling.iops-read-max&quot; },
        { &quot;iops_wr_max&quot;,    &quot;throttling.iops-write-max&quot; },

        { &quot;bps_max&quot;,        &quot;throttling.bps-total-max&quot; },
        { &quot;bps_rd_max&quot;,     &quot;throttling.bps-read-max&quot; },
        { &quot;bps_wr_max&quot;,     &quot;throttling.bps-write-max&quot; },

        { &quot;iops_size&quot;,      &quot;throttling.iops-size&quot; },

        { &quot;group&quot;,          &quot;throttling.group&quot; },

        { &quot;readonly&quot;,       BDRV_OPT_READ_ONLY },
    };

    for (i = 0; i &amp;lt; ARRAY_SIZE(opt_renames); i++) {
        if (!qemu_opt_rename(all_opts, opt_renames[i].from,
                             opt_renames[i].to, errp)) {
            return NULL;
        }
    }

    value = qemu_opt_get(all_opts, &quot;cache&quot;);
    if (value) {
        int flags = 0;
        bool writethrough;

        if (bdrv_parse_cache_mode(value, &amp;amp;flags, &amp;amp;writethrough) != 0) {
            error_setg(errp, &quot;invalid cache option&quot;);
            return NULL;
        }

        /* Specific options take precedence */
        if (!qemu_opt_get(all_opts, BDRV_OPT_CACHE_WB)) {
            qemu_opt_set_bool(all_opts, BDRV_OPT_CACHE_WB,
                              !writethrough, &amp;amp;error_abort);
        }
        if (!qemu_opt_get(all_opts, BDRV_OPT_CACHE_DIRECT)) {
            qemu_opt_set_bool(all_opts, BDRV_OPT_CACHE_DIRECT,
                              !!(flags &amp;amp; BDRV_O_NOCACHE), &amp;amp;error_abort);
        }
        if (!qemu_opt_get(all_opts, BDRV_OPT_CACHE_NO_FLUSH)) {
            qemu_opt_set_bool(all_opts, BDRV_OPT_CACHE_NO_FLUSH,
                              !!(flags &amp;amp; BDRV_O_NO_FLUSH), &amp;amp;error_abort);
        }
        qemu_opt_unset(all_opts, &quot;cache&quot;);
    }

    /* Get a QDict for processing the options */
    bs_opts = qdict_new();
    qemu_opts_to_qdict(all_opts, bs_opts);

    legacy_opts = qemu_opts_create(&amp;amp;qemu_legacy_drive_opts, NULL, 0,
                                   &amp;amp;error_abort);
    if (!qemu_opts_absorb_qdict(legacy_opts, bs_opts, errp)) {
        goto fail;
    }

    /* Media type */
    value = qemu_opt_get(legacy_opts, &quot;media&quot;);
    if (value) {
        if (!strcmp(value, &quot;disk&quot;)) {
            media = MEDIA_DISK;
        } else if (!strcmp(value, &quot;cdrom&quot;)) {
            media = MEDIA_CDROM;
            read_only = true;
        } else {
            error_setg(errp, &quot;'%s' invalid media&quot;, value);
            goto fail;
        }
    }

    /* copy-on-read is disabled with a warning for read-only devices */
    read_only |= qemu_opt_get_bool(legacy_opts, BDRV_OPT_READ_ONLY, false);
    copy_on_read = qemu_opt_get_bool(legacy_opts, &quot;copy-on-read&quot;, false);

    if (read_only &amp;amp;&amp;amp; copy_on_read) {
        warn_report(&quot;disabling copy-on-read on read-only drive&quot;);
        copy_on_read = false;
    }

    qdict_put_str(bs_opts, BDRV_OPT_READ_ONLY, read_only ? &quot;on&quot; : &quot;off&quot;);
    qdict_put_str(bs_opts, &quot;copy-on-read&quot;, copy_on_read ? &quot;on&quot; : &quot;off&quot;);

    /* Controller type */
    value = qemu_opt_get(legacy_opts, &quot;if&quot;);
    if (value) {
        for (type = 0;
             type &amp;lt; IF_COUNT &amp;amp;&amp;amp; strcmp(value, if_name[type]);
             type++) {
        }
        if (type == IF_COUNT) {
            error_setg(errp, &quot;unsupported bus type '%s'&quot;, value);
            goto fail;
        }
    } else {
        type = block_default_type;
    }

    /* Device address specified by bus/unit or index.
     * If none was specified, try to find the first free one. */
    bus_id  = qemu_opt_get_number(legacy_opts, &quot;bus&quot;, 0);
    unit_id = qemu_opt_get_number(legacy_opts, &quot;unit&quot;, -1);
    index   = qemu_opt_get_number(legacy_opts, &quot;index&quot;, -1);

    max_devs = if_max_devs[type];

    if (index != -1) {
        if (bus_id != 0 || unit_id != -1) {
            error_setg(errp, &quot;index cannot be used with bus and unit&quot;);
            goto fail;
        }
        bus_id = drive_index_to_bus_id(type, index);
        unit_id = drive_index_to_unit_id(type, index);
    }

    if (unit_id == -1) {
       unit_id = 0;
       while (drive_get(type, bus_id, unit_id) != NULL) {
           unit_id++;
           if (max_devs &amp;amp;&amp;amp; unit_id &amp;gt;= max_devs) {
               unit_id -= max_devs;
               bus_id++;
           }
       }
    }

    if (max_devs &amp;amp;&amp;amp; unit_id &amp;gt;= max_devs) {
        error_setg(errp, &quot;unit %d too big (max is %d)&quot;, unit_id, max_devs - 1);
        goto fail;
    }

    if (drive_get(type, bus_id, unit_id) != NULL) {
        error_setg(errp, &quot;drive with bus=%d, unit=%d (index=%d) exists&quot;,
                   bus_id, unit_id, index);
        goto fail;
    }

    /* no id supplied -&amp;gt; create one */
    if (qemu_opts_id(all_opts) == NULL) {
        char *new_id;
        const char *mediastr = &quot;&quot;;
        if (type == IF_IDE || type == IF_SCSI) {
            mediastr = (media == MEDIA_CDROM) ? &quot;-cd&quot; : &quot;-hd&quot;;
        }
        if (max_devs) {
            new_id = g_strdup_printf(&quot;%s%i%s%i&quot;, if_name[type], bus_id,
                                     mediastr, unit_id);
        } else {
            new_id = g_strdup_printf(&quot;%s%s%i&quot;, if_name[type],
                                     mediastr, unit_id);
        }
        qdict_put_str(bs_opts, &quot;id&quot;, new_id);
        g_free(new_id);
    }

    /* Add virtio block device */
    if (type == IF_VIRTIO) {
        QemuOpts *devopts;
        devopts = qemu_opts_create(qemu_find_opts(&quot;device&quot;), NULL, 0,
                                   &amp;amp;error_abort);
        qemu_opt_set(devopts, &quot;driver&quot;, &quot;virtio-blk&quot;, &amp;amp;error_abort);
        qemu_opt_set(devopts, &quot;drive&quot;, qdict_get_str(bs_opts, &quot;id&quot;),
                     &amp;amp;error_abort);
    }

    filename = qemu_opt_get(legacy_opts, &quot;file&quot;);

    /* Check werror/rerror compatibility with if=... */
    werror = qemu_opt_get(legacy_opts, &quot;werror&quot;);
    if (werror != NULL) {
        if (type != IF_IDE &amp;amp;&amp;amp; type != IF_SCSI &amp;amp;&amp;amp; type != IF_VIRTIO &amp;amp;&amp;amp;
            type != IF_NONE) {
            error_setg(errp, &quot;werror is not supported by this bus type&quot;);
            goto fail;
        }
        qdict_put_str(bs_opts, &quot;werror&quot;, werror);
    }

    rerror = qemu_opt_get(legacy_opts, &quot;rerror&quot;);
    if (rerror != NULL) {
        if (type != IF_IDE &amp;amp;&amp;amp; type != IF_VIRTIO &amp;amp;&amp;amp; type != IF_SCSI &amp;amp;&amp;amp;
            type != IF_NONE) {
            error_setg(errp, &quot;rerror is not supported by this bus type&quot;);
            goto fail;
        }
        qdict_put_str(bs_opts, &quot;rerror&quot;, rerror);
    }

    /* Actual block device init: Functionality shared with blockdev-add */
    blk = blockdev_init(filename, bs_opts, errp);
    bs_opts = NULL;
    if (!blk) {
        goto fail;
    }

    /* Create legacy DriveInfo */
    dinfo = g_malloc0(sizeof(*dinfo));
    dinfo-&amp;gt;opts = all_opts;

    dinfo-&amp;gt;type = type;
    dinfo-&amp;gt;bus = bus_id;
    dinfo-&amp;gt;unit = unit_id;

    blk_set_legacy_dinfo(blk, dinfo);

    switch (type) {
    case IF_IDE:
    case IF_SCSI:
    case IF_XEN:
    case IF_NONE:
        dinfo-&amp;gt;media_cd = media == MEDIA_CDROM;
        break;
    default:
        break;
    }

fail:
    qemu_opts_del(legacy_opts);
    qobject_unref(bs_opts);
    return dinfo;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;시작부터 drive_new까지 함수 호출 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uAcPN/dJMcafAdxNx/uhk8yWMUYMckTKI1Zo7cZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uAcPN/dJMcafAdxNx/uhk8yWMUYMckTKI1Zo7cZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uAcPN/dJMcafAdxNx/uhk8yWMUYMckTKI1Zo7cZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuAcPN%2FdJMcafAdxNx%2Fuhk8yWMUYMckTKI1Zo7cZK%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;205&quot; height=&quot;666&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;drive_new 함수 자세히 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 다룬 QEMU 시작 과정에서 호출되는 함수 흐름은 다음과 같다&lt;/p&gt;
&lt;pre id=&quot;code_1781531590418&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;main 함수
   ---&amp;gt; qemu_init 함수
       ---&amp;gt; qemu_create_early_backends() 함수
           ---&amp;gt; configure_blockdev 함수
               ---&amp;gt; drive_init_func 함수
                   ---&amp;gt; driver_new 함수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmqhAd/dJMcaa6L2Qz/36lbETxZalpZ9xUNhsjET1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmqhAd/dJMcaa6L2Qz/36lbETxZalpZ9xUNhsjET1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmqhAd/dJMcaa6L2Qz/36lbETxZalpZ9xUNhsjET1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmqhAd%2FdJMcaa6L2Qz%2F36lbETxZalpZ9xUNhsjET1%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;784&quot; height=&quot;526&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781531627595&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void configure_blockdev(BlockdevOptionsQueue *bdo_queue,
                               MachineClass *machine_class, int snapshot)
{
    /*
     * If the currently selected machine wishes to override the
     * units-per-bus property of its default HBA interface type, do so
     * now.
     */
    if (machine_class-&amp;gt;units_per_default_bus) {
        override_max_devs(machine_class-&amp;gt;block_default_type,
                          machine_class-&amp;gt;units_per_default_bus);
    }

    /* open the virtual block devices */
    while (!QSIMPLEQ_EMPTY(bdo_queue)) {
        BlockdevOptionsQueueEntry *bdo = QSIMPLEQ_FIRST(bdo_queue);

        QSIMPLEQ_REMOVE_HEAD(bdo_queue, entry);
        loc_push_restore(&amp;amp;bdo-&amp;gt;loc);
        qmp_blockdev_add(bdo-&amp;gt;bdo, &amp;amp;error_fatal);
        loc_pop(&amp;amp;bdo-&amp;gt;loc);
        qapi_free_BlockdevOptions(bdo-&amp;gt;bdo);
        g_free(bdo);
    }
    if (snapshot) {
        qemu_opts_foreach(qemu_find_opts(&quot;drive&quot;), drive_enable_snapshot,
                          NULL, NULL);
    }
    if (qemu_opts_foreach(qemu_find_opts(&quot;drive&quot;), drive_init_func,
                          &amp;amp;machine_class-&amp;gt;block_default_type, &amp;amp;error_fatal)) {
        /* We printed help */
        exit(0);
    }

    default_drive(default_cdrom, snapshot, machine_class-&amp;gt;block_default_type, 2,
                  CDROM_OPTS);
    default_drive(default_floppy, snapshot, IF_FLOPPY, 0, FD_OPTS);
    default_drive(default_sdcard, snapshot, IF_SD, 0, SD_OPTS);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;drive_new 함수는 장치 하나를 만드는 데 쓰인다. 그 안에서 QEMU의 시작 파라미터를 파싱하게 된다. virtio에 대해 말하자면, device 파라미터를 파싱해서 driver를 &quot;virtio-blk&quot;로 설정한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781531653711&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Add virtio block device */
if (type == IF_VIRTIO) {
    QemuOpts *devopts;
    devopts = qemu_opts_create(qemu_find_opts(&quot;device&quot;),
                               NULL, 0,
                               &amp;amp;error_abort);
    qemu_opt_set(devopts, &quot;driver&quot;, &quot;virtio-blk&quot;, &amp;amp;error_abort);
    qemu_opt_set(devopts, &quot;drive&quot;, qdict_get_str(bs_opts, &quot;id&quot;),
                 &amp;amp;error_abort);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;시작 명령줄 중 다음 줄에 대응한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531669842&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,bootindex=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;다음으로, drive_new 함수는 file 파라미터를 파싱하는데, 호스트의 그 파일을 가리키는 거다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531684558&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;filename = qemu_opt_get(legacy_opts, &quot;file&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;시작 명령줄 중 다음 줄에 대응한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531698666&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-drive file=/var/lib/nova/instances/1f8e6f7e-5a70-4780-89c1-464dc0e7f308/disk,if=none,id=drive-virtio-disk0,format=qcow2,cache=none&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;다음으로, drive_new 함수는 blockdev_init 함수를 호출해서, 파라미터에 따라 초기화를 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531714220&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Actual block device init: Functionality shared with blockdev-add */
blk = blockdev_init(filename, bs_opts, errp);
bs_opts = NULL;
if (!blk) {
    goto fail;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로, drive_new 함수는 DriverInfo 구조체 객체를 하나 만들어서, 이 새로 만든 장치를 관리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531731305&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DriveInfo *dinfo = NULL;
&amp;hellip;&amp;hellip;

/* Create legacy DriveInfo */
dinfo = g_malloc0(sizeof(*dinfo));
dinfo-&amp;gt;opts = all_opts;

dinfo-&amp;gt;type = type;
dinfo-&amp;gt;bus = bus_id;
dinfo-&amp;gt;unit = unit_id;

blk_set_legacy_dinfo(blk, dinfo);

switch (type) {
case IF_IDE:
case IF_SCSI:
case IF_XEN:
case IF_NONE:
    dinfo-&amp;gt;media_cd = media == MEDIA_CDROM;
    break;
default:
    break;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;drive_new 함수의 4가지 핵심 작업&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;1528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BNumn/dJMcaaTcDk1/74Qk2QpwbeZCpOS5KUcUe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BNumn/dJMcaaTcDk1/74Qk2QpwbeZCpOS5KUcUe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BNumn/dJMcaaTcDk1/74Qk2QpwbeZCpOS5KUcUe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBNumn%2FdJMcaaTcDk1%2F74Qk2QpwbeZCpOS5KUcUe0%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;342&quot; height=&quot;957&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;1528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;명령줄 파라미터와 drive_new 처리 매핑&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beXSP6/dJMcabYUxXo/ussUdiWK6sbOSL2YLeHJqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beXSP6/dJMcabYUxXo/ussUdiWK6sbOSL2YLeHJqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beXSP6/dJMcabYUxXo/ussUdiWK6sbOSL2YLeHJqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeXSP6%2FdJMcabYUxXo%2FussUdiWK6sbOSL2YLeHJqk%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;569&quot; height=&quot;438&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;전체 흐름 - 명령줄에서 VirtQueue 생성까지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LdwWL/dJMcaftqeRB/fh1eVO6kEyxEKwVgFBGwyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LdwWL/dJMcaftqeRB/fh1eVO6kEyxEKwVgFBGwyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LdwWL/dJMcaftqeRB/fh1eVO6kEyxEKwVgFBGwyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLdwWL%2FdJMcaftqeRB%2Ffh1eVO6kEyxEKwVgFBGwyk%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;784&quot; height=&quot;419&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;핵심 자료구조 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;633&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l5Nyx/dJMcaaTcDlf/EuIX1WRbe6QBkYFHcy8fmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l5Nyx/dJMcaaTcDlf/EuIX1WRbe6QBkYFHcy8fmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l5Nyx/dJMcaaTcDlf/EuIX1WRbe6QBkYFHcy8fmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl5Nyx%2FdJMcaaTcDlf%2FEuIX1WRbe6QBkYFHcy8fmK%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;784&quot; height=&quot;633&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;두 단계의 관점 차이&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yd6oN/dJMcaaTcDlW/NjFjPKky9kbq1m7eEvopT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yd6oN/dJMcaaTcDlW/NjFjPKky9kbq1m7eEvopT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yd6oN/dJMcaaTcDlW/NjFjPKky9kbq1m7eEvopT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYd6oN%2FdJMcaaTcDlW%2FNjFjPKky9kbq1m7eEvopT0%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;784&quot; height=&quot;63&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;핵심 개념 정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2BWXH/dJMcabdrlg6/4RoRa5ewawOIDg0gmihPBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2BWXH/dJMcabdrlg6/4RoRa5ewawOIDg0gmihPBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2BWXH/dJMcabdrlg6/4RoRa5ewawOIDg0gmihPBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2BWXH%2FdJMcabdrlg6%2F4RoRa5ewawOIDg0gmihPBk%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;553&quot; height=&quot;548&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;-drive vs -device: -drive는 호스트 파일을 어떻게 다룰지(file, format, cache)를 정하고, -device는 게스트에 어떤 드라이버로 보일지(virtio-blk-pci 등)를 정한다. 둘은 id로 연결된다.&lt;/li&gt;
&lt;li&gt;두 단계로 나뉜 흐름: QEMU 시작 시점에는 configure_blockdev &amp;rarr; drive_new &amp;rarr; blockdev_init로 호스트 측 블록 백엔드를 준비한다. 그 다음 장치 실체화 시점에 virtio_blk_pci_realize &amp;rarr; virtio_blk_device_realize로 게스트 측 virtio 장치를 만든다.&lt;/li&gt;
&lt;li&gt;num_queues 결정 순서: PCI 단의 virtio_blk_pci_realize가 AUTO면 virtio_pci_optimal_num_queues로 설정하고, 그래도 AUTO면 공통 단의 virtio_blk_device_realize가 1로 설정한다.&lt;/li&gt;
&lt;li&gt;virtio_add_queue 호출은 num_queues만큼: 각 호출이 VirtQueue 슬롯 하나를 차지하고, 그 안의 VRing을 초기화하고, handle_output을 virtio_blk_handle_output 함수로 등록한다.&lt;/li&gt;
&lt;li&gt;handle_output이 진짜 데이터 처리 진입점: 게스트가 큐에 요청을 넣을 때마다 호출될 콜백.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 디스크 이미지 열기 (bdrv_open &amp;rarr; qcow2 / raw)&lt;/h3&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;drive_new부터 시작해서 디스크 파일이 실제로 열리기까지의 호출 체인 전체를 먼저 한눈에 보자. QEMU 명령행의 -drive 옵션이 어떻게 가상 머신 내부의 블록 디바이스로 변환되는지, 그 흐름이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;1438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl5gfk/dJMcacQYBnc/DuRCgQSfWt6bfC1GEi2Qo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl5gfk/dJMcacQYBnc/DuRCgQSfWt6bfC1GEi2Qo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl5gfk/dJMcacQYBnc/DuRCgQSfWt6bfC1GEi2Qo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl5gfk%2FdJMcacQYBnc%2FDuRCgQSfWt6bfC1GEi2Qo1%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;333&quot; height=&quot;1046&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;1438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노란색은 command line, 초록색은 프론트엔드(blockdev 레벨), 파란색은 백엔드(bdrv 레벨), 주황색은 포맷별 드라이버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;drive_new에서 blockdev_init으로&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이전에 drive_new 함수를 대략 분석했는데, 이번에는 drive_new 함수에서 호출하는 blockdev_init 함수에 대한 분석을 중점적으로 다룬다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531938460&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Actual block device init: Functionality shared with blockdev-add */
blk = blockdev_init(filename, bs_opts, errp);
bs_opts = NULL;
if (!blk) {
    goto fail;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;blockdev_init 함수는 blockdev.c 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531955176&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Takes the ownership of bs_opts */
static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,
                                   Error **errp)
{
    const char *buf;
    int bdrv_flags = 0;
    int on_read_error, on_write_error;
    bool account_invalid, account_failed;
    BlockBackend *blk;
    BlockDriverState *bs;
    ThrottleConfig cfg;
    int snapshot = 0;
    Error *error = NULL;
    QemuOpts *opts;
    QDict *interval_dict = NULL;
    QList *interval_list = NULL;
    const char *id;
    BlockdevDetectZeroesOptions detect_zeroes =
        BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
    const char *throttling_group = NULL;

    /* Check common options by copying from bs_opts to opts, all other options
     * stay in bs_opts for processing by bdrv_open(). */
    id = qdict_get_try_str(bs_opts, &quot;id&quot;);
    opts = qemu_opts_create(&amp;amp;qemu_common_drive_opts, id, 1, errp);
    if (!opts) {
        goto err_no_opts;
    }

    if (!qemu_opts_absorb_qdict(opts, bs_opts, errp)) {
        goto early_err;
    }

    if (id) {
        qdict_del(bs_opts, &quot;id&quot;);
    }

    /* extract parameters */
    snapshot = qemu_opt_get_bool(opts, &quot;snapshot&quot;, 0);

    account_invalid = account_get_opt(opts, &quot;stats-account-invalid&quot;);
    account_failed = account_get_opt(opts, &quot;stats-account-failed&quot;);

    writethrough = !qemu_opt_get_bool(opts, BDRV_OPT_CACHE_WB, true);

    id = qemu_opts_id(opts);

    qdict_extract_subqdict(bs_opts, &amp;amp;interval_dict, &quot;stats-intervals.&quot;);
    qdict_array_split(interval_dict, &amp;amp;interval_list);

    if (qdict_size(interval_dict) != 0) {
        error_setg(errp, &quot;Invalid option stats-intervals.%s&quot;,
                   qdict_first(interval_dict)-&amp;gt;key);
        goto early_err;
    }

    extract_common_blockdev_options(opts, &amp;amp;bdrv_flags, &amp;amp;throttling_group, &amp;amp;cfg,
                                    &amp;amp;detect_zeroes, &amp;amp;error);
    if (error) {
        error_propagate(errp, error);
        goto early_err;
    }

    if ((buf = qemu_opt_get(opts, &quot;format&quot;)) != NULL) {
        if (is_help_option(buf)) {
            qemu_printf(&quot;Supported formats:&quot;);
            bdrv_iterate_format(bdrv_format_print, NULL, false);
            qemu_printf(&quot;\nSupported formats (read-only):&quot;);
            bdrv_iterate_format(bdrv_format_print, NULL, true);
            qemu_printf(&quot;\n&quot;);
            goto early_err;
        }

        if (qdict_haskey(bs_opts, &quot;driver&quot;)) {
            error_setg(errp, &quot;Cannot specify both 'driver' and 'format'&quot;);
            goto early_err;
        }
        qdict_put_str(bs_opts, &quot;driver&quot;, buf);
    }

    on_write_error = BLOCKDEV_ON_ERROR_ENOSPC;
    if ((buf = qemu_opt_get(opts, &quot;werror&quot;)) != NULL) {
        on_write_error = parse_block_error_action(buf, 0, &amp;amp;error);
        if (error) {
            error_propagate(errp, error);
            goto early_err;
        }
    }

    on_read_error = BLOCKDEV_ON_ERROR_REPORT;
    if ((buf = qemu_opt_get(opts, &quot;rerror&quot;)) != NULL) {
        on_read_error = parse_block_error_action(buf, 1, &amp;amp;error);
        if (error) {
            error_propagate(errp, error);
            goto early_err;
        }
    }

    if (snapshot) {
        bdrv_flags |= BDRV_O_SNAPSHOT;
    }

    read_only = qemu_opt_get_bool(opts, BDRV_OPT_READ_ONLY, false);

    /* init */
    if ((!file || !*file) &amp;amp;&amp;amp; !qdict_size(bs_opts)) {
        BlockBackendRootState *blk_rs;

        blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
        blk_rs = blk_get_root_state(blk);
        blk_rs-&amp;gt;open_flags    = bdrv_flags | (read_only ? 0 : BDRV_O_RDWR);
        blk_rs-&amp;gt;detect_zeroes = detect_zeroes;

        qobject_unref(bs_opts);
    } else {
        if (file &amp;amp;&amp;amp; !*file) {
            file = NULL;
        }

        /* bdrv_open() defaults to the values in bdrv_flags (for compatibility
         * with other callers) rather than what we want as the real defaults.
         * Apply the defaults here instead. */
        qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_DIRECT, &quot;off&quot;);
        qdict_set_default_str(bs_opts, BDRV_OPT_CACHE_NO_FLUSH, &quot;off&quot;);
        qdict_set_default_str(bs_opts, BDRV_OPT_READ_ONLY,
                              read_only ? &quot;on&quot; : &quot;off&quot;);
        qdict_set_default_str(bs_opts, BDRV_OPT_AUTO_READ_ONLY, &quot;on&quot;);
        assert((bdrv_flags &amp;amp; BDRV_O_CACHE_MASK) == 0);

        if (runstate_check(RUN_STATE_INMIGRATE)) {
            bdrv_flags |= BDRV_O_INACTIVE;
        }

        blk = blk_new_open(file, NULL, bs_opts, bdrv_flags, errp);
        if (!blk) {
            goto err_no_bs_opts;
        }
        bs = blk_bs(blk);

        bs-&amp;gt;detect_zeroes = detect_zeroes;

        block_acct_setup(blk_get_stats(blk), account_invalid, account_failed);

        if (!parse_stats_intervals(blk_get_stats(blk), interval_list, errp)) {
            blk_unref(blk);
            blk = NULL;
            goto err_no_bs_opts;
        }
    }

    /* disk I/O throttling */
    if (throttle_enabled(&amp;amp;cfg)) {
        if (!throttling_group) {
            throttling_group = id;
        }
        blk_io_limits_enable(blk, throttling_group);
        blk_set_io_limits(blk, &amp;amp;cfg);
    }

    blk_set_enable_write_cache(blk, !writethrough);
    blk_set_on_error(blk, on_read_error, on_write_error);

    if (!monitor_add_blk(blk, id, errp)) {
        blk_unref(blk);
        blk = NULL;
        goto err_no_bs_opts;
    }

err_no_bs_opts:
    qemu_opts_del(opts);
    qobject_unref(interval_dict);
    qobject_unref(interval_list);
    return blk;

early_err:
    qemu_opts_del(opts);
    qobject_unref(interval_dict);
    qobject_unref(interval_list);
err_no_opts:
    qobject_unref(bs_opts);
    return NULL;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;blockdev_init 함수 안의 분기 구조를 그림으로 정리해보면 이렇게 된다. file이 비어있는지 아닌지에 따라 두 갈래로 갈린다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;1430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEwSOI/dJMcajihUmc/IRaFJK1xA0CMWKpQGy9MQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEwSOI/dJMcajihUmc/IRaFJK1xA0CMWKpQGy9MQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEwSOI/dJMcajihUmc/IRaFJK1xA0CMWKpQGy9MQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEwSOI%2FdJMcajihUmc%2FIRaFJK1xA0CMWKpQGy9MQk%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;379&quot; height=&quot;1004&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;1430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서, file은 blockdev_init 함수의 입력 파라미터다.&lt;/p&gt;
&lt;pre id=&quot;code_1781531997358&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,
                                   Error **errp)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이에 대응되는 실제 인자는 지난번에 다뤘다.&lt;/p&gt;
&lt;blockquote data-pm-slice=&quot;0 0 []&quot; data-ke-style=&quot;style2&quot;&gt;다음으로, drive_new 함수는 file 파라미터를 파싱하는데, 이것이 바로 호스트 머신 상의 그 파일을 가리킨다. 코드 조각은 아래와 같다&lt;br /&gt;
&lt;pre id=&quot;code_1781570371027&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;filename = qemu_opt_get(legacy_opts, &quot;file&quot;);​&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;시작 명령행에서 대응되는 부분은&lt;br /&gt;-drive file=/var/lib/nova/instances/1f8e6f7e-5a70-4780-89c1-464dc0e7f308/disk,if=none,id=drive-virtio-disk0,format=qcow2,cache=none&lt;br /&gt;다음으로, drive_new 함수는 blockdev_init 함수를 호출해서 파라미터에 따라 초기화를 진행한다. 코드 조각은 아래와 같다
&lt;pre id=&quot;code_1781570432999&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Actual block device init: Functionality shared with blockdev-add */
blk = blockdev_init(filename, bs_opts, errp);
bs_opts = NULL;
if (!blk) {
    goto fail;
}​&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;실제 인자 filename은 당연히 비어 있지 않으니, blk_new_open 함수를 호출해서 호스트 머신 상의 하드 디스크 파일(명령행에 있는 그 /var/lib/nova/instances/1f8e6f7e-5a70-4780-89c1-464dc0e7f308/disk가 돼야 한다)을 연다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blk_new_open 함수는 block/block-backend.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570468079&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * Creates a new BlockBackend, opens a new BlockDriverState, and connects both.
 * By default, the new BlockBackend is in the main AioContext, but if the
 * parameters connect it with any existing node in a different AioContext, it
 * may end up there instead.
 *
 * Just as with bdrv_open(), after having called this function the reference to
 * @options belongs to the block layer (even on failure).
 *
 * Called without holding an AioContext lock.
 *
 * TODO: Remove @filename and @flags; it should be possible to specify a whole
 * BDS tree just by specifying the options QDict (or @reference,
 * alternatively). At the time of adding this function, this is not possible,
 * though, so callers of this function have to be able to specify @filename and
 * @flags.
 */
BlockBackend *blk_new_open(const char *filename, const char *reference,
                           QDict *options, int flags, Error **errp)
{
    BlockBackend *blk;
    BlockDriverState *bs;
    AioContext *ctx;
    uint64_t perm = 0;
    uint64_t shared = BLK_PERM_ALL;

    GLOBAL_STATE_CODE();

    /*
     * blk_new_open() is mainly used in .bdrv_create implementations and the
     * tools where sharing isn't a major concern because the BDS stays private
     * and the file is generally not supposed to be used by a second process,
     * so we just request permission according to the flags.
     *
     * The exceptions are xen_disk and blockdev_init(); in these cases, the
     * caller of blk_new_open() doesn't make use of the permissions, but they
     * shouldn't hurt either. We can still share everything here because the
     * guest devices will add their own blockers if they can't share.
     */
    if ((flags &amp;amp; BDRV_O_NO_IO) == 0) {
        perm |= BLK_PERM_CONSISTENT_READ;
        if (flags &amp;amp; BDRV_O_RDWR) {
            perm |= BLK_PERM_WRITE;
        }
    }
    if (flags &amp;amp; BDRV_O_RESIZE) {
        perm |= BLK_PERM_RESIZE;
    }
    if (flags &amp;amp; BDRV_O_NO_SHARE) {
        shared = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED;
    }

    aio_context_acquire(qemu_get_aio_context());
    bs = bdrv_open(filename, reference, options, flags, errp);
    aio_context_release(qemu_get_aio_context());
    if (!bs) {
        return NULL;
    }

    /* bdrv_open() could have moved bs to a different AioContext */
    ctx = bdrv_get_aio_context(bs);
    blk = blk_new(bdrv_get_aio_context(bs), perm, shared);
    blk-&amp;gt;perm = perm;
    blk-&amp;gt;shared_perm = shared;

    aio_context_acquire(ctx);
    blk_insert_bs(blk, bs, errp);
    bdrv_unref(bs);
    aio_context_release(ctx);

    if (!blk-&amp;gt;root) {
        blk_unref(blk);
        return NULL;
    }

    return blk;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;blk_new_open이 하는 일을 정리하면 이렇게 된다. 핵심은 BlockBackend(blk)와 BlockDriverState(bs)를 만들고 둘을 연결하는 것&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;1054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LQSvM/dJMcahEKeaB/iK2CsJtvxDTUXRjmXBX1Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LQSvM/dJMcahEKeaB/iK2CsJtvxDTUXRjmXBX1Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LQSvM/dJMcahEKeaB/iK2CsJtvxDTUXRjmXBX1Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLQSvM%2FdJMcahEKeaB%2FiK2CsJtvxDTUXRjmXBX1Z0%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;189&quot; height=&quot;722&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;1054&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위에서 이미 언급했듯이, blk_new_open 함수의 역할은 호스트 머신 상의 하드 디스크 파일(즉 명령행 파라미터 file에 대응되는 그 /var/lib/nova/instances/1f8e6f7e-5a70-4780-89c1-464dc0e7f308/disk 파일)을 여는 것이다. 반환 결과는 BlockBackend *blk이고, 이 blk는 함수 안에서 bdrv_open 함수를 통해 얻은 것이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이름만 봐도 알 수 있듯이, 이미 백엔드 디바이스의 범주에 속한다. blk_new_open 함수의 주석에 분명하게 적혀 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;새로운 BlockBackend를 생성하고, 새로운 BlockDriverState를 열고, 그리고 둘을 연결한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 BlockBackend, BlockDriverState, BlockDriver 세 개의 핵심 데이터 구조 관계를 짚어두자. 이게 QEMU 블록 레이어의 기본 그림이다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p02ZK/dJMcabkjGo8/yO2SFHRHNwXmUHGB5c5Pik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p02ZK/dJMcabkjGo8/yO2SFHRHNwXmUHGB5c5Pik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p02ZK/dJMcabkjGo8/yO2SFHRHNwXmUHGB5c5Pik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp02ZK%2FdJMcabkjGo8%2FyO2SFHRHNwXmUHGB5c5Pik%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;784&quot; height=&quot;65&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open 함수는 block.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570541241&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* The caller must always hold the main AioContext lock. */
BlockDriverState *bdrv_open(const char *filename, const char *reference,
                            QDict *options, int flags, Error **errp)
{
    GLOBAL_STATE_CODE();

    return bdrv_open_inherit(filename, reference, options, flags, NULL,
                             NULL, 0, errp);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;bdrv_open에서 bdrv_open_driver로&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 blockdev_init 함수를 분석하기 시작했고, 그 안에서 호출되는 blk_new_open 함수에 대해 다뤘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수의 역할은 호스트 머신 상의 하드 디스크 파일을 여는 것이다. blk_new_open 함수 안에서는 또 bdrv_open 함수를 호출하는데, 이것은 block.c 안에 있고 코드는 아래와 같다&lt;/p&gt;
&lt;pre id=&quot;code_1781570584976&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static BlockBackend *blockdev_init(const char *file, QDict *bs_opts,
                                   Error **errp)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_inherit 함수도 block.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570603884&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * Opens a disk image (raw, qcow2, vmdk, ...)
 *
 * options is a QDict of options to pass to the block drivers, or NULL for an
 * empty set of options. The reference to the QDict belongs to the block layer
 * after the call (even on failure), so if the caller intends to reuse the
 * dictionary, it needs to use qobject_ref() before calling bdrv_open.
 *
 * If *pbs is NULL, a new BDS will be created with a pointer to it stored there.
 * If it is not NULL, the referenced BDS will be reused.
 *
 * The reference parameter may be used to specify an existing block device which
 * should be opened. If specified, neither options nor a filename may be given,
 * nor can an existing BDS be reused (that is, *pbs has to be NULL).
 *
 * The caller must always hold the main AioContext lock.
 */
static BlockDriverState * no_coroutine_fn
bdrv_open_inherit(const char *filename, const char *reference, QDict *options,
                  int flags, BlockDriverState *parent,
                  const BdrvChildClass *child_class, BdrvChildRole child_role,
                  Error **errp)
{
    int ret;
    BlockBackend *file = NULL;
    BlockDriverState *bs;
    BlockDriver *drv = NULL;
    BdrvChild *child;
    const char *drvname;
    const char *backing;
    Error *local_err = NULL;
    QDict *snapshot_options = NULL;
    int snapshot_flags = 0;
    AioContext *ctx = qemu_get_aio_context();

    assert(!child_class || !flags);
    assert(!child_class == !parent);
    GLOBAL_STATE_CODE();
    assert(!qemu_in_coroutine());

    if (reference) {
        bool options_non_empty = options ? qdict_size(options) : false;
        qobject_unref(options);

        if (filename || options_non_empty) {
            error_setg(errp, &quot;Cannot reference an existing block device with &quot;
                       &quot;additional options or a new filename&quot;);
            return NULL;
        }

        bs = bdrv_lookup_bs(reference, reference, errp);
        if (!bs) {
            return NULL;
        }

        bdrv_ref(bs);
        return bs;
    }

    bs = bdrv_new();

    /* NULL means an empty set of options */
    if (options == NULL) {
        options = qdict_new();
    }

    /* json: syntax counts as explicit options, as if in the QDict */
    parse_json_protocol(options, &amp;amp;filename, &amp;amp;local_err);
    if (local_err) {
        goto fail;
    }

    bs-&amp;gt;explicit_options = qdict_clone_shallow(options);

    if (child_class) {
        bool parent_is_format;

        if (parent-&amp;gt;drv) {
            parent_is_format = parent-&amp;gt;drv-&amp;gt;is_format;
        } else {
            /*
             * parent-&amp;gt;drv is not set yet because this node is opened for
             * (potential) format probing.  That means that @parent is going
             * to be a format node.
             */
            parent_is_format = true;
        }

        bs-&amp;gt;inherits_from = parent;
        child_class-&amp;gt;inherit_options(child_role, parent_is_format,
                                     &amp;amp;flags, options,
                                     parent-&amp;gt;open_flags, parent-&amp;gt;options);
    }

    ret = bdrv_fill_options(&amp;amp;options, filename, &amp;amp;flags, &amp;amp;local_err);
    if (ret &amp;lt; 0) {
        goto fail;
    }

    /*
     * Set the BDRV_O_RDWR and BDRV_O_ALLOW_RDWR flags.
     * Caution: getting a boolean member of @options requires care.
     * When @options come from -blockdev or blockdev_add, members are
     * typed according to the QAPI schema, but when they come from
     * -drive, they're all QString.
     */
    if (g_strcmp0(qdict_get_try_str(options, BDRV_OPT_READ_ONLY), &quot;on&quot;) &amp;amp;&amp;amp;
        !qdict_get_try_bool(options, BDRV_OPT_READ_ONLY, false)) {
        flags |= (BDRV_O_RDWR | BDRV_O_ALLOW_RDWR);
    } else {
        flags &amp;amp;= ~BDRV_O_RDWR;
    }

    if (flags &amp;amp; BDRV_O_SNAPSHOT) {
        snapshot_options = qdict_new();
        bdrv_temp_snapshot_options(&amp;amp;snapshot_flags, snapshot_options,
                                   flags, options);
        /* Let bdrv_backing_options() override &quot;read-only&quot; */
        qdict_del(options, BDRV_OPT_READ_ONLY);
        bdrv_inherited_options(BDRV_CHILD_COW, true,
                               &amp;amp;flags, options, flags, options);
    }

    bs-&amp;gt;open_flags = flags;
    bs-&amp;gt;options = options;
    options = qdict_clone_shallow(options);

    /* Find the right image format driver */
    /* See cautionary note on accessing @options above */
    drvname = qdict_get_try_str(options, &quot;driver&quot;);
    if (drvname) {
        drv = bdrv_find_format(drvname);
        if (!drv) {
            error_setg(errp, &quot;Unknown driver: '%s'&quot;, drvname);
            goto fail;
        }
    }

    assert(drvname || !(flags &amp;amp; BDRV_O_PROTOCOL));

    /* See cautionary note on accessing @options above */
    backing = qdict_get_try_str(options, &quot;backing&quot;);
    if (qobject_to(QNull, qdict_get(options, &quot;backing&quot;)) != NULL ||
        (backing &amp;amp;&amp;amp; *backing == '\0'))
    {
        if (backing) {
            warn_report(&quot;Use of \&quot;backing\&quot;: \&quot;\&quot; is deprecated; &quot;
                        &quot;use \&quot;backing\&quot;: null instead&quot;);
        }
        flags |= BDRV_O_NO_BACKING;
        qdict_del(bs-&amp;gt;explicit_options, &quot;backing&quot;);
        qdict_del(bs-&amp;gt;options, &quot;backing&quot;);
        qdict_del(options, &quot;backing&quot;);
    }

    /* Open image file without format layer. This BlockBackend is only used for
     * probing, the block drivers will do their own bdrv_open_child() for the
     * same BDS, which is why we put the node name back into options. */
    if ((flags &amp;amp; BDRV_O_PROTOCOL) == 0) {
        BlockDriverState *file_bs;

        file_bs = bdrv_open_child_bs(filename, options, &quot;file&quot;, bs,
                                     &amp;amp;child_of_bds, BDRV_CHILD_IMAGE,
                                     true, &amp;amp;local_err);
        if (local_err) {
            goto fail;
        }
        if (file_bs != NULL) {
            /* Not requesting BLK_PERM_CONSISTENT_READ because we're only
             * looking at the header to guess the image format. This works even
             * in cases where a guest would not see a consistent state. */
            ctx = bdrv_get_aio_context(file_bs);
            aio_context_acquire(ctx);
            file = blk_new(ctx, 0, BLK_PERM_ALL);
            blk_insert_bs(file, file_bs, &amp;amp;local_err);
            bdrv_unref(file_bs);
            aio_context_release(ctx);

            if (local_err) {
                goto fail;
            }

            qdict_put_str(options, &quot;file&quot;, bdrv_get_node_name(file_bs));
        }
    }

    /* Image format probing */
    bs-&amp;gt;probed = !drv;
    if (!drv &amp;amp;&amp;amp; file) {
        ret = find_image_format(file, filename, &amp;amp;drv, &amp;amp;local_err);
        if (ret &amp;lt; 0) {
            goto fail;
        }
        /*
         * This option update would logically belong in bdrv_fill_options(),
         * but we first need to open bs-&amp;gt;file for the probing to work, while
         * opening bs-&amp;gt;file already requires the (mostly) final set of options
         * so that cache mode etc. can be inherited.
         *
         * Adding the driver later is somewhat ugly, but it's not an option
         * that would ever be inherited, so it's correct. We just need to make
         * sure to update both bs-&amp;gt;options (which has the full effective
         * options for bs) and options (which has file.* already removed).
         */
        qdict_put_str(bs-&amp;gt;options, &quot;driver&quot;, drv-&amp;gt;format_name);
        qdict_put_str(options, &quot;driver&quot;, drv-&amp;gt;format_name);
    } else if (!drv) {
        error_setg(errp, &quot;Must specify either driver or file&quot;);
        goto fail;
    }

    /* BDRV_O_PROTOCOL must be set iff a protocol BDS is about to be created */
    assert(!!(flags &amp;amp; BDRV_O_PROTOCOL) == !!drv-&amp;gt;bdrv_file_open);
    /* file must be NULL if a protocol BDS is about to be created
     * (the inverse results in an error message from bdrv_open_common()) */
    assert(!(flags &amp;amp; BDRV_O_PROTOCOL) || !file);

    /* Open the image */
    ret = bdrv_open_common(bs, file, options, &amp;amp;local_err);
    if (ret &amp;lt; 0) {
        goto fail;
    }

    /* The AioContext could have changed during bdrv_open_common() */
    ctx = bdrv_get_aio_context(bs);

    if (file) {
        aio_context_acquire(ctx);
        blk_unref(file);
        aio_context_release(ctx);
        file = NULL;
    }

    /* If there is a backing file, use it */
    if ((flags &amp;amp; BDRV_O_NO_BACKING) == 0) {
        ret = bdrv_open_backing_file(bs, options, &quot;backing&quot;, &amp;amp;local_err);
        if (ret &amp;lt; 0) {
            goto close_and_fail;
        }
    }

    /* Remove all children options and references
     * from bs-&amp;gt;options and bs-&amp;gt;explicit_options */
    QLIST_FOREACH(child, &amp;amp;bs-&amp;gt;children, next) {
        char *child_key_dot;
        child_key_dot = g_strdup_printf(&quot;%s.&quot;, child-&amp;gt;name);
        qdict_extract_subqdict(bs-&amp;gt;explicit_options, NULL, child_key_dot);
        qdict_extract_subqdict(bs-&amp;gt;options, NULL, child_key_dot);
        qdict_del(bs-&amp;gt;explicit_options, child-&amp;gt;name);
        qdict_del(bs-&amp;gt;options, child-&amp;gt;name);
        g_free(child_key_dot);
    }

    /* Check if any unknown options were used */
    if (qdict_size(options) != 0) {
        const QDictEntry *entry = qdict_first(options);
        if (flags &amp;amp; BDRV_O_PROTOCOL) {
            error_setg(errp, &quot;Block protocol '%s' doesn't support the option &quot;
                       &quot;'%s'&quot;, drv-&amp;gt;format_name, entry-&amp;gt;key);
        } else {
            error_setg(errp,
                       &quot;Block format '%s' does not support the option '%s'&quot;,
                       drv-&amp;gt;format_name, entry-&amp;gt;key);
        }

        goto close_and_fail;
    }

    bdrv_parent_cb_change_media(bs, true);

    qobject_unref(options);
    options = NULL;

    /* For snapshot=on, create a temporary qcow2 overlay. bs points to the
     * temporary snapshot afterwards. */
    if (snapshot_flags) {
        BlockDriverState *snapshot_bs;
        snapshot_bs = bdrv_append_temp_snapshot(bs, snapshot_flags,
                                                snapshot_options, &amp;amp;local_err);
        snapshot_options = NULL;
        if (local_err) {
            goto close_and_fail;
        }
        /* We are not going to return bs but the overlay on top of it
         * (snapshot_bs); thus, we have to drop the strong reference to bs
         * (which we obtained by calling bdrv_new()). bs will not be deleted,
         * though, because the overlay still has a reference to it. */
        aio_context_acquire(ctx);
        bdrv_unref(bs);
        aio_context_release(ctx);
        bs = snapshot_bs;
    }

    return bs;

fail:
    aio_context_acquire(ctx);
    blk_unref(file);
    qobject_unref(snapshot_options);
    qobject_unref(bs-&amp;gt;explicit_options);
    qobject_unref(bs-&amp;gt;options);
    qobject_unref(options);
    bs-&amp;gt;options = NULL;
    bs-&amp;gt;explicit_options = NULL;
    bdrv_unref(bs);
    aio_context_release(ctx);
    error_propagate(errp, local_err);
    return NULL;

close_and_fail:
    aio_context_acquire(ctx);
    bdrv_unref(bs);
    aio_context_release(ctx);
    qobject_unref(snapshot_options);
    qobject_unref(options);
    error_propagate(errp, local_err);
    return NULL;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_inherit는 분기가 좀 많아서 한눈에 보기 어려운데, 큰 줄기만 추리면 이런 흐름이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;3112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/meErQ/dJMcag6VH1O/iOOtC7pBkkEdo4CqsWsYK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/meErQ/dJMcag6VH1O/iOOtC7pBkkEdo4CqsWsYK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/meErQ/dJMcag6VH1O/iOOtC7pBkkEdo4CqsWsYK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmeErQ%2FdJMcag6VH1O%2FiOOtC7pBkkEdo4CqsWsYK1%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;388&quot; height=&quot;1967&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;3112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_inherit 함수의 역할은 디스크 이미지(raw, qcow2, vmdk 등 포맷)를 여는 것이다. 그중 가장 핵심이 되는 부분은&lt;/p&gt;
&lt;pre id=&quot;code_1781570660103&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Open the image */
ret = bdrv_open_common(bs, file, options, &amp;amp;local_err);
if (ret &amp;lt; 0) {
    goto fail;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_common 함수도 block.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570684803&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * Common part for opening disk images and files
 *
 * Removes all processed options from *options.
 */
static int bdrv_open_common(BlockDriverState *bs, BlockBackend *file,
                            QDict *options, Error **errp)
{
    int ret, open_flags;
    const char *filename;
    const char *driver_name = NULL;
    const char *node_name = NULL;
    const char *discard;
    QemuOpts *opts;
    BlockDriver *drv;
    Error *local_err = NULL;
    bool ro;

    assert(bs-&amp;gt;file == NULL);
    assert(options != NULL &amp;amp;&amp;amp; bs-&amp;gt;options != options);
    GLOBAL_STATE_CODE();

    opts = qemu_opts_create(&amp;amp;bdrv_runtime_opts, NULL, 0, &amp;amp;error_abort);
    if (!qemu_opts_absorb_qdict(opts, options, errp)) {
        ret = -EINVAL;
        goto fail_opts;
    }

    update_flags_from_options(&amp;amp;bs-&amp;gt;open_flags, opts);

    driver_name = qemu_opt_get(opts, &quot;driver&quot;);
    drv = bdrv_find_format(driver_name);
    assert(drv != NULL);

    bs-&amp;gt;force_share = qemu_opt_get_bool(opts, BDRV_OPT_FORCE_SHARE, false);

    if (bs-&amp;gt;force_share &amp;amp;&amp;amp; (bs-&amp;gt;open_flags &amp;amp; BDRV_O_RDWR)) {
        error_setg(errp,
                   BDRV_OPT_FORCE_SHARE
                   &quot;=on can only be used with read-only images&quot;);
        ret = -EINVAL;
        goto fail_opts;
    }

    if (file != NULL) {
        bdrv_refresh_filename(blk_bs(file));
        filename = blk_bs(file)-&amp;gt;filename;
    } else {
        /*
         * Caution: while qdict_get_try_str() is fine, getting
         * non-string types would require more care.  When @options
         * come from -blockdev or blockdev_add, its members are typed
         * according to the QAPI schema, but when they come from
         * -drive, they're all QString.
         */
        filename = qdict_get_try_str(options, &quot;filename&quot;);
    }

    if (drv-&amp;gt;bdrv_needs_filename &amp;amp;&amp;amp; (!filename || !filename[0])) {
        error_setg(errp, &quot;The '%s' block driver requires a file name&quot;,
                   drv-&amp;gt;format_name);
        ret = -EINVAL;
        goto fail_opts;
    }

    trace_bdrv_open_common(bs, filename ?: &quot;&quot;, bs-&amp;gt;open_flags,
                           drv-&amp;gt;format_name);

    ro = bdrv_is_read_only(bs);

    if (use_bdrv_whitelist &amp;amp;&amp;amp; !bdrv_is_whitelisted(drv, ro)) {
        if (!ro &amp;amp;&amp;amp; bdrv_is_whitelisted(drv, true)) {
            ret = bdrv_apply_auto_read_only(bs, NULL, NULL);
        } else {
            ret = -ENOTSUP;
        }
        if (ret &amp;lt; 0) {
            error_setg(errp,
                       !ro &amp;amp;&amp;amp; bdrv_is_whitelisted(drv, true)
                       ? &quot;Driver '%s' can only be used for read-only devices&quot;
                       : &quot;Driver '%s' is not whitelisted&quot;,
                       drv-&amp;gt;format_name);
            goto fail_opts;
        }
    }

    /* bdrv_new() and bdrv_close() make it so */
    assert(qatomic_read(&amp;amp;bs-&amp;gt;copy_on_read) == 0);

    if (bs-&amp;gt;open_flags &amp;amp; BDRV_O_COPY_ON_READ) {
        if (!ro) {
            bdrv_enable_copy_on_read(bs);
        } else {
            error_setg(errp, &quot;Can't use copy-on-read on read-only device&quot;);
            ret = -EINVAL;
            goto fail_opts;
        }
    }

    discard = qemu_opt_get(opts, BDRV_OPT_DISCARD);
    if (discard != NULL) {
        if (bdrv_parse_discard_flags(discard, &amp;amp;bs-&amp;gt;open_flags) != 0) {
            error_setg(errp, &quot;Invalid discard option&quot;);
            ret = -EINVAL;
            goto fail_opts;
        }
    }

    bs-&amp;gt;detect_zeroes =
        bdrv_parse_detect_zeroes(opts, bs-&amp;gt;open_flags, &amp;amp;local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        ret = -EINVAL;
        goto fail_opts;
    }

    if (filename != NULL) {
        pstrcpy(bs-&amp;gt;filename, sizeof(bs-&amp;gt;filename), filename);
    } else {
        bs-&amp;gt;filename[0] = '\0';
    }
    pstrcpy(bs-&amp;gt;exact_filename, sizeof(bs-&amp;gt;exact_filename), bs-&amp;gt;filename);

    /* Open the image, either directly or using a protocol */
    open_flags = bdrv_open_flags(bs, bs-&amp;gt;open_flags);
    node_name = qemu_opt_get(opts, &quot;node-name&quot;);

    assert(!drv-&amp;gt;bdrv_file_open || file == NULL);
    ret = bdrv_open_driver(bs, drv, node_name, options, open_flags, errp);
    if (ret &amp;lt; 0) {
        goto fail_opts;
    }

    qemu_opts_del(opts);
    return 0;

fail_opts:
    qemu_opts_del(opts);
    return ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_common 함수 안에서, 디스크 파일의 포맷에 따라 BlockDriver *drv를 얻는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570712056&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int bdrv_open_common(BlockDriverState *bs, BlockBackend *file,
                            QDict *options, Error **errp)
{
    int ret, open_flags;
    const char *filename;
    const char *driver_name = NULL;
    const char *node_name = NULL;
    const char *discard;
    QemuOpts *opts;
    BlockDriver *drv;
    Error *local_err = NULL;
    bool ro;
    &amp;hellip;&amp;hellip;
    driver_name = qemu_opt_get(opts, &quot;driver&quot;);
    drv = bdrv_find_format(driver_name);
    assert(drv != NULL);
    &amp;hellip;&amp;hellip;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그리고 나서 bdrv_open_driver 함수를 호출하는데, 얻어진 BlockDriver *drv에 따라 디스크 파일을 연다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570729135&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assert(!drv-&amp;gt;bdrv_file_open || file == NULL);
ret = bdrv_open_driver(bs, drv, node_name, options, open_flags, errp);
if (ret &amp;lt; 0) {
    goto fail_opts;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_common의 핵심 두 단계를 그림으로 보면 이렇다. &quot;포맷 이름 &amp;rarr; 드라이버 객체&quot;로 변환한 다음, 그 드라이버를 들고 bdrv_open_driver로 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oOoAd/dJMcagFVLar/ogHkxGFTOJy6Y259VVmGn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oOoAd/dJMcagFVLar/ogHkxGFTOJy6Y259VVmGn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oOoAd/dJMcagFVLar/ogHkxGFTOJy6Y259VVmGn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoOoAd%2FdJMcagFVLar%2FogHkxGFTOJy6Y259VVmGn1%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;784&quot; height=&quot;64&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_driver 함수도 block 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570761483&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * The caller must always hold @bs AioContext lock, because this function calls
 * bdrv_refresh_total_sectors() which polls when called from non-coroutine
 * context.
 */
static int no_coroutine_fn GRAPH_UNLOCKED
bdrv_open_driver(BlockDriverState *bs, BlockDriver *drv, const char *node_name,
                 QDict *options, int open_flags, Error **errp)
{
    AioContext *ctx;
    Error *local_err = NULL;
    int i, ret;
    GLOBAL_STATE_CODE();

    bdrv_assign_node_name(bs, node_name, &amp;amp;local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return -EINVAL;
    }

    bs-&amp;gt;drv = drv;
    bs-&amp;gt;opaque = g_malloc0(drv-&amp;gt;instance_size);

    if (drv-&amp;gt;bdrv_file_open) {
        assert(!drv-&amp;gt;bdrv_needs_filename || bs-&amp;gt;filename[0]);
        ret = drv-&amp;gt;bdrv_file_open(bs, options, open_flags, &amp;amp;local_err);
    } else if (drv-&amp;gt;bdrv_open) {
        ret = drv-&amp;gt;bdrv_open(bs, options, open_flags, &amp;amp;local_err);
    } else {
        ret = 0;
    }

    if (ret &amp;lt; 0) {
        if (local_err) {
            error_propagate(errp, local_err);
        } else if (bs-&amp;gt;filename[0]) {
            error_setg_errno(errp, -ret, &quot;Could not open '%s'&quot;, bs-&amp;gt;filename);
        } else {
            error_setg_errno(errp, -ret, &quot;Could not open image&quot;);
        }
        goto open_failed;
    }

    assert(!(bs-&amp;gt;supported_read_flags &amp;amp; ~BDRV_REQ_MASK));
    assert(!(bs-&amp;gt;supported_write_flags &amp;amp; ~BDRV_REQ_MASK));

    /*
     * Always allow the BDRV_REQ_REGISTERED_BUF optimization hint. This saves
     * drivers that pass read/write requests through to a child the trouble of
     * declaring support explicitly.
     *
     * Drivers must not propagate this flag accidentally when they initiate I/O
     * to a bounce buffer. That case should be rare though.
     */
    bs-&amp;gt;supported_read_flags |= BDRV_REQ_REGISTERED_BUF;
    bs-&amp;gt;supported_write_flags |= BDRV_REQ_REGISTERED_BUF;

    /* Get the context after .bdrv_open, it can change the context */
    ctx = bdrv_get_aio_context(bs);
    aio_context_acquire(ctx);

    ret = bdrv_refresh_total_sectors(bs, bs-&amp;gt;total_sectors);
    if (ret &amp;lt; 0) {
        error_setg_errno(errp, -ret, &quot;Could not refresh total sector count&quot;);
        aio_context_release(ctx);
        return ret;
    }

    bdrv_graph_rdlock_main_loop();
    bdrv_refresh_limits(bs, NULL, &amp;amp;local_err);
    bdrv_graph_rdunlock_main_loop();
    aio_context_release(ctx);

    if (local_err) {
        error_propagate(errp, local_err);
        return -EINVAL;
    }

    assert(bdrv_opt_mem_align(bs) != 0);
    assert(bdrv_min_mem_align(bs) != 0);
    assert(is_power_of_2(bs-&amp;gt;request_alignment));

    for (i = 0; i &amp;lt; bs-&amp;gt;quiesce_counter; i++) {
        if (drv-&amp;gt;bdrv_drain_begin) {
            drv-&amp;gt;bdrv_drain_begin(bs);
        }
    }

    return 0;
open_failed:
    bs-&amp;gt;drv = NULL;
    if (bs-&amp;gt;file != NULL) {
        bdrv_unref_child(bs, bs-&amp;gt;file);
        assert(!bs-&amp;gt;file);
    }
    g_free(bs-&amp;gt;opaque);
    bs-&amp;gt;opaque = NULL;
    return ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_driver의 핵심은 결국 함수 포인터 디스패치 한 줄이다. drv-&amp;gt;bdrv_open이 raw_open인지 qcow2_open인지에 따라 다른 코드가 실행된다. C 언어로 구현된 &quot;가상 함수 호출&quot;이라고 봐도 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t1rD2/dJMcaaZW0EM/oYnG5dLY3uomWDcVHkoN7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t1rD2/dJMcaaZW0EM/oYnG5dLY3uomWDcVHkoN7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t1rD2/dJMcaaZW0EM/oYnG5dLY3uomWDcVHkoN7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft1rD2%2FdJMcaaZW0EM%2FoYnG5dLY3uomWDcVHkoN7k%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;370&quot; height=&quot;660&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위에서 이미 언급했듯이, 가상 머신의 하드 디스크 파일 포맷은 여러 가지가 있다. 예를 들어 raw, qcow2, vmdk 등이고, 각 포맷마다 각자의 장단점이 있다. 가상 머신을 시작할 때, 자유롭게 지정할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;raw&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;raw는 원본 디스크 이미지 포맷이다. 베어 디스크 포맷이라고도 부르고, 기본 가상 디스크 포맷이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;raw는, 본래 의미가 &quot;미가공&quot;이고, 미포맷화의 디스크를 가리키기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux 안에서, raw는 디스크 이미지의 순수 이진 이미지 포맷이다. 희소 파일(sparse file)을 지원하는 파일 시스템에서는, raw 포맷의 이미지 파일은 데이터 기록을 위해 실제로 사용되는 공간만 차지한다.&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;이 포맷의 장점은 단순하고, 다른 에뮬레이터(가상화 환경) 안으로 가져오고 내보내기가 쉽다는 것이다. 만약 파일 시스템이 holes을 지원한다면 (예를 들어 linux 안의 ext2, ext3, ext4나 windows 상의 ntfs),&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 환경에서 raw 디스크에 대해서는 이미 데이터가 기록된 섹터만 저장 공간을 차지하므로, &quot;qemu-img info&quot; 명령으로 raw 디스크의 실제 크기를 볼 수 있고, &quot;ls -ls&quot;로도 볼 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;qcow2&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;qcow2 이미지 포맷은 QEMU에서 가장 많이 쓰이는 이미지 파일 포맷이다. 이것도 하나의 파일 형태로 일정한 고정 크기의 블록 디바이스 디스크를 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;holes 을 지원하지 않는 파일 시스템에서도 qcow2는 더 적은 저장 공간을 차지할 수 있고, 게다가 선택 가능한 몇 가지 설정 항목들이 있다. 예를 들어 [AES] 암호화, zlib 압축, 스냅샷 등이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;qcow2는 현재 비교적 주류인 가상화 이미지 포맷이다. 현재 qcow2의 성능은 raw 베어 포맷의 성능에 가깝고, 보통의 raw 포맷 이미지와 비교했을 때, 추가로 다음과 같은 특성을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 적은 저장 공간 점유, 파일 시스템이 홀(holes)을 지원하지 않더라도. qcow2 포맷의 이미지는 raw 포맷 파일보다 더 작고, 가상 머신이 실제로 디스크 공간을 점유했을 때만 그 파일이 커진다. 이는 마이그레이션에 드는 트래픽을 편리하게 줄일 수 있고, 클라우드 컴퓨팅 시나리오에 더 적합하다.&lt;/li&gt;
&lt;li&gt;쓰기 시 복사(COW, copy-on-write) 지원, 이미지 파일은 베이스 디스크의 변화만 반영한다.&lt;/li&gt;
&lt;li&gt;스냅샷(snapshot) 지원, 이미지 파일이 여러 개의 과거 스냅샷 이력을 포함할 수 있다.&lt;/li&gt;
&lt;li&gt;zlib 기반의 압축 방식 선택 가능, 각 클러스터(cluster) 단독으로 zlib 압축을 사용하도록 허용한다.&lt;/li&gt;
&lt;li&gt;AES 암호화 선택 가능, 128비트의 AES 키를 사용해서 암호화하는 것을 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;BlockDriver 다형성과 qcow2_open 코루틴&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 bdrv_open 함수 --&amp;gt; bdrv_open_inherit 함수 --&amp;gt; bdrv_open_common 함수를 따라가면서, bdrv_open_common 함수의 두 가지 핵심 단계를 설명했다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bdrv_open_common 함수 안에서, 디스크 파일의 포맷에 따라 BlockDriver *drv를 얻는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570927026&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int bdrv_open_common(BlockDriverState *bs, BlockBackend *file,
                            QDict *options, Error **errp)
{
    int ret, open_flags;
    const char *filename;
    const char *driver_name = NULL;
    const char *node_name = NULL;
    const char *discard;
    QemuOpts *opts;
    BlockDriver *drv;
    Error *local_err = NULL;
    bool ro;
    &amp;hellip;&amp;hellip;
    driver_name = qemu_opt_get(opts, &quot;driver&quot;);
    drv = bdrv_find_format(driver_name);
    assert(drv != NULL);
    &amp;hellip;&amp;hellip;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그리고 나서 bdrv_open_driver 함수를 호출하는데, 얻어진 BlockDriver *drv에 따라 디스크 파일을 연다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570943077&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;assert(!drv-&amp;gt;bdrv_file_open || file == NULL);
ret = bdrv_open_driver(bs, drv, node_name, options, open_flags, errp);
if (ret &amp;lt; 0) {
    goto fail_opts;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU가 지원하는 몇 가지 디스크 파일 포맷도 확인했는데, raw, qcow2, vmdk, vdi 등이 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;서로 다른 포맷에 대해, 여는 방식이 같지 않다. raw와 qcow2를 예로 들면, 각각의 BlockDriver 정의는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781570982077&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BlockDriver bdrv_raw = {
    .format_name          = &quot;raw&quot;,
    .instance_size        = sizeof(BDRVRawState),
    .supports_zoned_children = true,
    .bdrv_probe           = &amp;amp;raw_probe,
    .bdrv_reopen_prepare  = &amp;amp;raw_reopen_prepare,
    .bdrv_reopen_commit   = &amp;amp;raw_reopen_commit,
    .bdrv_reopen_abort    = &amp;amp;raw_reopen_abort,
    .bdrv_open            = &amp;amp;raw_open,
    .bdrv_child_perm      = raw_child_perm,
    .bdrv_co_create_opts  = &amp;amp;raw_co_create_opts,
    .bdrv_co_preadv       = &amp;amp;raw_co_preadv,
    .bdrv_co_pwritev      = &amp;amp;raw_co_pwritev,
    .bdrv_co_pwrite_zeroes = &amp;amp;raw_co_pwrite_zeroes,
    .bdrv_co_pdiscard     = &amp;amp;raw_co_pdiscard,
    .bdrv_co_zone_report  = &amp;amp;raw_co_zone_report,
    .bdrv_co_zone_mgmt    = &amp;amp;raw_co_zone_mgmt,
    .bdrv_co_zone_append  = &amp;amp;raw_co_zone_append,
    .bdrv_co_block_status = &amp;amp;raw_co_block_status,
    .bdrv_co_copy_range_from = &amp;amp;raw_co_copy_range_from,
    .bdrv_co_copy_range_to  = &amp;amp;raw_co_copy_range_to,
    .bdrv_co_truncate     = &amp;amp;raw_co_truncate,
    .bdrv_co_getlength    = &amp;amp;raw_co_getlength,
    .is_format            = true,
    .bdrv_measure         = &amp;amp;raw_measure,
    .bdrv_co_get_info     = &amp;amp;raw_co_get_info,
    .bdrv_refresh_limits  = &amp;amp;raw_refresh_limits,
    .bdrv_probe_blocksizes = &amp;amp;raw_probe_blocksizes,
    .bdrv_probe_geometry  = &amp;amp;raw_probe_geometry,
    .bdrv_co_eject        = &amp;amp;raw_co_eject,
    .bdrv_co_lock_medium  = &amp;amp;raw_co_lock_medium,
    .bdrv_co_ioctl        = &amp;amp;raw_co_ioctl,
    .create_opts          = &amp;amp;raw_create_opts,
    .bdrv_has_zero_init   = &amp;amp;raw_has_zero_init,
    .strong_runtime_opts  = raw_strong_runtime_opts,
    .mutable_opts         = mutable_opts,
    .bdrv_cancel_in_flight = raw_cancel_in_flight,
};

static void bdrv_raw_init(void)
{
    bdrv_register(&amp;amp;bdrv_raw);
}

block_init(bdrv_raw_init);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781570993417&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BlockDriver bdrv_qcow2 = {
    .format_name        = &quot;qcow2&quot;,
    .instance_size      = sizeof(BDRVQcow2State),
    .bdrv_probe         = qcow2_probe,
    .bdrv_open          = qcow2_open,
    .bdrv_close         = qcow2_close,
    .bdrv_reopen_prepare  = qcow2_reopen_prepare,
    .bdrv_reopen_commit   = qcow2_reopen_commit,
    .bdrv_reopen_commit_post = qcow2_reopen_commit_post,
    .bdrv_reopen_abort    = qcow2_reopen_abort,
    .bdrv_join_options    = qcow2_join_options,
    .bdrv_child_perm      = bdrv_default_perms,
    .bdrv_co_create_opts  = qcow2_co_create_opts,
    .bdrv_co_create       = qcow2_co_create,
    .bdrv_has_zero_init   = qcow2_has_zero_init,
    .bdrv_co_block_status = qcow2_co_block_status,

    .bdrv_co_preadv_part    = qcow2_co_preadv_part,
    .bdrv_co_pwritev_part   = qcow2_co_pwritev_part,
    .bdrv_co_flush_to_os    = qcow2_co_flush_to_os,

    .bdrv_co_pwrite_zeroes  = qcow2_co_pwrite_zeroes,
    .bdrv_co_pdiscard       = qcow2_co_pdiscard,
    .bdrv_co_copy_range_from = qcow2_co_copy_range_from,
    .bdrv_co_copy_range_to  = qcow2_co_copy_range_to,
    .bdrv_co_truncate       = qcow2_co_truncate,
    .bdrv_co_pwritev_compressed_part = qcow2_co_pwritev_compressed_part,
    .bdrv_make_empty        = qcow2_make_empty,

    .bdrv_snapshot_create   = qcow2_snapshot_create,
    .bdrv_snapshot_goto     = qcow2_snapshot_goto,
    .bdrv_snapshot_delete   = qcow2_snapshot_delete,
    .bdrv_snapshot_list     = qcow2_snapshot_list,
    .bdrv_snapshot_load_tmp = qcow2_snapshot_load_tmp,
    .bdrv_measure           = qcow2_measure,
    .bdrv_co_get_info       = qcow2_co_get_info,
    .bdrv_get_specific_info = qcow2_get_specific_info,

    .bdrv_co_save_vmstate   = qcow2_co_save_vmstate,
    .bdrv_co_load_vmstate   = qcow2_co_load_vmstate,

    .is_format                  = true,
    .supports_backing           = true,
    .bdrv_change_backing_file   = qcow2_change_backing_file,

    .bdrv_refresh_limits        = qcow2_refresh_limits,
    .bdrv_co_invalidate_cache   = qcow2_co_invalidate_cache,
    .bdrv_inactivate            = qcow2_inactivate,

    .create_opts         = &amp;amp;qcow2_create_opts,
    .amend_opts          = &amp;amp;qcow2_amend_opts,
    .strong_runtime_opts = qcow2_strong_runtime_opts,
    .mutable_opts        = mutable_opts,
    .bdrv_co_check       = qcow2_co_check,
    .bdrv_amend_options  = qcow2_amend_options,
    .bdrv_co_amend       = qcow2_co_amend,

    .bdrv_detach_aio_context  = qcow2_detach_aio_context,
    .bdrv_attach_aio_context  = qcow2_attach_aio_context,

    .bdrv_supports_persistent_dirty_bitmap =
        qcow2_supports_persistent_dirty_bitmap,
    .bdrv_co_can_store_new_dirty_bitmap = qcow2_co_can_store_new_dirty_bitmap,
    .bdrv_co_remove_persistent_dirty_bitmap =
        qcow2_co_remove_persistent_dirty_bitmap,
};

static void bdrv_qcow2_init(void)
{
    bdrv_register(&amp;amp;bdrv_qcow2);
}

block_init(bdrv_qcow2_init);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그러면, raw 포맷의 경우는 drv-&amp;gt;bdrv_open()이 호출하는 게 바로 raw_open 함수다. qcow2 포맷의 경우는 drv-&amp;gt;bdrv_open()이 호출하는 게 qcow2_open 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781571013930&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BlockDriver bdrv_raw = {
    .format_name          = &quot;raw&quot;,
    .instance_size        = sizeof(BDRVRawState),
    .supports_zoned_children = true,
    .bdrv_probe           = &amp;amp;raw_probe,
    .bdrv_reopen_prepare  = &amp;amp;raw_reopen_prepare,
    .bdrv_reopen_commit   = &amp;amp;raw_reopen_commit,
    .bdrv_reopen_abort    = &amp;amp;raw_reopen_abort,
    .bdrv_open            = &amp;amp;raw_open,
    &amp;hellip;&amp;hellip;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781571023275&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BlockDriver bdrv_qcow2 = {
    .format_name        = &quot;qcow2&quot;,
    .instance_size      = sizeof(BDRVQcow2State),
    .bdrv_probe         = qcow2_probe,
    .bdrv_open          = qcow2_open,
    .bdrv_close         = qcow2_close,
    &amp;hellip;&amp;hellip;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;BlockDriver의 함수 포인터 테이블 구조를 그림으로 정리하면 이렇다. 같은 BlockDriver 구조체 모양에 raw용 함수 한 세트, qcow2용 함수 한 세트가 들어 있는 것이다 (C 언어로 만든 다형성)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lZ9Zw/dJMcagskYUG/aO1lMlb7JDC9bNhQfz3y61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lZ9Zw/dJMcagskYUG/aO1lMlb7JDC9bNhQfz3y61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lZ9Zw/dJMcagskYUG/aO1lMlb7JDC9bNhQfz3y61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlZ9Zw%2FdJMcagskYUG%2FaO1lMlb7JDC9bNhQfz3y61%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;441&quot; height=&quot;592&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이번엔 먼저 qcow2 포맷에 대응되는 qcow2_open 함수를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781571059262&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int qcow2_open(BlockDriverState *bs, QDict *options, int flags,
                      Error **errp)
{
    BDRVQcow2State *s = bs-&amp;gt;opaque;
    QCow2OpenCo qoc = {
        .bs = bs,
        .options = options,
        .flags = flags,
        .errp = errp,
        .ret = -EINPROGRESS
    };
    int ret;

    ret = bdrv_open_file_child(NULL, options, &quot;file&quot;, bs, errp);
    if (ret &amp;lt; 0) {
        return ret;
    }

    /* Initialise locks */
    qemu_co_mutex_init(&amp;amp;s-&amp;gt;lock);

    assert(!qemu_in_coroutine());
    assert(qemu_get_current_aio_context() == qemu_get_aio_context());

    aio_co_enter(bdrv_get_aio_context(bs),
                 qemu_coroutine_create(qcow2_open_entry, &amp;amp;qoc));
    AIO_WAIT_WHILE_UNLOCKED(NULL, qoc.ret == -EINPROGRESS);

    return qoc.ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;qcow2_open 함수 안에서는, qemu_coroutine_create()와 aio_co_center()를 통해서 코루틴(coroutine) 하나로 들어간다.&amp;nbsp;&lt;br /&gt;&lt;br /&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;/li&gt;
&lt;li&gt;코루틴 간 공유 데이터에 락이 필요 없다. 왜냐하면 모두 하나의 스레드 안에서 돌고 있고, 진정한 병행 실행은 아니기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드는 커널의 개념이고, 만들어진 스레드 하나하나가 커널 안에서 각각 하나의 task_struct 객체에 대응된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널의 스케줄링도 스레드를 단위로 한다. 보통의 프로세스 개발에서는 이게 문제가 없지만, 여기 QEMU의 경우 가상 머신은 사용자 모드와 커널 모드 사이를 자주 왔다 갔다 하면 대가가 비교적 크다(특히 그게 가상 머신의 상태와 관련되어 있을 때).&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;하지만, QEMU 안의 디바이스도 동시성 능력이 필요하다. 그럼 어떻게 해야 하나? 사용자 모드에서 스레드 비슷한 메커니즘 을 구현해야 하니, 즉 코루틴을 도입해서 동시성 실현에 쓰고, 게다가 커널로 전환할 필요 없이 스케줄링을 모두 사용자 모드에서 완성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhIySp/dJMcaaMp90x/T0xrGFP7xkwChTpLVhXTs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhIySp/dJMcaaMp90x/T0xrGFP7xkwChTpLVhXTs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhIySp/dJMcaaMp90x/T0xrGFP7xkwChTpLVhXTs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhIySp%2FdJMcaaMp90x%2FT0xrGFP7xkwChTpLVhXTs1%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;450&quot; height=&quot;368&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서, qcow2 포맷의 파일 하나를 여는 데 코루틴 하나가 쓰인다. 코루틴 하나를 만드는 건 스레드 하나를 만드는 것과 비슷하다. 마찬가지로 함수 하나(코루틴 함수)를 지정해서 실행되도록 해야 하고, 위 코드 안의 qcow2_open_entry가 바로 코루틴 함수다.&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;qcow2_open 안의 호출 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F2dKa/dJMcabLppzQ/yDnmeKJKt968kMcOJBIVlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F2dKa/dJMcabLppzQ/yDnmeKJKt968kMcOJBIVlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F2dKa/dJMcabLppzQ/yDnmeKJKt968kMcOJBIVlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF2dKa%2FdJMcabLppzQ%2FyDnmeKJKt968kMcOJBIVlK%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;784&quot; height=&quot;649&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;qcow2_open_entry 함수도 block/qcow2.c 안에 있고, 코드는 아래와 같다&lt;/p&gt;
&lt;pre id=&quot;code_1781571244595&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void coroutine_fn qcow2_open_entry(void *opaque)
{
    QCow2OpenCo *qoc = opaque;
    BDRVQcow2State *s = qoc-&amp;gt;bs-&amp;gt;opaque;

    GRAPH_RDLOCK_GUARD();

    qemu_co_mutex_lock(&amp;amp;s-&amp;gt;lock);
    qoc-&amp;gt;ret = qcow2_do_open(qoc-&amp;gt;bs, qoc-&amp;gt;options, qoc-&amp;gt;flags, true,
                             qoc-&amp;gt;errp);
    qemu_co_mutex_unlock(&amp;amp;s-&amp;gt;lock);

    aio_wait_kick();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;함수명 앞에 붙어 있는 coroutine_fn은 하나의 코루틴 함수임을 설명해준다. 핵심은 qcow2_do_open 함수를 호출한 것이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;qcow2_do_open 함수도 block/qcow2.c 안에 있는데, 코드가 비교적 길다(거의 600줄에 가깝다). 여기서는 깊이 파고들어서 분석하지는 않겠다. 그 주요 기능이 qcow2 포맷에 근거해서 디스크 파일을 여는 것이라는 점만 알면 된다&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써, qcow2 포맷에 대응되는 qcow2_open 함수는 기본적으로 분석이 끝났다.&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;raw 포맷: raw_open 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이전까지 qcow2 포맷에 대응하는 qcow2_open 함수를 분석했는데, 이번에는 raw 포맷에 대응하는 raw_open 함수를 분석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;raw_open 함수는 block/raw-format.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781571385914&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int raw_open(BlockDriverState *bs, QDict *options, int flags,
                    Error **errp)
{
    BDRVRawState *s = bs-&amp;gt;opaque;
    AioContext *ctx;
    bool has_size;
    uint64_t offset, size;
    BdrvChildRole file_role;
    int ret;

    ret = raw_read_options(options, &amp;amp;offset, &amp;amp;has_size, &amp;amp;size, errp);
    if (ret &amp;lt; 0) {
        return ret;
    }

    /*
     * Without offset and a size limit, this driver behaves very much
     * like a filter.  With any such limit, it does not.
     */
    if (offset || has_size) {
        file_role = BDRV_CHILD_DATA | BDRV_CHILD_PRIMARY;
    } else {
        file_role = BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY;
    }

    bdrv_open_child(NULL, options, &quot;file&quot;, bs, &amp;amp;child_of_bds,
                    file_role, false, errp);
    if (!bs-&amp;gt;file) {
        return -EINVAL;
    }

    bs-&amp;gt;sg = bdrv_is_sg(bs-&amp;gt;file-&amp;gt;bs);
    bs-&amp;gt;supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
        (BDRV_REQ_FUA &amp;amp; bs-&amp;gt;file-&amp;gt;bs-&amp;gt;supported_write_flags);
    bs-&amp;gt;supported_zero_flags = BDRV_REQ_WRITE_UNCHANGED |
        ((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) &amp;amp;
         bs-&amp;gt;file-&amp;gt;bs-&amp;gt;supported_zero_flags);
    bs-&amp;gt;supported_truncate_flags = bs-&amp;gt;file-&amp;gt;bs-&amp;gt;supported_truncate_flags &amp;amp;
        BDRV_REQ_ZERO_WRITE;

    if (bs-&amp;gt;probed &amp;amp;&amp;amp; !bdrv_is_read_only(bs)) {
        bdrv_refresh_filename(bs-&amp;gt;file-&amp;gt;bs);
        fprintf(stderr,
                &quot;WARNING: Image format was not specified for '%s' and probing &quot;
                &quot;guessed raw.\n&quot;
                &quot;         Automatically detecting the format is dangerous for &quot;
                &quot;raw images, write operations on block 0 will be restricted.\n&quot;
                &quot;         Specify the 'raw' format explicitly to remove the &quot;
                &quot;restrictions.\n&quot;,
                bs-&amp;gt;file-&amp;gt;bs-&amp;gt;filename);
    }

    ctx = bdrv_get_aio_context(bs);
    aio_context_acquire(ctx);
    ret = raw_apply_options(bs, s, offset, has_size, size, errp);
    aio_context_release(ctx);

    if (ret &amp;lt; 0) {
        return ret;
    }

    if (bdrv_is_sg(bs) &amp;amp;&amp;amp; (s-&amp;gt;offset || s-&amp;gt;has_size)) {
        error_setg(errp, &quot;Cannot use offset/size with SCSI generic devices&quot;);
        return -EINVAL;
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;raw_open 함수는 qcow2_open 함수와 메커니즘이 다른데, 그 안에 coroutine이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 코드 부분은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781571425904&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bdrv_open_child(NULL, options, &quot;file&quot;, bs, &amp;amp;child_of_bds,
                file_role, false, errp);
if (!bs-&amp;gt;file) {
    return -EINVAL;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_child 함수는 block.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781571439886&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * Opens a disk image whose options are given as BlockdevRef in another block
 * device's options.
 *
 * If allow_none is true, no image will be opened if filename is false and no
 * BlockdevRef is given. NULL will be returned, but errp remains unset.
 *
 * bdrev_key specifies the key for the image's BlockdevRef in the options QDict.
 * That QDict has to be flattened; therefore, if the BlockdevRef is a QDict
 * itself, all options starting with &quot;${bdref_key}.&quot; are considered part of the
 * BlockdevRef.
 *
 * The BlockdevRef will be removed from the options QDict.
 *
 * The caller must hold the lock of the main AioContext and no other AioContext.
 * @parent can move to a different AioContext in this function. Callers must
 * make sure that their AioContext locking is still correct after this.
 */
BdrvChild *bdrv_open_child(const char *filename,
                           QDict *options, const char *bdref_key,
                           BlockDriverState *parent,
                           const BdrvChildClass *child_class,
                           BdrvChildRole child_role,
                           bool allow_none, Error **errp)
{
    BlockDriverState *bs;
    BdrvChild *child;
    AioContext *ctx;

    GLOBAL_STATE_CODE();

    bs = bdrv_open_child_bs(filename, options, bdref_key, parent, child_class,
                            child_role, allow_none, errp);
    if (bs == NULL) {
        return NULL;
    }

    ctx = bdrv_get_aio_context(bs);
    aio_context_acquire(ctx);
    child = bdrv_attach_child(parent, bs, bdref_key, child_class, child_role,
                              errp);
    aio_context_release(ctx);

    return child;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;함수의 주석을 자세히 읽어보자.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 기능: 디스크 이미지를 하나 여는데, 그 옵션은 또 다른 블록 디바이스의 옵션 중 BlockdevRef로 주어진다.&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;bool allow_none&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;allow_none이 true라면, filename이 false이고 BlockdevRef도 주어지지 않았을 때 어떠한 이미지도 열리지 않는다. NULL을 반환하지만, errp는 여전히 설정되지 않는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;const char *bdref_key&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bdrev_key는 옵션 QDict 안에서 이미지의 BlockdevRef를 지정하는 키다. QDict는 반드시 평탄화되어야 한다. 따라서 BlockdevRef가 QDict 자체라면, &quot;${bdref_key}.&quot;로 시작하는 모든 옵션은 BlockdevRef의 일부로 간주된다. BlockdevRef는 옵션 QDict에서 제거된다. 호출자는 반드시 주 AioContext의 락을 보유해야 하며, 다른 AioContext의 락을 보유해서는 안 된다. 부모 노드는 이 함수 안에서 다른 AioContext로 이동할 수 있다. 호출자는 반드시 이 호출 이후 그들의 AioContext 락이 여전히 정확한지 확인해야 한다.&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;여기서 한마디 끼워넣자면, 사실 이전의 qcow2_open 함수에서 호출되는 bdrv_open_file_child 함수는 바로 bdrv_open_child 함수에 한 겹의 래핑을 더한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bdrv_open_child_bs 함수도 block.c 안에 있고, 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781571474737&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static BlockDriverState *
bdrv_open_child_bs(const char *filename, QDict *options, const char *bdref_key,
                   BlockDriverState *parent, const BdrvChildClass *child_class,
                   BdrvChildRole child_role, bool allow_none, Error **errp)
{
    BlockDriverState *bs = NULL;
    QDict *image_options;
    char *bdref_key_dot;
    const char *reference;

    assert(child_class != NULL);

    bdref_key_dot = g_strdup_printf(&quot;%s.&quot;, bdref_key);
    qdict_extract_subqdict(options, &amp;amp;image_options, bdref_key_dot);
    g_free(bdref_key_dot);

    /*
     * Caution: while qdict_get_try_str() is fine, getting non-string
     * types would require more care.  When @options come from
     * -blockdev or blockdev_add, its members are typed according to
     * the QAPI schema, but when they come from -drive, they're all
     * QString.
     */
    reference = qdict_get_try_str(options, bdref_key);
    if (!filename &amp;amp;&amp;amp; !reference &amp;amp;&amp;amp; !qdict_size(image_options)) {
        if (!allow_none) {
            error_setg(errp,
                       &quot;A block device must be specified for \&quot;%s\&quot;&quot;,
                       bdref_key);
        }
        qobject_unref(image_options);
        goto done;
    }

    bs = bdrv_open_inherit(filename, reference, image_options, 0,
                           parent, child_class, child_role, errp);
    if (!bs) {
        goto done;
    }

done:
    qdict_del(options, bdref_key);
    return bs;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;bdrv_open_child_bs 함수 안에서는 또 이전에 다뤘던 bdrv_open_inherit 함수가 호출됐다. 이건 거의 순환처럼 보이는데, 현재 인식 수준으로는 아직 종료 조건이 명확하게 정리되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;전체 흐름과 데이터 구조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QEMU 명령행이 입력된 순간부터 qcow2_do_open이 실제로 호출되기까지의 흐름이다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3QXlY/dJMcah5T4hI/4IqYMFEBkAyQCkOhf4JleK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3QXlY/dJMcah5T4hI/4IqYMFEBkAyQCkOhf4JleK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3QXlY/dJMcah5T4hI/4IqYMFEBkAyQCkOhf4JleK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3QXlY%2FdJMcah5T4hI%2F4IqYMFEBkAyQCkOhf4JleK%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;784&quot; height=&quot;377&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그리고 마지막으로, 이 과정에서 만들어지는 객체들이 메모리상에서 어떻게 연결되는지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KWfXA/dJMcacpYCUG/wlBqGiEtFBknOz715bmdBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KWfXA/dJMcacpYCUG/wlBqGiEtFBknOz715bmdBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KWfXA/dJMcacpYCUG/wlBqGiEtFBknOz715bmdBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKWfXA%2FdJMcacpYCUG%2FwlBqGiEtFBknOz715bmdBk%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;497&quot; height=&quot;498&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 명령행의 -drive file=...,format=qcow2가 결국 BlockBackend &amp;rarr; BlockDriverState (포맷 레이어, qcow2) &amp;rarr; BlockDriverState (프로토콜 레이어, file) &amp;rarr; 호스트 파일로 이어지는 트리 구조를 만들어낸다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU 시작 시 블록 디바이스 가상화의 전체 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;1726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tub3f/dJMcahSg177/dypFPufjsgnhFfhbytwFek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tub3f/dJMcahSg177/dypFPufjsgnhFfhbytwFek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tub3f/dJMcahSg177/dypFPufjsgnhFfhbytwFek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftub3f%2FdJMcahSg177%2FdypFPufjsgnhFfhbytwFek%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;239&quot; height=&quot;1357&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;1726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;QEMU의 시작 명령줄에서 스토리지 관련 파라미터를 설정한다 (-drive、-device 등).&lt;/li&gt;
&lt;li&gt;qemu_create_early_backends 함수(qemu_init 함수 안에서 호출됨) 안에서 configure_blockdev 함수를 호출해 QEMU 명령줄의 시작 파라미터 -drive를 분석하고, 초기화한다. 이 디바이스는 drive_init_func 함수로 초기화하라고 한다.&lt;/li&gt;
&lt;li&gt;drive_init_func 함수 안에서는 drive_new 함수를 호출해 디바이스 하나를 생성한다.&lt;/li&gt;
&lt;li&gt;driver_new 함수 안에서는 또 QEMU의 시작 파라미터 -device를 분석하고, driver를 virblk-blk-pci로 설정한다. 또한 file 파라미터도 분석한다.&lt;/li&gt;
&lt;li&gt;drive_new 함수는 이어서 blockdev_init 함수를 호출해 파라미터에 따라 초기화를 진행한다.&lt;/li&gt;
&lt;li&gt;blockdev_init 함수는 blk_new_open 함수를 호출, 호스트 머신상의 디스크 파일을 열고, BlockBackend를 반환한다. 이게 바로 virtio 백엔드다.&lt;/li&gt;
&lt;li&gt;blk_new_open 함수는 호출 체인 bdrv_open() &amp;rarr; bdrv_open_inherit() &amp;rarr; bdrv_open_common()을 통해, 디스크 파일의 포맷에 따라 BlockDriver를 얻는다. 다른 포맷에 대해서, 그 파일을 여는 방식이 다르다. 예를 들어 qcow2 포맷에 대해서는, 파일을 여는 함수가 qcow2_open 함수다. 반면 raw 포맷에 대해서는, 파일을 여는 함수가 raw_open 함수다.&lt;/li&gt;
&lt;li&gt;drive_new 함수는 마지막으로 DriverInfo를 하나 생성해서 열린 디바이스를 관리한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 회차부터는, 가상 머신의 한 프로세스가 파일 하나를 쓰는 전체 과정에 대해 분석을 진행한다.&lt;/p&gt;</description>
      <category>System Programming/Hypervisor</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/725</guid>
      <comments>https://1000sj.tistory.com/725#entry725comment</comments>
      <pubDate>Mon, 9 Mar 2026 15:53:33 +0900</pubDate>
    </item>
    <item>
      <title>QEMU 코드 분석 #8 VirtIO 디바이스 초기화&amp;mdash; 게스트 커널 드라이버 로딩 &amp;amp; virtqueue 설정</title>
      <link>https://1000sj.tistory.com/722</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;게스트&amp;nbsp;커널에서&amp;nbsp;virtio&amp;nbsp;드라이버가&amp;nbsp;로딩&amp;middot;초기화되는&amp;nbsp;과정과&amp;nbsp;virtqueue&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;1) 게스트 커널의 virtio 드라이버 로딩 (디바이스 리셋 ~ DRIVER_OK)&lt;br /&gt;2) virtio_pci_probe (pci_enable_device &amp;rarr; legacy/modern &amp;rarr; register_virtio_device)&lt;br /&gt;3) virtqueue 설정 (init_vqs &amp;middot; find_vqs &amp;middot; setup_vq &amp;middot; vring_create_virtqueue)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;게스트 커널의 virtio 드라이버 로딩&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;virtio 드라이버 로딩 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 virtio 드라이버가 디바이스 하나를 초기화하는 과정은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;1646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b58Vmb/dJMcaffVxbt/f88mv1vs0231FQUfxfNd5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b58Vmb/dJMcaffVxbt/f88mv1vs0231FQUfxfNd5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b58Vmb/dJMcaffVxbt/f88mv1vs0231FQUfxfNd5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb58Vmb%2FdJMcaffVxbt%2Ff88mv1vs0231FQUfxfNd5k%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;399&quot; height=&quot;1371&quot; data-origin-width=&quot;479&quot; data-origin-height=&quot;1646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) 디바이스 리셋&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이건 지난번에 설명한 register_virtio_device 함수에서 dev-&amp;gt;config-&amp;gt;reset 호출로 완료된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서 주의할 점은, 옛날 버전에서 호출하던 게 dev-&amp;gt;config-&amp;gt;reset()이었는데, 새 버전에서는 직접 virtio_reset_device 함수를 쓴다는 거다.&lt;/p&gt;
&lt;pre id=&quot;code_1781417840998&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * virtio_reset_device - quiesce device for removal
 * @dev: the device to reset
 *
 * Prevents device from sending interrupts and accessing memory.
 *
 * Generally used for cleanup during driver / device removal.
 *
 * Once this has been invoked, caller must ensure that
 * virtqueue_notify / virtqueue_are not in progress.
 *
 * Note: this guarantees that vq callbacks are not in progress, however caller
 * is responsible for preventing access from other contexts, such as a system
 * call/workqueue/bh.  Invoking virtio_break_device then flushing any such
 * contexts is one way to handle that.
 */
void virtio_reset_device(struct virtio_device *dev)
{
#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
	/*
	 * The below virtio_synchronize_cbs() guarantees that any
	 * interrupt for this line arriving after
	 * virtio_synchronize_vqs() has completed is guaranteed to see
	 * vq-&amp;gt;broken as true.
	 */
	virtio_break_device(dev);
	virtio_synchronize_cbs(dev);
#endif
	dev-&amp;gt;config-&amp;gt;reset(dev);
}
EXPORT_SYMBOL_GPL(virtio_reset_device);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;보다시피, 실제로 virtio_reset_device 함수는 dev-&amp;gt;config-&amp;gt;reset()을 간단히 감싼 거라고 할 수 있다. 단지 몇 가지 추가 설정 옵션이랑 그 옵션들에 대한 관련 처리만 더 있을 뿐이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) ACKNOWLEDGE 상태비트 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;ACKNOWLEDGE 상태비트를 설정한다는 건, virtio 드라이버가 이미 그 디바이스를 알고 있다는 걸 나타낸다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이것도 마찬가지로 register_virtio_device 함수에서 virtio_add_status() 함수를 통해 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781417886359&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* We always start by resetting the device, in case a previous
	 * driver messed it up.  This also tests that code path a little. */
	virtio_reset_device(dev);

	/* Acknowledge that we've seen the device. */
	virtio_add_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781417896612&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void virtio_add_status(struct virtio_device *dev, unsigned int status)
{
	might_sleep();
	dev-&amp;gt;config-&amp;gt;set_status(dev, dev-&amp;gt;config-&amp;gt;get_status(dev) | status);
}
EXPORT_SYMBOL_GPL(virtio_add_status);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) DRIVER 상태비트 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DRIVER 상태비트를 설정한다는 건, virtio 드라이버가 그 디바이스를 어떻게 구동할지 알고 있다는 걸 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 virtio 버스의 probe 함수인 virtio_dev_probe에서 virtio_add_status 함수를 호출해 완료한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781417923177&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct bus_type virtio_bus = {
	.name  = &quot;virtio&quot;,
	.match = virtio_dev_match,
	.dev_groups = virtio_dev_groups,
	.uevent = virtio_uevent,
	.probe = virtio_dev_probe,
	.remove = virtio_dev_remove,
};

int register_virtio_driver(struct virtio_driver *driver)
{
	/* Catch this early. */
	BUG_ON(driver-&amp;gt;feature_table_size &amp;amp;&amp;amp; !driver-&amp;gt;feature_table);
	driver-&amp;gt;driver.bus = &amp;amp;virtio_bus;
	return driver_register(&amp;amp;driver-&amp;gt;driver);
}
EXPORT_SYMBOL_GPL(register_virtio_driver);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781417934164&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int virtio_dev_probe(struct device *_d)
{
	int err, i;
	struct virtio_device *dev = dev_to_virtio(_d);
	struct virtio_driver *drv = drv_to_virtio(dev-&amp;gt;dev.driver);
	u64 device_features;
	u64 driver_features;
	u64 driver_features_legacy;

	/* We have a driver! */
	virtio_add_status(dev, VIRTIO_CONFIG_S_DRIVER);

	/* Figure out what features the device supports. */
	device_features = dev-&amp;gt;config-&amp;gt;get_features(dev);

	/* Figure out what features the driver supports. */
	driver_features = 0;
	for (i = 0; i &amp;lt; drv-&amp;gt;feature_table_size; i++) {
		unsigned int f = drv-&amp;gt;feature_table[i];
		BUG_ON(f &amp;gt;= 64);
		driver_features |= (1ULL &amp;lt;&amp;lt; f);
	}

	/* Some drivers have a separate feature table for virtio v1.0 */
	if (drv-&amp;gt;feature_table_legacy) {
		driver_features_legacy = 0;
		for (i = 0; i &amp;lt; drv-&amp;gt;feature_table_size_legacy; i++) {
			unsigned int f = drv-&amp;gt;feature_table_legacy[i];
			BUG_ON(f &amp;gt;= 64);
			driver_features_legacy |= (1ULL &amp;lt;&amp;lt; f);
		}
	} else {
		driver_features_legacy = driver_features;
	}

	if (device_features &amp;amp; (1ULL &amp;lt;&amp;lt; VIRTIO_F_VERSION_1))
		dev-&amp;gt;features = driver_features &amp;amp; device_features;
	else
		dev-&amp;gt;features = driver_features_legacy &amp;amp; device_features;

	/* Transport features always preserved to pass to finalize_features. */
	for (i = VIRTIO_TRANSPORT_F_START; i &amp;lt; VIRTIO_TRANSPORT_F_END; i++)
		if (device_features &amp;amp; (1ULL &amp;lt;&amp;lt; i))
			__virtio_set_bit(dev, i);

	err = dev-&amp;gt;config-&amp;gt;finalize_features(dev);
	if (err)
		goto err;

	if (drv-&amp;gt;validate) {
		u64 features = dev-&amp;gt;features;

		err = drv-&amp;gt;validate(dev);
		if (err)
			goto err;

		/* Did validation change any features? Then write them again. */
		if (features != dev-&amp;gt;features) {
			err = dev-&amp;gt;config-&amp;gt;finalize_features(dev);
			if (err)
				goto err;
		}
	}

	err = virtio_features_ok(dev);
	if (err)
		goto err;

	err = drv-&amp;gt;probe(dev);
	if (err)
		goto err;

	/* If probe didn't do it, mark device DRIVER_OK ourselves. */
	if (!(dev-&amp;gt;config-&amp;gt;get_status(dev) &amp;amp; VIRTIO_CONFIG_S_DRIVER_OK))
		virtio_device_ready(dev);

	if (drv-&amp;gt;scan)
		drv-&amp;gt;scan(dev);

	virtio_config_enable(dev);

	return 0;
err:
	virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
	return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;핵심 코드 조각은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781417952915&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* We have a driver! */
	virtio_add_status(dev, VIRTIO_CONFIG_S_DRIVER);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(4) virtio 디바이스의 feature 비트 읽기, 드라이버가 설정한 feature 구하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 디바이스의 feature 비트를 읽고, 드라이버가 설정한 feature를 구한다. 이것도 위쪽의 virtio_dev_probe 함수에서 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781417976630&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Figure out what features the device supports. */
	device_features = dev-&amp;gt;config-&amp;gt;get_features(dev);

	/* Figure out what features the driver supports. */
	driver_features = 0;
	for (i = 0; i &amp;lt; drv-&amp;gt;feature_table_size; i++) {
		unsigned int f = drv-&amp;gt;feature_table[i];
		BUG_ON(f &amp;gt;= 64);
		driver_features |= (1ULL &amp;lt;&amp;lt; f);
	}

	/* Some drivers have a separate feature table for virtio v1.0 */
	if (drv-&amp;gt;feature_table_legacy) {
		driver_features_legacy = 0;
		for (i = 0; i &amp;lt; drv-&amp;gt;feature_table_size_legacy; i++) {
			unsigned int f = drv-&amp;gt;feature_table_legacy[i];
			BUG_ON(f &amp;gt;= 64);
			driver_features_legacy |= (1ULL &amp;lt;&amp;lt; f);
		}
	} else {
		driver_features_legacy = driver_features;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;feature 협상 다이어그램&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d99TC0/dJMcadoLjek/GgHwBzkyyO7nGykhkSnXEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d99TC0/dJMcadoLjek/GgHwBzkyyO7nGykhkSnXEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d99TC0/dJMcadoLjek/GgHwBzkyyO7nGykhkSnXEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd99TC0%2FdJMcadoLjek%2FGgHwBzkyyO7nGykhkSnXEK%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;708&quot; height=&quot;172&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) 둘(virtio 디바이스의 feature와 드라이버가 설정한 feature)의 부분집합 계산&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘(virtio 디바이스의 feature와 드라이버가 설정한 feature)의 부분집합을 계산한다. 이것도 위쪽의 virtio_dev_probe 함수에서 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418013110&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (device_features &amp;amp; (1ULL &amp;lt;&amp;lt; VIRTIO_F_VERSION_1))
		dev-&amp;gt;features = driver_features &amp;amp; device_features;
	else
		dev-&amp;gt;features = driver_features_legacy &amp;amp; device_features;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(6) 디바이스에 이 부분집합 특성 쓰기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 디바이스의 feature와 드라이버가 설정한 feature의 부분집합을 계산한 후, 디바이스에 이 부분집합 특성을 쓴다. 이것도 위쪽의 virtio_dev_probe 함수에서 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418045023&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Transport features always preserved to pass to finalize_features. */
	for (i = VIRTIO_TRANSPORT_F_START; i &amp;lt; VIRTIO_TRANSPORT_F_END; i++)
		if (device_features &amp;amp; (1ULL &amp;lt;&amp;lt; i))
			__virtio_set_bit(dev, i);

	err = dev-&amp;gt;config-&amp;gt;finalize_features(dev);
	if (err)
		goto err;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;driver_features와 device_features를 계산한 다음에 virtio_finalize_features를 호출한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(7) FEATURES_OK 특성비트 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FEATURES_OK 특성비트를 설정한다. 이 시점 이후로는 virtio 드라이버가 새로운 특성을 더 받아들이지 않게 된다. 이것도 위쪽의 virtio_dev_probe 함수에서 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418071387&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = virtio_features_ok(dev);
	if (err)
		goto err;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 단계는 virtio_features_ok 함수에서 virtio_add_status 함수를 호출해서 완료한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418100689&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Do some validation, then set FEATURES_OK */
static int virtio_features_ok(struct virtio_device *dev)
{
	unsigned int status;

	might_sleep();

	if (virtio_check_mem_acc_cb(dev)) {
		if (!virtio_has_feature(dev, VIRTIO_F_VERSION_1)) {
			dev_warn(&amp;amp;dev-&amp;gt;dev,
				&quot;device must provide VIRTIO_F_VERSION_1\n&quot;);
			return -ENODEV;
		}

		if (!virtio_has_feature(dev, VIRTIO_F_ACCESS_PLATFORM)) {
			dev_warn(&amp;amp;dev-&amp;gt;dev,
				&quot;device must provide VIRTIO_F_ACCESS_PLATFORM\n&quot;);
			return -ENODEV;
		}
	}

	if (!virtio_has_feature(dev, VIRTIO_F_VERSION_1))
		return 0;

	virtio_add_status(dev, VIRTIO_CONFIG_S_FEATURES_OK);
	status = dev-&amp;gt;config-&amp;gt;get_status(dev);
	if (!(status &amp;amp; VIRTIO_CONFIG_S_FEATURES_OK)) {
		dev_err(&amp;amp;dev-&amp;gt;dev, &quot;virtio: device refuses features: %x\n&quot;,
			status);
		return -ENODEV;
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위에서 언급한 핵심 코드 조각은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418117692&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_add_status(dev, VIRTIO_CONFIG_S_FEATURES_OK);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(8) 디바이스의 feature 비트 다시 읽기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스의 feature 비트를 다시 읽어서, VIRTIO_CONFIG_S_FEATURES_OK가 설정됐는지 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418134890&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;status = dev-&amp;gt;config-&amp;gt;get_status(dev);
	if (!(status &amp;amp; VIRTIO_CONFIG_S_FEATURES_OK)) {
		dev_err(&amp;amp;dev-&amp;gt;dev, &quot;virtio: device refuses features: %x\n&quot;,
			status);
		return -ENODEV;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;만약 설정에 성공하지 못했다면, 디바이스가 virtio 드라이버가 설정한 일부 상태를 지원하지 않는 거고, 디바이스를 쓸 수 없다는 뜻이다. 이것도 마찬가지로 virtio_features_ok 함수에서 완료된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(9) 디바이스 관련 초기화 작업 수행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스 관련 초기화 작업을 수행한다. 디바이스의 virtqueue 발견, virtio 디바이스의 설정 공간 읽기/쓰기 등이 포함된다. 이것들은 모두 virtio_dev_probe 함수에서 드라이버의 probe 함수(즉 drv-&amp;gt;probe(dev))를 호출해 완료한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418161935&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = drv-&amp;gt;probe(dev);
	if (err)
		goto err;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio balloon의 경우에는 virtballoon_probe 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418177445&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int virtballoon_probe(struct virtio_device *vdev)
{
	struct virtio_balloon *vb;
	int err;

	if (!vdev-&amp;gt;config-&amp;gt;get) {
		dev_err(&amp;amp;vdev-&amp;gt;dev, &quot;%s failure: config access disabled\n&quot;,
			__func__);
		return -EINVAL;
	}

	vdev-&amp;gt;priv = vb = kzalloc(sizeof(*vb), GFP_KERNEL);
	if (!vb) {
		err = -ENOMEM;
		goto out;
	}

	INIT_WORK(&amp;amp;vb-&amp;gt;update_balloon_stats_work, update_balloon_stats_func);
	INIT_WORK(&amp;amp;vb-&amp;gt;update_balloon_size_work, update_balloon_size_func);
	spin_lock_init(&amp;amp;vb-&amp;gt;stop_update_lock);
	mutex_init(&amp;amp;vb-&amp;gt;balloon_lock);
	init_waitqueue_head(&amp;amp;vb-&amp;gt;acked);
	vb-&amp;gt;vdev = vdev;

	balloon_devinfo_init(&amp;amp;vb-&amp;gt;vb_dev_info);

	err = init_vqs(vb);
	if (err)
		goto out_free_vb;

#ifdef CONFIG_BALLOON_COMPACTION
	vb-&amp;gt;vb_dev_info.migratepage = virtballoon_migratepage;
#endif
	if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
		/*
		 * There is always one entry reserved for cmd id, so the ring
		 * size needs to be at least two to report free page hints.
		 */
		if (virtqueue_get_vring_size(vb-&amp;gt;free_page_vq) &amp;lt; 2) {
			err = -ENOSPC;
			goto out_del_vqs;
		}
		vb-&amp;gt;balloon_wq = alloc_workqueue(&quot;balloon-wq&quot;,
					WQ_FREEZABLE | WQ_CPU_INTENSIVE, 0);
		if (!vb-&amp;gt;balloon_wq) {
			err = -ENOMEM;
			goto out_del_vqs;
		}
		INIT_WORK(&amp;amp;vb-&amp;gt;report_free_page_work, report_free_page_func);
		vb-&amp;gt;cmd_id_received_cache = VIRTIO_BALLOON_CMD_ID_STOP;
		vb-&amp;gt;cmd_id_active = cpu_to_virtio32(vb-&amp;gt;vdev,
						VIRTIO_BALLOON_CMD_ID_STOP);
		vb-&amp;gt;cmd_id_stop = cpu_to_virtio32(vb-&amp;gt;vdev,
						VIRTIO_BALLOON_CMD_ID_STOP);
		spin_lock_init(&amp;amp;vb-&amp;gt;free_page_list_lock);
		INIT_LIST_HEAD(&amp;amp;vb-&amp;gt;free_page_list);
		/*
		 * We're allowed to reuse any free pages, even if they are
		 * still to be processed by the host.
		 */
		err = virtio_balloon_register_shrinker(vb);
		if (err)
			goto out_del_balloon_wq;
	}

	if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM)) {
		vb-&amp;gt;oom_nb.notifier_call = virtio_balloon_oom_notify;
		vb-&amp;gt;oom_nb.priority = VIRTIO_BALLOON_OOM_NOTIFY_PRIORITY;
		err = register_oom_notifier(&amp;amp;vb-&amp;gt;oom_nb);
		if (err &amp;lt; 0)
			goto out_unregister_shrinker;
	}

	if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_PAGE_POISON)) {
		/* Start with poison val of 0 representing general init */
		__u32 poison_val = 0;

		/*
		 * Let the hypervisor know that we are expecting a
		 * specific value to be written back in balloon pages.
		 *
		 * If the PAGE_POISON value was larger than a byte we would
		 * need to byte swap poison_val here to guarantee it is
		 * little-endian. However for now it is a single byte so we
		 * can pass it as-is.
		 */
		if (!want_init_on_free())
			memset(&amp;amp;poison_val, PAGE_POISON, sizeof(poison_val));

		virtio_cwrite_le(vb-&amp;gt;vdev,
				 struct virtio_balloon_config,
				 poison_val, &amp;amp;poison_val);
	}

	vb-&amp;gt;pr_dev_info.report = virtballoon_free_page_report;
	if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_REPORTING)) {
		unsigned int capacity;

		capacity = virtqueue_get_vring_size(vb-&amp;gt;reporting_vq);
		if (capacity &amp;lt; PAGE_REPORTING_CAPACITY) {
			err = -ENOSPC;
			goto out_unregister_oom;
		}

		/*
		 * The default page reporting order is @pageblock_order, which
		 * corresponds to 512MB in size on ARM64 when 64KB base page
		 * size is used. The page reporting won't be triggered if the
		 * freeing page can't come up with a free area like that huge.
		 * So we specify the page reporting order to 5, corresponding
		 * to 2MB. It helps to avoid THP splitting if 4KB base page
		 * size is used by host.
		 *
		 * Ideally, the page reporting order is selected based on the
		 * host's base page size. However it needs more work to report
		 * that value. The hard-coded order would be fine currently.
		 */
#if defined(CONFIG_ARM64) &amp;amp;&amp;amp; defined(CONFIG_ARM64_64K_PAGES)
		vb-&amp;gt;pr_dev_info.order = 5;
#endif

		err = page_reporting_register(&amp;amp;vb-&amp;gt;pr_dev_info);
		if (err)
			goto out_unregister_oom;
	}

	virtio_device_ready(vdev);

	if (towards_target(vb))
		virtballoon_changed(vdev);
	return 0;

out_unregister_oom:
	if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM))
		unregister_oom_notifier(&amp;amp;vb-&amp;gt;oom_nb);
out_unregister_shrinker:
	if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
		virtio_balloon_unregister_shrinker(vb);
out_del_balloon_wq:
	if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
		destroy_workqueue(vb-&amp;gt;balloon_wq);
out_del_vqs:
	vdev-&amp;gt;config-&amp;gt;del_vqs(vdev);
out_free_vb:
	kfree(vb);
out:
	return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(10) DRIVER_OK 상태비트 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DRIVER_OK 상태비트를 설정한다. 이건 보통 구체적인 디바이스 드라이버의 probe 함수에서 virtio_device_ready 함수를 호출해 완료한다. virtio balloon 디바이스로 말하자면, 바로 위쪽의 virtballoon_probe 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418223497&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_device_ready(vdev);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781418232999&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * virtio_device_ready - enable vq use in probe function
 * @dev: the virtio device
 *
 * Driver must call this to use vqs in the probe function.
 *
 * Note: vqs are enabled automatically after probe returns.
 */
static inline
void virtio_device_ready(struct virtio_device *dev)
{
	unsigned status = dev-&amp;gt;config-&amp;gt;get_status(dev);

	WARN_ON(status &amp;amp; VIRTIO_CONFIG_S_DRIVER_OK);

#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
	/*
	 * The virtio_synchronize_cbs() makes sure vring_interrupt()
	 * will see the driver specific setup if it sees vq-&amp;gt;broken
	 * as false (even if the notifications come before DRIVER_OK).
	 */
	virtio_synchronize_cbs(dev);
	__virtio_unbreak_device(dev);
#endif
	/*
	 * The transport should ensure the visibility of vq-&amp;gt;broken
	 * before setting DRIVER_OK. See the comments for the transport
	 * specific set_status() method.
	 *
	 * A well behaved device will only notify a virtqueue after
	 * DRIVER_OK, this means the device should &quot;see&quot; the coherent
	 * memory write that set vq-&amp;gt;broken as false which is done by
	 * the driver when it sees DRIVER_OK, then the following
	 * driver's vring_interrupt() will see vq-&amp;gt;broken as false so
	 * we won't lose any notification.
	 */
	dev-&amp;gt;config-&amp;gt;set_status(dev, status | VIRTIO_CONFIG_S_DRIVER_OK);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;만약 디바이스 드라이버가 DRIVER_OK 상태비트를 설정하지 않았다면, 버스의 probe 함수인 virtio_dev_probe 함수에서 설정해준다. 이것도 위쪽의 virtio_dev_probe 함수에서 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418259820&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* If probe didn't do it, mark device DRIVER_OK ourselves. */
	if (!(dev-&amp;gt;config-&amp;gt;get_status(dev) &amp;amp; VIRTIO_CONFIG_S_DRIVER_OK))
		virtio_device_ready(dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio 드라이버 초기화 - virtio_pci_config_ops&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 virtio 드라이버의 로딩을 확인해보았다. 지금부터는 virtio 드라이버의 초기화를 확인해보자.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 드라이버의 초기화를 설명하기 전에, 먼저 virtio 설정의 함수 집합 변수인 virtio_pci_config_ops를 봐야 한다.&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;virtio 관련 구조체 관계도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJHVj/dJMcaa6KXBi/8ZBx8Gk1zG6qoMlS5hK6kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJHVj/dJMcaa6KXBi/8ZBx8Gk1zG6qoMlS5hK6kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJHVj/dJMcaa6KXBi/8ZBx8Gk1zG6qoMlS5hK6kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJHVj%2FdJMcaa6KXBi%2F8ZBx8Gk1zG6qoMlS5hK6kk%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;699&quot; height=&quot;547&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_config_ops의 초기화는 두 군데에 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;drivers/virtio/virtio_pci_legacy.c&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;drivers/virtio/virtio_pci_modern.c&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;legacy&lt;/p&gt;
&lt;pre id=&quot;code_1781418387321&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const struct virtio_config_ops virtio_pci_config_ops = {
	.get		= vp_get,
	.set		= vp_set,
	.get_status	= vp_get_status,
	.set_status	= vp_set_status,
	.reset		= vp_reset,
	.find_vqs	= vp_find_vqs,
	.del_vqs	= vp_del_vqs,
	.synchronize_cbs = vp_synchronize_vectors,
	.get_features	= vp_get_features,
	.finalize_features = vp_finalize_features,
	.bus_name	= vp_bus_name,
	.set_vq_affinity = vp_set_vq_affinity,
	.get_vq_affinity = vp_get_vq_affinity,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;modern&lt;/p&gt;
&lt;pre id=&quot;code_1781418400746&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const struct virtio_config_ops virtio_pci_config_ops = {
	.get		= vp_get,
	.set		= vp_set,
	.generation	= vp_generation,
	.get_status	= vp_get_status,
	.set_status	= vp_set_status,
	.reset		= vp_reset,
	.find_vqs	= vp_modern_find_vqs,
	.del_vqs	= vp_del_vqs,
	.synchronize_cbs = vp_synchronize_vectors,
	.get_features	= vp_get_features,
	.finalize_features = vp_finalize_features,
	.bus_name	= vp_bus_name,
	.set_vq_affinity = vp_set_vq_affinity,
	.get_vq_affinity = vp_get_vq_affinity,
	.get_shm_region  = vp_get_shm_region,
	.disable_vq_and_reset = vp_modern_disable_vq_and_reset,
	.enable_vq_after_reset = vp_modern_enable_vq_after_reset,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 drivers/virtio/virtio_pci_modern.c의 virtio_pci_config_ops를 분석한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418449077&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* the PCI probing function */
int virtio_pci_modern_probe(struct virtio_device *vp_dev)
{
	struct virtio_pci_modern_device *mdev = &amp;amp;vp_dev-&amp;gt;mdev;
	struct pci_dev *pci_dev = vp_dev-&amp;gt;pci_dev;
	int err;

	mdev-&amp;gt;pci_dev = pci_dev;

	err = vp_modern_probe(mdev);
	if (err)
		return err;

	if (mdev-&amp;gt;device)
		vp_dev-&amp;gt;vdev.config = &amp;amp;virtio_pci_config_ops;
	else
		vp_dev-&amp;gt;vdev.config = &amp;amp;virtio_pci_config_nodev_ops;

	vp_dev-&amp;gt;config_vector = vp_config_vector;
	vp_dev-&amp;gt;setup_vq = setup_vq;
	vp_dev-&amp;gt;del_vq = del_vq;
	vp_dev-&amp;gt;isr = mdev-&amp;gt;isr;
	vp_dev-&amp;gt;vdev.id = mdev-&amp;gt;id;

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_config_ops 변수는 virtio_device 구조체의 config 멤버에 대입된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418466703&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * struct virtio_device - representation of a device using virtio
 * @index: unique position on the virtio bus
 * @failed: saved value for VIRTIO_CONFIG_S_FAILED bit (for restore)
 * @config_enabled: configuration change reporting enabled
 * @config_change_pending: configuration change reported while disabled
 * @config_lock: protects @vqs.
 * @vqs_list_lock: protects @vqs.
 * @dev: underlying device.
 * @id: the device type identification (used to match it with a driver).
 * @config: the configuration ops for this device.
 * @vringh_config: configuration ops for host vrings.
 * @vqs: the list of virtqueues for this device.
 * @features: the features supported by both driver and device.
 * @priv: private pointer for the driver's use.
 */
struct virtio_device {
	int index;
	bool failed;
	bool config_enabled;
	bool config_change_pending;
	spinlock_t config_lock;
	spinlock_t vqs_list_lock;
	struct device dev;
	struct virtio_device_id id;
	const struct virtio_config_ops *config;
	const struct vringh_config_ops *vringh_config;
	struct list_head vqs;
	u64 features;
	void *priv;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781418479700&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * struct virtio_config_ops - operations for configuring a virtio device
 * Note: Do not assume that a transport implements all of the operations
 *       getting/setting a value as a simple read/write! Generally speaking,
 *       any of @get/@set, @get_status/@set_status, or @get_features/
 *       @finalize_features are NOT safe to be called from an atomic
 *       context.
 * @get: read the value of a configuration field
 *	vdev: the virtio_device
 *	offset: the offset of the configuration field
 *	buf: the buffer to write the field value into.
 *	len: the length of the buffer
 * @set: write the value of a configuration field
 *	vdev: the virtio_device
 *	offset: the offset of the configuration field
 *	buf: the buffer to read the field value from.
 *	len: the length of the buffer
 * @generation: config generation counter (optional)
 *	vdev: the virtio_device
 *	Returns the config generation counter
 * @get_status: read the status byte
 *	vdev: the virtio_device
 *	Returns the status byte
 * @set_status: write the status byte
 *	vdev: the virtio_device
 *	status: the new status byte
 * @reset: reset the device
 *	vdev: the virtio_device
 *	After this, status and feature negotiation must be done again
 *	Device must not be reset from its vq/config callbacks, or in
 *	parallel with being added/removed.
 * @find_vqs: find virtqueues and instantiate them.
 *	vdev: the virtio_device
 *	nvqs: the number of virtqueues to find
 *	vqs: on success, includes new virtqueues
 *	callbacks: array of callbacks, for each virtqueue
 *		include a NULL entry for vqs that do not need a callback
 *	names: array of virtqueue names (mainly for debugging)
 *		include a NULL entry for vqs unused by driver
 *	Returns 0 on success or error status
 * @del_vqs: free virtqueues found by find_vqs().
 * @synchronize_cbs: synchronize with the virtqueue callbacks (optional)
 *      The function guarantees that all memory operations on the
 *      queue before it are visible to the vring_interrupt() that is
 *      called after it.
 *      vdev: the virtio_device
 * @get_features: get the array of feature bits for this device.
 *	vdev: the virtio_device
 *	Returns the first 64 feature bits (all we currently need).
 * @finalize_features: confirm what device features we'll be using.
 *	vdev: the virtio_device
 *	This sends the driver feature bits to the device: it can change
 *	the dev-&amp;gt;feature bits if it wants.
 *	Note that despite the name this can be called any number of
 *	times.
 *	Returns 0 on success or error status
 * @bus_name: return the bus name associated with the device (optional)
 *	vdev: the virtio_device
 *      This returns a pointer to the bus name a la pci_name from which
 *      the caller can then copy.
 * @set_vq_affinity: set the affinity for a virtqueue (optional).
 * @get_vq_affinity: get the affinity for a virtqueue (optional).
 * @get_shm_region: get a shared memory region based on the index.
 * @disable_vq_and_reset: reset a queue individually (optional).
 *	vq: the virtqueue
 *	Returns 0 on success or error status
 *	disable_vq_and_reset will guarantee that the callbacks are disabled and
 *	synchronized.
 *	Except for the callback, the caller should guarantee that the vring is
 *	not accessed by any functions of virtqueue.
 * @enable_vq_after_reset: enable a reset queue
 *	vq: the virtqueue
 *	Returns 0 on success or error status
 *	If disable_vq_and_reset is set, then enable_vq_after_reset must also be
 *	set.
 */
struct virtio_config_ops {
	void (*get)(struct virtio_device *vdev, unsigned offset,
		    void *buf, unsigned len);
	void (*set)(struct virtio_device *vdev, unsigned offset,
		    const void *buf, unsigned len);
	u32 (*generation)(struct virtio_device *vdev);
	u8 (*get_status)(struct virtio_device *vdev);
	void (*set_status)(struct virtio_device *vdev, u8 status);
	void (*reset)(struct virtio_device *vdev);
	int (*find_vqs)(struct virtio_device *, unsigned nvqs,
			struct virtqueue *vqs[], vq_callback_t *callbacks[],
			const char * const names[], const bool *ctx,
			struct irq_affinity *desc);
	void (*del_vqs)(struct virtio_device *);
	void (*synchronize_cbs)(struct virtio_device *);
	u64 (*get_features)(struct virtio_device *vdev);
	int (*finalize_features)(struct virtio_device *vdev);
	const char *(*bus_name)(struct virtio_device *vdev);
	int (*set_vq_affinity)(struct virtqueue *vq,
			       const struct cpumask *cpu_mask);
	const struct cpumask *(*get_vq_affinity)(struct virtio_device *vdev,
			int index);
	bool (*get_shm_region)(struct virtio_device *vdev,
			       struct virtio_shm_region *region, u8 id);
	int (*disable_vq_and_reset)(struct virtqueue *vq);
	int (*enable_vq_after_reset)(struct virtqueue *vq);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;drivers/virtio/virtio_pci_modern.c의 virtio_pci_config_ops와 struct virtio_config_ops의 정의와 대조해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_config_ops 구조체의 멤버 함수들은 보통 virtio PCI proxy 디바이스의 I/O 동작이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio PCI proxy 디바이스의 PIO와 MMIO 읽기/쓰기가 포함된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 get_status와 set_status 멤버에 대응되는 vp_get_status 함수와 vp_set_status 함수가 있다. 하나씩 보자.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;get_status&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct virtio_config_ops의 설명에 따르면&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;@get_status: read the status byte&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vdev: the virtio_device&lt;/li&gt;
&lt;li&gt;Returns the status byte&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get_status의 역할은 상태 바이트를 읽는 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터가 하나 있는데, vdev는 virtio device를 나타낸다. 리턴값은 읽어낸 상태 바이트다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418627618&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* config-&amp;gt;{get,set}_status() implementations */
static u8 vp_get_status(struct virtio_device *vdev)
{
	struct virtio_pci_device *vp_dev = to_vp_device(vdev);

	return vp_modern_get_status(&amp;amp;vp_dev-&amp;gt;mdev);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781418637384&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_get_status - get the device status
 * @mdev: the modern virtio-pci device
 *
 * Returns the status read from device
 */
u8 vp_modern_get_status(struct virtio_pci_modern_device *mdev)
{
	struct virtio_pci_common_cfg __iomem *cfg = mdev-&amp;gt;common;

	return vp_ioread8(&amp;amp;cfg-&amp;gt;device_status);
}
EXPORT_SYMBOL_GPL(vp_modern_get_status);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;set_status&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@set_status: write the status byte&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vdev: the virtio_device&lt;/li&gt;
&lt;li&gt;status: the new status byte&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set_status의 역할은 상태 바이트를 쓰는 거다. &lt;br /&gt;파라미터가 둘 있는데, vdev는 virtio device를 나타내고, status는 새로 써넣을 상태 바이트다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418668703&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void vp_set_status(struct virtio_device *vdev, u8 status)
{
	struct virtio_pci_device *vp_dev = to_vp_device(vdev);

	/* We should never be setting status to 0. */
	BUG_ON(status == 0);
	vp_modern_set_status(&amp;amp;vp_dev-&amp;gt;mdev, status);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781418678776&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_set_status - set status to device
 * @mdev: the modern virtio-pci device
 * @status: the status set to device
 */
void vp_modern_set_status(struct virtio_pci_modern_device *mdev,
				 u8 status)
{
	struct virtio_pci_common_cfg __iomem *cfg = mdev-&amp;gt;common;

	/*
	 * Per memory-barriers.txt, wmb() is not needed to guarantee
	 * that the cache coherent memory writes have completed
	 * before writing to the MMIO region.
	 */
	vp_iowrite8(status, &amp;amp;cfg-&amp;gt;device_status);
}
EXPORT_SYMBOL_GPL(vp_modern_set_status);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_dev-&amp;gt;common과 QEMU의 관계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vp_modern_get_status와 vp_modern_set_status 함수는 직접 vp_dev-&amp;gt;mdev-&amp;gt;common-&amp;gt;device_status를 읽고 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 알 수 있듯이, vp_dev-&amp;gt;common에 대응되는 건 virtio PCI proxy 디바이스의 네 번째 BAR가 표시하는 주소 안의 한 구간이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418726656&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
    VirtioPCIClass *k = VIRTIO_PCI_GET_CLASS(pci_dev);
    bool pcie_port = pci_bus_is_express(pci_get_bus(pci_dev)) &amp;amp;&amp;amp;
                        !pci_bus_is_root(pci_get_bus(pci_dev));

    if (kvm_enabled() &amp;amp;&amp;amp; !kvm_has_many_ioeventfds()) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /* fd-based ioevents can't be synchronized in record/replay */
    if (replay_mode != REPLAY_MODE_NONE) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /*
     * virtio pci bar layout used by default.
     * subclasses can re-arrange things if needed.
     *
     *   region 0   --  virtio legacy io bar
     *   region 1   --  msi-x bar
     *   region 2   --  virtio modern io bar (off by default)
     *   region 4+5 --  virtio modern memory (64bit) bar
     *
     */
    proxy-&amp;gt;legacy_io_bar_idx  = 0;
    proxy-&amp;gt;msix_bar_idx       = 1;
    proxy-&amp;gt;modern_io_bar_idx  = 2;
    proxy-&amp;gt;modern_mem_bar_idx = 4;   // &amp;larr; 여기가 핵심 (네 번째 BAR)

    proxy-&amp;gt;common.offset = 0x0;
    proxy-&amp;gt;common.size = 0x1000;
    proxy-&amp;gt;common.type = VIRTIO_PCI_CAP_COMMON_CFG;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;PCI 설정 공간과 BAR 구조&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3720&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkWOML/dJMcagMGd7Q/pvYh7GL8bh41jKuskUkln0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkWOML/dJMcagMGd7Q/pvYh7GL8bh41jKuskUkln0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkWOML/dJMcagMGd7Q/pvYh7GL8bh41jKuskUkln0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkWOML%2FdJMcagMGd7Q%2FpvYh7GL8bh41jKuskUkln0%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;3720&quot; height=&quot;830&quot; data-origin-width=&quot;3720&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_dev-&amp;gt;mdev-&amp;gt;common의 타입은 struct virtio_pci_common_cfg이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781418753658&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
struct virtio_pci_common_cfg {
	/* About the whole device. */
	__le32 device_feature_select;	/* read-write */
	__le32 device_feature;		/* read-only */
	__le32 guest_feature_select;	/* read-write */
	__le32 guest_feature;		/* read-write */
	__le16 msix_config;		/* read-write */
	__le16 num_queues;		/* read-only */
	__u8 device_status;		/* read-write */
	__u8 config_generation;		/* read-only */

	/* About a specific virtqueue. */
	__le16 queue_select;		/* read-write */
	__le16 queue_size;		/* read-write, power of 2. */
	__le16 queue_msix_vector;	/* read-write */
	__le16 queue_enable;		/* read-write */
	__le16 queue_notify_off;	/* read-only */
	__le32 queue_desc_lo;		/* read-write */
	__le32 queue_desc_hi;		/* read-write */
	__le32 queue_avail_lo;		/* read-write */
	__le32 queue_avail_hi;		/* read-write */
	__le32 queue_used_lo;		/* read-write */
	__le32 queue_used_hi;		/* read-write */
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;struct virtio_pci_common_cfg의 각 멤버는 virtio PCI proxy 디바이스의 modern MMIO 주소 공간에서 대응되는 값을 나타낸다. 이 멤버들을 읽고 쓰면 QEMU로 trap된다.(실행흐름이 넘어간다.)&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 위의 디바이스 상태를 읽거나 설정하는 device_status 멤버는 그 주소가 virtio_pci_common_cfg 구조체 시작점에서 20바이트 위치(4+4+4+4+2+2=20)에 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이 주소를 읽고 쓸 때 QEMU로 트랩되고, 그 주소는 또 virtio 디바이스의 common MemoryRegion의 20바이트 위치다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 MemoryRegion에 대응되는 콜백 동작 구조는 common_ops이고, 타입은 MemoryRegionOps다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_common_cfg 오프셋 다이어그램&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2759&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzguFW/dJMcagMGd7U/s0BSPadnetW6rQQTnKx4EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzguFW/dJMcagMGd7U/s0BSPadnetW6rQQTnKx4EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzguFW/dJMcagMGd7U/s0BSPadnetW6rQQTnKx4EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzguFW%2FdJMcagMGd7U%2Fs0BSPadnetW6rQQTnKx4EK%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;2759&quot; height=&quot;320&quot; data-origin-width=&quot;2759&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;게스트 드라이버 &amp;rarr; QEMU trap 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZCciM/dJMcahSf2rm/Ltm7zuvscA5K6jw4DdVABK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZCciM/dJMcahSf2rm/Ltm7zuvscA5K6jw4DdVABK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZCciM/dJMcahSf2rm/Ltm7zuvscA5K6jw4DdVABK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZCciM%2FdJMcahSf2rm%2FLtm7zuvscA5K6jw4DdVABK%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;1361&quot; height=&quot;537&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781419074948&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_modern_regions_init(VirtIOPCIProxy *proxy,
                                           const char *vdev_name)
{
    static const MemoryRegionOps common_ops = {
        .read = virtio_pci_common_read,
        .write = virtio_pci_common_write,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 4,
        },
        .endianness = DEVICE_LITTLE_ENDIAN,
    };
    &amp;hellip;&amp;hellip;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_config_ops의 각 함수들이 이런 I/O 동작을 캡슐화한 거다. MMIO 동작뿐만 아니라 PIO 동작도 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 디바이스는 이 구조 안의 각 콜백 함수를 통해 디바이스를 구동할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;1742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nX1pH/dJMcajoXhn3/Tjo7vCC216l2euYnuXXgZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nX1pH/dJMcajoXhn3/Tjo7vCC216l2euYnuXXgZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nX1pH/dJMcajoXhn3/Tjo7vCC216l2euYnuXXgZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnX1pH%2FdJMcajoXhn3%2FTjo7vCC216l2euYnuXXgZ1%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;273&quot; height=&quot;1219&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;1742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;※ virtio_pci_device_plugged에서 게스트 드라이버 로딩으로의 전환&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;앞에서는 각 virtio 장치가 모두 대응하는 virtio PCI proxy 장치를 가지는걸 확인했고, 지금부터는 가상머신 내부 운영체제가 어떻게 virtio PCI proxy 장치와 virtio 장치 드라이버를 로딩하는지, 그리고 어떻게 virtio 장치와 통신하는지를 분석한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio PCI proxy 장치의 존재 때문에, PCI가 스캔을 진행할 때 이 장치를 스캔해내고, 게다가 관련된 probe 함수를 호출하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnQD60/dJMcaiDGjqj/9ahTYh2I5lU3DKjwrlpE7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnQD60/dJMcaiDGjqj/9ahTYh2I5lU3DKjwrlpE7K/img.png&quot; data-alt=&quot;QEMU에서 게스트 커널로&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnQD60/dJMcaiDGjqj/9ahTYh2I5lU3DKjwrlpE7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnQD60%2FdJMcaiDGjqj%2F9ahTYh2I5lU3DKjwrlpE7K%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;676&quot; height=&quot;302&quot; data-origin-width=&quot;1191&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;QEMU에서 게스트 커널로&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781419306364&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct pci_driver virtio_pci_driver = {
    .name        = &quot;virtio-pci&quot;,
    .id_table    = virtio_pci_id_table,
    .probe       = virtio_pci_probe,
    .remove      = virtio_pci_remove,
#ifdef CONFIG_PM_SLEEP
    .driver.pm   = &amp;amp;virtio_pci_pm_ops,
#endif
    .sriov_configure = virtio_pci_sriov_configure,
};

module_pci_driver(virtio_pci_driver);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781419317786&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int virtio_pci_probe(struct pci_dev *pci_dev,
                            const struct pci_device_id *id)
{
    struct virtio_pci_device *vp_dev, *reg_dev = NULL;
    int rc;

    /* allocate our structure and fill it out */
    vp_dev = kzalloc(sizeof(struct virtio_pci_device), GFP_KERNEL);
    if (!vp_dev)
        return -ENOMEM;

    pci_set_drvdata(pci_dev, vp_dev);
    vp_dev-&amp;gt;vdev.dev.parent = &amp;amp;pci_dev-&amp;gt;dev;
    vp_dev-&amp;gt;vdev.dev.release = virtio_pci_release_dev;
    vp_dev-&amp;gt;pci_dev = pci_dev;
    INIT_LIST_HEAD(&amp;amp;vp_dev-&amp;gt;virtqueues);
    spin_lock_init(&amp;amp;vp_dev-&amp;gt;lock);

    /* enable the device */
    rc = pci_enable_device(pci_dev);
    if (rc)
        goto err_enable_device;

    if (force_legacy) {
        rc = virtio_pci_legacy_probe(vp_dev);
        /* Also try modern mode if we can't map BAR0 (no IO space). */
        if (rc == -ENODEV || rc == -ENOMEM)
            goto err_probe;
        if (rc) {
            rc = virtio_pci_modern_probe(vp_dev);
            if (rc)
                goto err_probe;
        }
    } else {
        rc = virtio_pci_modern_probe(vp_dev);
        if (rc == -ENODEV)
            rc = virtio_pci_legacy_probe(vp_dev);
        if (rc)
            goto err_probe;
    }

    pci_set_master(pci_dev);

    vp_dev-&amp;gt;is_legacy = vp_dev-&amp;gt;ldev.ioaddr ? true : false;

    rc = register_virtio_device(&amp;amp;vp_dev-&amp;gt;vdev);
    reg_dev = vp_dev;
    if (rc)
        goto err_register;

    return 0;

err_register:
    if (vp_dev-&amp;gt;is_legacy)
        virtio_pci_legacy_remove(vp_dev);
    else
        virtio_pci_modern_remove(vp_dev);
err_probe:
    pci_disable_device(pci_dev);
err_enable_device:
    if (reg_dev)
        put_device(&amp;amp;vp_dev-&amp;gt;vdev.dev);
    else
        kfree(vp_dev);
    return rc;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_probe 함수 전체 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;1420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WqTtS/dJMcabxKUGU/2hcUkkmLZ4xau8U9uZy6S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WqTtS/dJMcabxKUGU/2hcUkkmLZ4xau8U9uZy6S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WqTtS/dJMcabxKUGU/2hcUkkmLZ4xau8U9uZy6S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWqTtS%2FdJMcabxKUGU%2F2hcUkkmLZ4xau8U9uZy6S1%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;409&quot; height=&quot;1039&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;1420&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) virtio_pci_probe 함수는 virtio_pci_device 구조체 인스턴스 하나를 할당하고 vp_dev에 값을 대입한다. virtio PCI proxy 장치 하나를 표시하는 데 사용한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781419354859&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_pci_device *vp_dev, *reg_dev = NULL;
......
/* allocate our structure and fill it out */
vp_dev = kzalloc(sizeof(struct virtio_pci_device), GFP_KERNEL);
if (!vp_dev)
    return -ENOMEM;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781419362442&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Our device structure */
struct virtio_pci_device {
    struct virtio_device vdev;
    struct pci_dev *pci_dev;
    struct virtio_pci_legacy_device ldev;
    struct virtio_pci_modern_device mdev;

    bool is_legacy;

    /* Where to read and clear interrupt */
    u8 __iomem *isr;

    /* a list of queues so we can dispatch IRQs */
    spinlock_t lock;
    struct list_head virtqueues;

    /* array of all queues for house-keeping */
    struct virtio_pci_vq_info **vqs;

    /* MSI-X support */
    int msix_enabled;
    int intx_enabled;
    cpumask_var_t *msix_affinity_masks;
    /* Name strings for interrupts. This size should be enough,
     * and I'm too lazy to allocate each name separately. */
    char (*msix_names)[256];
    /* Number of available vectors */
    unsigned msix_vectors;
    /* Vectors allocated, excluding per-vq vectors if any */
    unsigned msix_used_vectors;

    /* Whether we have vector per vq */
    bool per_vq_vectors;

    struct virtqueue *(*setup_vq)(struct virtio_pci_device *vp_dev,
                                  struct virtio_pci_vq_info *info,
                                  unsigned int idx,
                                  void (*callback)(struct virtqueue *vq),
                                  const char *name,
                                  bool ctx,
                                  u16 msix_vec);
    void (*del_vq)(struct virtio_pci_vq_info *info);

    u16 (*config_vector)(struct virtio_pci_device *vp_dev, u16 vector);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_device 구조체 구성도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2427&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cheZwi/dJMcadoLjgS/g1WNvnTf0vvflLMt3oTlx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cheZwi/dJMcadoLjgS/g1WNvnTf0vvflLMt3oTlx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cheZwi/dJMcadoLjgS/g1WNvnTf0vvflLMt3oTlx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcheZwi%2FdJMcadoLjgS%2Fg1WNvnTf0vvflLMt3oTlx0%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;2427&quot; height=&quot;294&quot; data-origin-width=&quot;2427&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) vp_dev를 그 pci_dev의 사적 구조에 설정한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781419393073&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pci_set_drvdata(pci_dev, vp_dev);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781419402199&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline void pci_set_drvdata(struct pci_dev *pdev, void *data)
{
    dev_set_drvdata(&amp;amp;pdev-&amp;gt;dev, data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781419411533&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline void dev_set_drvdata(struct device *dev, void *data)
{
    dev-&amp;gt;driver_data = data;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합하면&lt;/p&gt;
&lt;pre id=&quot;code_1781419425511&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pci_set_drvdata(pci_dev, vp_dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전개하면 실제로는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781419438801&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(&amp;amp;pci_dev-&amp;gt;dev)-&amp;gt;driver_data = vp_dev;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;pci_set_drvdata 호출 체인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSalD9/dJMcabExbmJ/4rnorpqhkRN5dvaFnni8P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSalD9/dJMcabExbmJ/4rnorpqhkRN5dvaFnni8P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSalD9/dJMcabExbmJ/4rnorpqhkRN5dvaFnni8P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSalD9%2FdJMcabExbmJ%2F4rnorpqhkRN5dvaFnni8P1%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;1202&quot; height=&quot;118&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781419455818&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* The pci_dev structure describes PCI devices */
struct pci_dev {
    struct list_head bus_list;    /* Node in per-bus list */
    struct pci_bus  *bus;         /* Bus this device is on */
    struct pci_bus  *subordinate; /* Bus this device bridges to */

    void        *sysdata;         /* Hook for sys-specific extension */
    struct proc_dir_entry *procent; /* Device entry in /proc/bus/pci */
    struct pci_slot *slot;        /* Physical slot this device is in */

    unsigned int    devfn;        /* Encoded device &amp;amp; function index */
    unsigned short  vendor;
    unsigned short  device;
    unsigned short  subsystem_vendor;
    unsigned short  subsystem_device;
    unsigned int    class;        /* 3 bytes: (base,sub,prog-if) */
    u8              revision;     /* PCI revision, low byte of class word */
    u8              hdr_type;     /* PCI header type (`multi' flag masked out) */
#ifdef CONFIG_PCIEAER
    u16             aer_cap;      /* AER capability offset */
    struct aer_stats *aer_stats;  /* AER stats for this device */
#endif
#ifdef CONFIG_PCIEPORTBUS
    struct rcec_ea  *rcec_ea;     /* RCEC cached endpoint association */
    struct pci_dev  *rcec;        /* Associated RCEC device */
#endif
    u32             devcap;       /* PCIe Device Capabilities */
    u8              pcie_cap;     /* PCIe capability offset */
    u8              msi_cap;      /* MSI capability offset */
    u8              msix_cap;     /* MSI-X capability offset */
    u8              pcie_mpss:3;  /* PCIe Max Payload Size Supported */
    u8              rom_base_reg; /* Config register controlling ROM */
    u8              pin;          /* Interrupt pin this device uses */
    u16             pcie_flags_reg; /* Cached PCIe Capabilities Register */
    unsigned long   *dma_alias_mask;/* Mask of enabled devfn aliases */

    struct pci_driver *driver;    /* Driver bound to this device */
    u64             dma_mask;     /* Mask of the bits of bus address this
                                     device implements.  Normally this is
                                     0xffffffff.  You only need to change
                                     this if your device has broken DMA
                                     or supports 64-bit transfers.  */

    struct device_dma_parameters dma_parms;

    pci_power_t     current_state;/* Current operating state. In ACPI,
                                     this is D0-D3, D0 being fully
                                     functional, and D3 being off. */
    unsigned int    imm_ready:1;  /* Supports Immediate Readiness */
    u8              pm_cap;       /* PM capability offset */
    unsigned int    pme_support:5; /* Bitmask of states from which PME#
                                      can be generated */
    unsigned int    pme_poll:1;   /* Poll device's PME status bit */
    unsigned int    d1_support:1; /* Low power state D1 is supported */
    unsigned int    d2_support:1; /* Low power state D2 is supported */
    unsigned int    no_d1d2:1;    /* D1 and D2 are forbidden */
    unsigned int    no_d3cold:1;  /* D3cold is forbidden */
    unsigned int    bridge_d3:1;  /* Allow D3 for bridge */
    unsigned int    d3cold_allowed:1; /* D3cold is allowed by user */
    unsigned int    mmio_always_on:1; /* Disallow turning off io/mem
                                         decoding during BAR sizing */
    unsigned int    wakeup_prepared:1;
    unsigned int    skip_bus_pm:1; /* Internal: Skip bus-level PM */
    unsigned int    ignore_hotplug:1; /* Ignore hotplug events */
    unsigned int    hotplug_user_indicators:1; /* SlotCtl
                                                  controlled exclusively by
                                                  user sysfs */
    unsigned int    clear_retrain_link:1; /* Need to clear Retrain Link
                                             bit manually */
    unsigned int    d3hot_delay;  /* D3hot-&amp;gt;D0 transition time in ms */
    unsigned int    d3cold_delay; /* D3cold-&amp;gt;D0 transition time in ms */

#ifdef CONFIG_PCIEASPM
    struct pcie_link_state *link_state; /* ASPM link state */
    unsigned int    ltr_path:1;   /* Latency Tolerance Reporting
                                     supported from root to here */
    u16             l1ss;         /* L1SS Capability pointer */
#endif
    unsigned int    pasid_no_tlp:1;     /* PASID works without TLP Prefix */
    unsigned int    eetlp_prefix_path:1; /* End-to-End TLP Prefix */

    pci_channel_state_t error_state; /* Current connectivity state */
    struct device   dev;          /* Generic device interface */

    int             cfg_size;     /* Size of config space */

    /*
     * Instead of touching interrupt line and base address registers
     * directly, use the values stored here. They might be different!
     */
    unsigned int    irq;
    struct resource resource[DEVICE_COUNT_RESOURCE]; /* I/O and memory regions + expansion ROMs */

    bool            match_driver;     /* Skip attaching driver */

    unsigned int    transparent:1;    /* Subtractive decode bridge */
    unsigned int    io_window:1;      /* Bridge has I/O window */
    unsigned int    pref_window:1;    /* Bridge has pref mem window */
    unsigned int    pref_64_window:1; /* Pref mem window is 64-bit */
    unsigned int    multifunction:1;  /* Multi-function device */

    unsigned int    is_busmaster:1;   /* Is busmaster */
    unsigned int    no_msi:1;         /* May not use MSI */
    unsigned int    no_64bit_msi:1;   /* May only use 32-bit MSIs */
    unsigned int    block_cfg_access:1; /* Config space access blocked */
    unsigned int    broken_parity_status:1; /* Generates false positive parity */
    unsigned int    irq_reroute_variant:2;  /* Needs IRQ rerouting variant */
    unsigned int    msi_enabled:1;
    unsigned int    msix_enabled:1;
    unsigned int    ari_enabled:1;    /* ARI forwarding */
    unsigned int    ats_enabled:1;    /* Address Translation Svc */
    unsigned int    pasid_enabled:1;  /* Process Address Space ID */
    unsigned int    pri_enabled:1;    /* Page Request Interface */
    unsigned int    is_managed:1;     /* Managed via devres */
    unsigned int    is_msi_managed:1; /* MSI release via devres installed */
    unsigned int    needs_freset:1;   /* Requires fundamental reset */
    unsigned int    state_saved:1;
    unsigned int    is_physfn:1;
    unsigned int    is_virtfn:1;
    unsigned int    is_hotplug_bridge:1;
    unsigned int    shpc_managed:1;   /* SHPC owned by shpchp */
    unsigned int    is_thunderbolt:1; /* Thunderbolt controller */
    /*
     * Devices marked being untrusted are the ones that can potentially
     * execute DMA attacks and similar. They are typically connected
     * through external ports such as Thunderbolt but not limited to
     * that. When an IOMMU is enabled they should be deemed full
     * mappings to make sure they cannot access arbitrary memory.
     */
    unsigned int    untrusted:1;
    /*
     * Info from the platform, e.g., ACPI or device tree, may mark a
     * device as &quot;external-facing&quot;.  An external-facing device is
     * itself internal but devices downstream from it are external.
     */
    unsigned int    external_facing:1;
    unsigned int    broken_intx_masking:1; /* INTx masking can't be used */
    unsigned int    io_window_1k:1;   /* Intel bridge 1K I/O windows */
    unsigned int    irq_managed:1;
    unsigned int    non_compliant_bars:1; /* Broken BARs; ignore them */
    unsigned int    is_probed:1;      /* Device probing in progress */
    unsigned int    link_active_reporting:1;/* Device capable of reporting link active */
    unsigned int    no_vf_scan:1;     /* Don't scan for VFs after IOV enablement */
    unsigned int    no_command_memory:1; /* No PCI_COMMAND_MEMORY */
    unsigned int    rom_bar_overlap:1; /* ROM BAR disable broken */
    pci_dev_flags_t dev_flags;
    atomic_t        enable_cnt;       /* pci_enable_device has been called */

    u32             saved_config_space[16]; /* Config space saved at suspend time */
    struct hlist_head saved_cap_space;
    int             rom_attr_enabled; /* Display of ROM attribute enabled? */
    struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */
    struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */

#ifdef CONFIG_HOTPLUG_PCI_PCIE
    unsigned int    broken_cmd_compl:1; /* No compl for some cmds */
#endif
#ifdef CONFIG_PCIE_PTM
    u16             ptm_cap;          /* PTM Capability */
    unsigned int    ptm_root:1;
    unsigned int    ptm_enabled:1;
    u8              ptm_granularity;
#endif
#ifdef CONFIG_PCI_MSI
    void __iomem    *msix_base;
    raw_spinlock_t  msi_lock;
#endif
    struct pci_vpd  vpd;
#ifdef CONFIG_PCIE_DPC
    u16             dpc_cap;
    unsigned int    dpc_rp_extensions:1;
    u8              dpc_rp_log_size;
#endif
#ifdef CONFIG_PCI_ATS
    union {
        struct pci_sriov    *sriov;    /* PF: SR-IOV info */
        struct pci_dev      *physfn;   /* VF: related PF */
    };
    u16             ats_cap;          /* ATS Capability offset */
    u8              ats_stu;          /* ATS Smallest Translation Unit */
#endif
#ifdef CONFIG_PCI_PRI
    u16             pri_cap;          /* PRI Capability offset */
    u32             pri_reqs_alloc;   /* Number of PRI requests allocated */
    unsigned int    pasid_required:1; /* PRG Response PASID Required */
#endif
#ifdef CONFIG_PCI_PASID
    u16             pasid_cap;        /* PASID Capability offset */
    u16             pasid_features;
#endif
#ifdef CONFIG_PCI_P2PDMA
    struct pci_p2pdma __rcu *p2pdma;
#endif
    u16             acs_cap;          /* ACS Capability offset */
    phys_addr_t     rom;              /* Physical address if not from BAR */
    size_t          romlen;           /* Length if not from BAR */
    /*
     * Driver name to force a match.  Do not set directly, because core
     * frees it.  Use driver_set_override() to set or clear it.
     */
    const char      *driver_override;

    unsigned long   priv_flags;       /* Private flags for the PCI driver */

    /* These methods index pci_reset_fn_methods[] */
    u8 reset_methods[PCI_NUM_RESET_METHODS]; /* In priority order */
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) vp_dev 즉 virtio_pci_device 구조 안의 virtio_device 타입의 vdev 멤버의 관련 구조를 초기화한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781419487860&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vp_dev-&amp;gt;vdev.dev.parent = &amp;amp;pci_dev-&amp;gt;dev;
vp_dev-&amp;gt;vdev.dev.release = virtio_pci_release_dev;
vp_dev-&amp;gt;pci_dev = pci_dev;
INIT_LIST_HEAD(&amp;amp;vp_dev-&amp;gt;virtqueues);
spin_lock_init(&amp;amp;vp_dev-&amp;gt;lock);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_dev 초기화 후의 관계는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6S3Dx/dJMcaali2FC/rX5NyKUzEe9jNWgl1AGEi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6S3Dx/dJMcaali2FC/rX5NyKUzEe9jNWgl1AGEi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6S3Dx/dJMcaali2FC/rX5NyKUzEe9jNWgl1AGEi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6S3Dx%2FdJMcaali2FC%2FrX5NyKUzEe9jNWgl1AGEi1%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;961&quot; height=&quot;454&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;전체 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2203&quot; data-origin-height=&quot;1301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOV6DN/dJMcah5SYul/PpxuiUOFliZ4sx3NjkcuvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOV6DN/dJMcah5SYul/PpxuiUOFliZ4sx3NjkcuvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOV6DN/dJMcah5SYul/PpxuiUOFliZ4sx3NjkcuvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOV6DN%2FdJMcah5SYul%2FPpxuiUOFliZ4sx3NjkcuvK%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;2203&quot; height=&quot;1301&quot; data-origin-width=&quot;2203&quot; data-origin-height=&quot;1301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;핵심 개념&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bln0xS/dJMcaiwUMCE/WvFpKe2fp3kTaa7dWCo7D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bln0xS/dJMcaiwUMCE/WvFpKe2fp3kTaa7dWCo7D0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bln0xS/dJMcaiwUMCE/WvFpKe2fp3kTaa7dWCo7D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbln0xS%2FdJMcaiwUMCE%2FWvFpKe2fp3kTaa7dWCo7D0%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;1488&quot; height=&quot;532&quot; data-origin-width=&quot;1488&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size23&quot;&gt;2) virtio_pci_probe (게스트 PCI 드라이버 probe)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터는 virtio_pci_probe 함수의 나머지 부분 - pci_enable_device, virtio_pci_modern_probe / virtio_pci_legacy_probe, register_virtio_device 등을 이어서 분석한다.&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;먼저 virtio_pci_probe 함수가 PCI 장치 하나를 받아서 virtio 장치로 등록하기까지의 큰 흐름을 보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blPXgQ/dJMcadoLjg5/ZCck90kcOZojUFKkCwFoD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blPXgQ/dJMcadoLjg5/ZCck90kcOZojUFKkCwFoD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blPXgQ/dJMcadoLjg5/ZCck90kcOZojUFKkCwFoD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblPXgQ%2FdJMcadoLjg5%2FZCck90kcOZojUFKkCwFoD1%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;387&quot; height=&quot;669&quot; data-origin-width=&quot;559&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781419657669&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int virtio_pci_probe(struct pci_dev *pci_dev,
                const struct pci_device_id *id)
{
    struct virtio_pci_device *vp_dev, *reg_dev = NULL;
    int rc;

    /* allocate our structure and fill it out */
    vp_dev = kzalloc(sizeof(struct virtio_pci_device), GFP_KERNEL);
    if (!vp_dev)
        return -ENOMEM;

    pci_set_drvdata(pci_dev, vp_dev);
    vp_dev-&amp;gt;vdev.dev.parent = &amp;amp;pci_dev-&amp;gt;dev;
    vp_dev-&amp;gt;vdev.dev.release = virtio_pci_release_dev;
    vp_dev-&amp;gt;pci_dev = pci_dev;
    INIT_LIST_HEAD(&amp;amp;vp_dev-&amp;gt;virtqueues);
    spin_lock_init(&amp;amp;vp_dev-&amp;gt;lock);

    /* enable the device */
    rc = pci_enable_device(pci_dev);
    if (rc)
        goto err_enable_device;

    if (force_legacy) {
        rc = virtio_pci_legacy_probe(vp_dev);
        /* Also try modern mode if we can't map BAR0 (no IO space). */
        if (rc == -ENODEV || rc == -ENOMEM)
            rc = virtio_pci_modern_probe(vp_dev);
        if (rc)
            goto err_probe;
    } else {
        rc = virtio_pci_modern_probe(vp_dev);
        if (rc == -ENODEV)
            rc = virtio_pci_legacy_probe(vp_dev);
        if (rc)
            goto err_probe;
    }

    pci_set_master(pci_dev);

    vp_dev-&amp;gt;is_legacy = vp_dev-&amp;gt;ldev.ioaddr ? true : false;

    rc = register_virtio_device(&amp;amp;vp_dev-&amp;gt;vdev);
    reg_dev = vp_dev;
    if (rc)
        goto err_register;

    return 0;

err_register:
    if (vp_dev-&amp;gt;is_legacy)
        virtio_pci_legacy_remove(vp_dev);
    else
        virtio_pci_modern_remove(vp_dev);
err_probe:
    pci_disable_device(pci_dev);
err_enable_device:
    if (reg_dev)
        put_device(&amp;amp;vp_dev-&amp;gt;vdev.dev);
    else
        kfree(vp_dev);
    return rc;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) pci_enable_device &amp;mdash; PCI 장치 활성화&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;pci_enable_device 함수를 호출해서 이 PCI 장치를 활성화한다&lt;/p&gt;
&lt;pre id=&quot;code_1781419722403&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* enable the device */
    rc = pci_enable_device(pci_dev);
    if (rc)
        goto err_enable_device;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;pci_dev는 virtio_pci_probe 함수의 첫 번째 인자이고, 구조체 struct pci_dev 타입이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781419749582&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * pci_enable_device - Initialize device before it's used by a driver.
 * @dev: PCI device to be initialized
 *
 * Initialize device before it's used by a driver. Ask low-level code
 * to enable I/O and memory. Wake up the device if it was suspended.
 * Beware, this function can fail.
 *
 * Note we don't actually enable the device many times if we call
 * this function repeatedly (we just increment the count).
 */
int pci_enable_device(struct pci_dev *dev)
{
    return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
EXPORT_SYMBOL(pci_enable_device);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781419759551&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
{
    struct pci_dev *bridge;
    int err;
    int i, bars = 0;

    /*
     * Power state could be unknown at this point, either due to a fresh
     * boot or a device removal call.  So get the current power state
     * so that things like MSI message writing will behave as expected
     * (e.g. if the device really is in D0 at enable time).
     */
    pci_update_current_state(dev, dev-&amp;gt;current_state);

    if (atomic_inc_return(&amp;amp;dev-&amp;gt;enable_cnt) &amp;gt; 1)
        return 0;        /* already enabled */

    bridge = pci_upstream_bridge(dev);
    if (bridge)
        pci_enable_bridge(bridge);

    /* only skip sriov related */
    for (i = 0; i &amp;lt;= PCI_ROM_RESOURCE; i++)
        if (dev-&amp;gt;resource[i].flags &amp;amp; flags)
            bars |= (1 &amp;lt;&amp;lt; i);
    for (i = PCI_BRIDGE_RESOURCES; i &amp;lt; DEVICE_COUNT_RESOURCE; i++)
        if (dev-&amp;gt;resource[i].flags &amp;amp; flags)
            bars |= (1 &amp;lt;&amp;lt; i);

    err = do_pci_enable_device(dev, bars);
    if (err &amp;lt; 0)
        atomic_dec(&amp;amp;dev-&amp;gt;enable_cnt);
    return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) legacy vs modern &amp;mdash; virtio 장치 초기화 분기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 virtio_pci_legacy_probe 또는 virtio_pci_modern_probe 함수를 호출해 이 PCI 장치에 대응하는 virtio 장치를 초기화한다&lt;/p&gt;
&lt;pre id=&quot;code_1781419791787&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (force_legacy) {
        rc = virtio_pci_legacy_probe(vp_dev);
        /* Also try modern mode if we can't map BAR0 (no IO space). */
        if (rc == -ENODEV || rc == -ENOMEM)
            rc = virtio_pci_modern_probe(vp_dev);
        if (rc)
            goto err_probe;
    } else {
        rc = virtio_pci_modern_probe(vp_dev);
        if (rc == -ENODEV)
            rc = virtio_pci_legacy_probe(vp_dev);
        if (rc)
            goto err_probe;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnMzCL/dJMcabdqmWW/MiuzHCRareez6B5TFxZHqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnMzCL/dJMcabdqmWW/MiuzHCRareez6B5TFxZHqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnMzCL/dJMcabdqmWW/MiuzHCRareez6B5TFxZHqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnMzCL%2FdJMcabdqmWW%2FMiuzHCRareez6B5TFxZHqK%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;440&quot; height=&quot;556&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;force_legacy 플래그가 true면 먼저 virtio_pci_legacy_probe 함수를 호출하고, 에러(-ENODEV 또는 -ENOMEM)가 나면 다시 virtio_pci_modern_probe 함수를 호출한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;force_legacy 플래그가 false면 그 반대로, 먼저 virtio_pci_modern_probe 함수를 호출하고 에러(-ENODEV 또는 -ENOMEM)가 나면 다시 virtio_pci_legacy_probe 함수를 호출한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;force_legacy는 모듈 파라미터고, Linux 커널&amp;nbsp;/drivers/virtio/virtio_pci_common.c 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781419844046&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static bool force_legacy = false;

#if IS_ENABLED(CONFIG_VIRTIO_PCI_LEGACY)
module_param(force_legacy, bool, 0444);
MODULE_PARM_DESC(force_legacy,
         &quot;Force legacy mode for transitional virtio 1 devices&quot;);
#endif&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_legacy_probe&lt;/p&gt;
&lt;pre id=&quot;code_1781419867372&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* the PCI probing function */
int virtio_pci_legacy_probe(struct virtio_pci_device *vp_dev)
{
    struct virtio_pci_legacy_device *ldev = &amp;amp;vp_dev-&amp;gt;ldev;
    struct pci_dev *pci_dev = vp_dev-&amp;gt;pci_dev;
    int rc;

    ldev-&amp;gt;pci_dev = pci_dev;

    rc = vp_legacy_probe(ldev);
    if (rc)
        return rc;

    vp_dev-&amp;gt;isr = ldev-&amp;gt;isr;
    vp_dev-&amp;gt;vdev.id = ldev-&amp;gt;id;
    vp_dev-&amp;gt;vdev.config = &amp;amp;virtio_pci_config_ops;
    vp_dev-&amp;gt;config_vector = vp_config_vector;
    vp_dev-&amp;gt;setup_vq = setup_vq;
    vp_dev-&amp;gt;del_vq = del_vq;

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_modern_probe&lt;/p&gt;
&lt;pre id=&quot;code_1781419882245&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* the PCI probing function */
int virtio_pci_modern_probe(struct virtio_pci_device *vp_dev)
{
    struct virtio_pci_modern_device *mdev = &amp;amp;vp_dev-&amp;gt;mdev;
    struct pci_dev *pci_dev = vp_dev-&amp;gt;pci_dev;
    int err;

    mdev-&amp;gt;pci_dev = pci_dev;

    err = vp_modern_probe(mdev);
    if (err)
        return err;

    if (mdev-&amp;gt;device)
        vp_dev-&amp;gt;vdev.config = &amp;amp;virtio_pci_config_ops;
    else
        vp_dev-&amp;gt;vdev.config = &amp;amp;virtio_pci_config_nodev_ops;

    vp_dev-&amp;gt;config_vector = vp_config_vector;
    vp_dev-&amp;gt;setup_vq = setup_vq;
    vp_dev-&amp;gt;del_vq = del_vq;
    vp_dev-&amp;gt;isr = mdev-&amp;gt;isr;
    vp_dev-&amp;gt;vdev.id = mdev-&amp;gt;id;

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781419941631&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (force_legacy) {
        rc = virtio_pci_legacy_probe(vp_dev);
        /* Also try modern mode if we can't map BAR0 (no IO space). */
        if (rc == -ENODEV || rc == -ENOMEM)
            rc = virtio_pci_modern_probe(vp_dev);
        if (rc)
            goto err_probe;
    } else {
        rc = virtio_pci_modern_probe(vp_dev);
        if (rc == -ENODEV)
            rc = virtio_pci_legacy_probe(vp_dev);
        if (rc)
            goto err_probe;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_modern_probe 함수 안에서 가장 핵심은 vp_modern_probe 함수를 호출하는 부분이고, 이건 Linux 커널 소스 drivers/virtio/virtio_pci_modern_dev.c 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781419993172&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_probe: probe the modern virtio pci device, note that the
 * caller is required to enable PCI device before calling this function.
 * @mdev: the modern virtio-pci device
 *
 * Return 0 on succeed otherwise fail
 */
int vp_modern_probe(struct virtio_pci_modern_device *mdev)
{
    struct pci_dev *pci_dev = mdev-&amp;gt;pci_dev;
    int err, common, isr, notify, device;
    u32 notify_length;
    u32 notify_offset;

    check_offsets();

    /* We only own devices &amp;gt;= 0x1000 and &amp;lt;= 0x107f: leave the rest. */
    if (pci_dev-&amp;gt;device &amp;lt; 0x1000 || pci_dev-&amp;gt;device &amp;gt; 0x107f)
        return -ENODEV;

    if (pci_dev-&amp;gt;device &amp;lt; 0x1040) {
        /* Transitional devices: use the PCI subsystem device id as
         * virtio device id, same as legacy driver always did.
         */
        mdev-&amp;gt;id.device = pci_dev-&amp;gt;subsystem_device;
    } else {
        /* Modern devices: simply use PCI device id, but start from 0x1040. */
        mdev-&amp;gt;id.device = pci_dev-&amp;gt;device - 0x1040;
    }
    mdev-&amp;gt;id.vendor = pci_dev-&amp;gt;subsystem_vendor;

    /* check for a common config: if not, use legacy mode (bar 0). */
    common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,
                IORESOURCE_IO | IORESOURCE_MEM,
                &amp;amp;mdev-&amp;gt;modern_bars);
    if (!common) {
        dev_info(&amp;amp;pci_dev-&amp;gt;dev,
             &quot;virtio_pci: leaving for legacy driver\n&quot;);
        return -ENODEV;
    }

    /* If common is there, these should be too... */
    isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);
    notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);
    if (!isr || !notify) {
        dev_err(&amp;amp;pci_dev-&amp;gt;dev,
            &quot;virtio_pci: missing capabilities %i/%i/%i\n&quot;,
            common, isr, notify);
        return -EINVAL;
    }

    err = dma_set_mask_and_coherent(&amp;amp;pci_dev-&amp;gt;dev, DMA_BIT_MASK(64));
    if (err)
        err = dma_set_mask_and_coherent(&amp;amp;pci_dev-&amp;gt;dev,
                        DMA_BIT_MASK(32));
    if (err)
        dev_warn(&amp;amp;pci_dev-&amp;gt;dev, &quot;Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might

    /* Device capability is only mandatory for devices that have
     * device-specific configuration.
     */
    device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);

    err = pci_request_selected_regions(pci_dev, mdev-&amp;gt;modern_bars,
                       &quot;virtio-pci-modern&quot;);
    if (err)
        return err;

    err = -EINVAL;
    mdev-&amp;gt;common = vp_modern_map_capability(mdev, common,
                      sizeof(struct virtio_pci_common_cfg), 4,
                      0, sizeof(struct virtio_pci_common_cfg),
                      NULL, NULL);
    if (!mdev-&amp;gt;common)
        goto err_map_common;
    mdev-&amp;gt;isr = vp_modern_map_capability(mdev, isr, sizeof(u8), 1,
                         0, 1,
                         NULL, NULL);
    if (!mdev-&amp;gt;isr)
        goto err_map_isr;

    /* Read notify_off_multiplier from config space. */
    pci_read_config_dword(pci_dev,
        notify + offsetof(struct virtio_pci_notify_cap,
                  notify_off_multiplier),
        &amp;amp;mdev-&amp;gt;notify_offset_multiplier);
    /* Read notify length and offset from config space. */
    pci_read_config_dword(pci_dev,
        notify + offsetof(struct virtio_pci_notify_cap,
                  cap.length),
        &amp;amp;notify_length);

    pci_read_config_dword(pci_dev,
        notify + offsetof(struct virtio_pci_notify_cap,
                  cap.offset),
        &amp;amp;notify_offset);

    /* We don't know how many VQs we'll map, ahead of the time.
     * If notify length is small, map it all now.
     * Otherwise, map each VQ individually later.
     */
    if ((u64)notify_length + (notify_offset % PAGE_SIZE) &amp;lt;= PAGE_SIZE) {
        mdev-&amp;gt;notify_base = vp_modern_map_capability(mdev, notify,
                            2, 2,
                            0, notify_length,
                            &amp;amp;mdev-&amp;gt;notify_pa);
        if (!mdev-&amp;gt;notify_base)
            goto err_map_notify;
    } else {
        mdev-&amp;gt;notify_map_cap = notify;
    }

    /* Again, we don't know how much we should map, but PAGE_SIZE
     * is more than enough for all existing devices.
     */
    if (device) {
        mdev-&amp;gt;device = vp_modern_map_capability(mdev, device, 0, 4,
                            0, PAGE_SIZE,
                            &amp;amp;mdev-&amp;gt;device_len,
                            NULL);
        if (!mdev-&amp;gt;device)
            goto err_map_device;
    }

    return 0;

err_map_device:
    if (mdev-&amp;gt;notify_base)
        pci_iounmap(pci_dev, mdev-&amp;gt;notify_base);
err_map_notify:
    pci_iounmap(pci_dev, mdev-&amp;gt;isr);
err_map_isr:
    pci_iounmap(pci_dev, mdev-&amp;gt;common);
err_map_common:
    pci_release_selected_regions(pci_dev, mdev-&amp;gt;modern_bars);
    return err;
}
EXPORT_SYMBOL_GPL(vp_modern_probe);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;사실 옛날 버전 KVM, 즉 Linux 커널 옛 코드에서는 vp_modern_probe 함수의 내용 대부분이 virtio_pci_modern_probe 함수 안에 그대로 박혀 있었는데, 나중에야 따로 함수로 떼낸 거다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vp_modern_probe의 전체 단계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cdzrk/dJMcaaeBq3b/E2E96cXFEPecGMIkHHEiLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cdzrk/dJMcaaeBq3b/E2E96cXFEPecGMIkHHEiLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cdzrk/dJMcaaeBq3b/E2E96cXFEPecGMIkHHEiLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCdzrk%2FdJMcaaeBq3b%2FE2E96cXFEPecGMIkHHEiLK%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;247&quot; height=&quot;678&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;1030&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) vendor ID와 device ID 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_modern_probe는 우선 virtio 장치의 vendor ID와 device ID를 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420053226&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* We only own devices &amp;gt;= 0x1000 and &amp;lt;= 0x107f: leave the rest. */
    if (pci_dev-&amp;gt;device &amp;lt; 0x1000 || pci_dev-&amp;gt;device &amp;gt; 0x107f)
        return -ENODEV;

    if (pci_dev-&amp;gt;device &amp;lt; 0x1040) {
        /* Transitional devices: use the PCI subsystem device id as
         * virtio device id, same as legacy driver always did.
         */
        mdev-&amp;gt;id.device = pci_dev-&amp;gt;subsystem_device;
    } else {
        /* Modern devices: simply use PCI device id, but start from 0x1040. */
        mdev-&amp;gt;id.device = pci_dev-&amp;gt;device - 0x1040;
    }
    mdev-&amp;gt;id.vendor = pci_dev-&amp;gt;subsystem_vendor;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;짚고 갈 점은, virtio PCI proxy 장치의 device ID는 virtio_pci_device_plugged 함수를 다룰 때 설정한 PCI_DEVICE_ID_VIRTIO_10_BASE + VIRTIO_ID_BALLOON, 즉 0x1040+5로 잡혀 있다는 거다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420106356&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PCI_DEVICE_ID_VIRTIO_10_BASE + VIRTIO_ID_BALLOON
= 0x1040 + 5
= 0x1045&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;왜 이렇게 계산하냐면, virtio 장치들이 PCI device ID를 &quot;기준값 + 장치 종류 번호&quot; 방식으로 규칙적으로 배정받기 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PCI_DEVICE_ID_VIRTIO_10_BASE = 0x1040 &amp;mdash; modern(1.0+) virtio 장치들의 공통 시작점(base). 모든 modern virtio 장치의 device ID는 이 0x1040부터 시작한다.&lt;/li&gt;
&lt;li&gt;VIRTIO_ID_BALLOON = 5 &amp;mdash; balloon 장치의 고유 종류 번호. virtio 명세에서 각 장치 타입마다 번호가 정해져 있다 (net=1, block=2, ..., balloon=5 등).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &quot;기준점 0x1040에서 balloon의 번호 5만큼 더한 0x1045&quot;가 balloon 장치의 device ID가 된다. 다른 장치도 같은 규칙이 다.&lt;/p&gt;
&lt;table style=&quot;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;&lt;b&gt;device&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;계산&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;device id&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;virtio-net&lt;/td&gt;
&lt;td&gt;0x1040 + 1&lt;/td&gt;
&lt;td&gt;0x1041&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;virtio-blk&lt;/td&gt;
&lt;td&gt;0x1040 + 2&lt;/td&gt;
&lt;td&gt;0x1042&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;virtio-balloon&lt;/td&gt;
&lt;td&gt;0x1040 + 5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;0x1045&lt;/b&gt;&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;pre id=&quot;code_1781420328951&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * modern virtio-pci devices get their id assigned automatically,
 * there is no need to add #defines here.  It gets calculated as
 *
 *    PCI_DEVICE_ID = PCI_DEVICE_ID_VIRTIO_10_BASE +
 *                    virtio_bus_get_vdev_id(bus)
 */
#define PCI_DEVICE_ID_VIRTIO_10_BASE     0x1040&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781420339594&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define VIRTIO_ID_BALLOON        5 /* virtio balloon */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1421&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnKpk3/dJMcabExboI/OCWNuBEl0TiNUHLMvAwxY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnKpk3/dJMcabExboI/OCWNuBEl0TiNUHLMvAwxY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnKpk3/dJMcabExboI/OCWNuBEl0TiNUHLMvAwxY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnKpk3%2FdJMcabExboI%2FOCWNuBEl0TiNUHLMvAwxY0%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;1421&quot; height=&quot;118&quot; data-origin-width=&quot;1421&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;PCI Agent 장치의 설정 공간 구조&lt;/p&gt;
&lt;pre id=&quot;code_1781420403676&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;31      24 23      16 15            8 7             0
┌─────────────────────┬───────────────────────────────┐
│      Device ID      │          Vendor ID            │ 0x00
├─────────────────────┼───────────────────────────────┤
│       Status        │           Command             │ 0x04
├─────────────────────┴───────┬───────────────────────┤
│         Class Code          │      Revision ID      │ 0x08
├──────┬───────────┬──────────┼───────────────────────┤
│ BIST │Header Type│Latency T.│   Cache Line Size     │ 0x0C
├──────┴───────────┴──────────┴───────────────────────┤
│             Base Address Register 0                 │ 0x10
├─────────────────────────────────────────────────────┤
│             Base Address Register 1                 │ 0x14
├─────────────────────────────────────────────────────┤
│             Base Address Register 2                 │ 0x18
├─────────────────────────────────────────────────────┤
│             Base Address Register 3                 │ 0x1C
├─────────────────────────────────────────────────────┤
│             Base Address Register 4                 │ 0x20
├─────────────────────────────────────────────────────┤
│             Base Address Register 5                 │ 0x24
├─────────────────────────────────────────────────────┤
│                Cardbus CIS Pointer                  │ 0x28
├─────────────────────┬───────────────────────────────┤
│    Subsystem ID     │     Subsystem Vendor ID       │ 0x2C
├─────────────────────┴───────────────────────────────┤
│           Expansion ROM Base Address                │ 0x30
├─────────────────────────────┬───────────────────────┤
│          Reserved           │  Capabilities Pointer │ 0x34
├─────────────────────────────┴───────────────────────┤
│                    Reserved                         │ 0x38
├──────────┬──────────┬────────────┬──────────────────┤
│ MAX_Lat  │ Min_Gnt  │Interrupt Pin│ Interrupt Line  │ 0x3C
└──────────┴──────────┴────────────┴──────────────────┘
            PCI Agent 장치의 설정 공간&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) virtio_pci_find_capability &amp;mdash; pci capability 찾기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 virtio_pci_find_capability 함수를 여러 번 호출해서 virtio PCI proxy 장치의 pci capability를 찾는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420450521&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* check for a common config: if not, use legacy mode (bar 0). */
    common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,
                IORESOURCE_IO | IORESOURCE_MEM,
                &amp;amp;mdev-&amp;gt;modern_bars);
    if (!common) {
        dev_info(&amp;amp;pci_dev-&amp;gt;dev,
             &quot;virtio_pci: leaving for legacy driver\n&quot;);
        return -ENODEV;
    }

    /* If common is there, these should be too... */
    isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);
    notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);
    if (!isr || !notify) {
        dev_err(&amp;amp;pci_dev-&amp;gt;dev,
            &quot;virtio_pci: missing capabilities %i/%i/%i\n&quot;,
            common, isr, notify);
        return -EINVAL;
    }

    err = dma_set_mask_and_coherent(&amp;amp;pci_dev-&amp;gt;dev, DMA_BIT_MASK(64));
    if (err)
        err = dma_set_mask_and_coherent(&amp;amp;pci_dev-&amp;gt;dev,
                        DMA_BIT_MASK(32));
    if (err)
        dev_warn(&amp;amp;pci_dev-&amp;gt;dev, &quot;Failed to enable 64-bit or 32-bit DMA.  Trying to continue, but this might

    /* Device capability is only mandatory for devices that have
     * device-specific configuration.
     */
    device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이어서 virtio 장치의 레지스터 설정 정보를 PCI capability로서 설정 공간에 써넣는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420473501&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (modern) {
        struct virtio_pci_cap cap = {
            .cap_len = sizeof cap,
            struct virtio_pci_notify_cap notify = {
                .cap.cap_len = sizeof notify,
                .notify_off_multiplier =
                    cpu_to_le32(virtio_pci_queue_mem_mult(proxy)),
            };
            struct virtio_pci_cfg_cap cfg = {
                .cap.cap_len = sizeof cfg,
                .cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,
            };
            struct virtio_pci_notify_cap notify_pio = {
                .cap.cap_len = sizeof notify,
                .notify_off_multiplier = cpu_to_le32(0x0),
            };

            struct virtio_pci_cfg_cap *cfg_mask;

            virtio_pci_modern_regions_init(proxy, vdev-&amp;gt;name);

            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;common, &amp;amp;cap);
            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;isr, &amp;amp;cap);
            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;device, &amp;amp;cap);
            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;notify, &amp;amp;notify.cap);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio-pci는 자기 기능 수요에 맞춰서 아래와 같은 capabilities 레이아웃을 설계했다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;오른쪽이 6개의 capability이고, 그중 5개는 virtio-pci의 capability를 기술하는 용도로 쓰이고, 1개는 MSI-X의 capability를 기술하는 용도로 쓰인다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 virtio-pci에서 쓰이는 capability만 소개한다. 다시 아래 그림을 보면, 오른쪽은 virtio-blk의 capability 레이아웃을 그리고 있고, 왼쪽은 각 capability가 가리키는 물리 주소 공간 레이아웃이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio-pci 장치의 초기화, 백엔드 알림, 데이터 전달 같은 핵심 기능은 모두 이 5개 capability에서 구현된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420936357&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                                         capabilities pointer ┌──┐
                                                              │98│ 0x34
                                                              └──┘
                            ┌─────────────────────┐  ┌──────────────────┐
BAR 4 ┌──────────┐          │virtio_pci_cap_common│  │04 01 10 00 │09│  │ 0x40
      │0xfe008000│          │       _cfg          │  │ length 0x1000    │
      └──────────┘          └─────────────────────┘  │ offset 0x00      │
                                                      └──────────────────┘

      ┌────────────────────────┐  ┌──────────────────┐  ┌──────────────────┐
      │struct virtio_pci_      │  │virtio_pci_cap_isr│  │04 03 10 40 │09│  │ 0x50
      │   common_cfg           │  │      _cfg        │  │ length 0x1000    │
      │     0xfe008000         │  │                  │  │ offset 0x1000    │
      ├────────────────────────┤  └──────────────────┘  └──────────────────┘
      │  ......                │
      ├────────────────────────┤  ┌──────────────────┐  ┌──────────────────┐
      │ isr status             │  │virtio_pci_cap_   │  │04 04 10 50 │09│  │ 0x60
      │     0xfe008100         │  │   device_cfg     │  │ length 0x1000    │
      ├────────────────────────┤  │                  │  │ offset 0x2000    │
      │  ......                │  └──────────────────┘  └──────────────────┘
      ├────────────────────────┤
      │ struct virtio_pci_     │  ┌──────────────────┐  ┌──────────────────┐
      │     notify_cap         │  │virtio_pci_cap_   │  │04 02 14 60 │09│  │ 0x70
      │     0xfe008300         │  │   notify_cfg     │  │ length 0x1000    │
      └────────────────────────┘  │                  │  │ offset 0x3000    │
                                  └──────────────────┘  └──────────────────┘

      ┌──────────┬───┬──────────┬─────────┐  ┌──────────────────┐  ┌──────────────────┐
      │ Bits     │ 0 │    1     │  2-31   │  │virtio_pci_cap_cfg│  │00 05 14 70 │09│  │ 0x84
      ├──────────┼───┼──────────┼─────────┤  │                  │  │                  │
      │          │que│device    │         │  └──────────────────┘  └──────────────────┘
      │ Purpose  │ue │config    │reserved │
      │          │int│interrupt │         │                        ┌──────────────────┐
      │          │err│          │         │                        │  84 11           │ 0x98
      │          │upt│          │         │                        └──────────────────┘
      └──────────┴───┴──────────┴─────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) PCI BAR 찾기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_find_capability 함수는 소속된 PCI BAR를 찾아낸 후 virt_pci_device의 modern_bars 멤버에 써 넣는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420564614&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Device capability is only mandatory for devices that have
     * device-specific configuration.
     */
    device = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_DEVICE_CFG,
            IORESOURCE_IO | IORESOURCE_MEM,
            &amp;amp;mdev-&amp;gt;modern_bars);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_realize 함수에서 이 modern_bars가 1&amp;lt;&amp;lt;4라는 걸 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781420598056&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
    VirtioPCIClass *k = VIRTIO_PCI_GET_CLASS(pci_dev);
    bool pcie_port = pci_bus_is_express(pci_get_bus(pci_dev)) &amp;amp;&amp;amp;
                     !pci_bus_is_root(pci_get_bus(pci_dev));

    if (kvm_enabled() &amp;amp;&amp;amp; !kvm_has_many_ioeventfds()) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /* fd-based ioevents can't be synchronized in record/replay */
    if (replay_mode != REPLAY_MODE_NONE) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /*
     * virtio pci bar layout used by default.
     * subclasses can re-arrange things if needed.
     *
     *   region 0   --  virtio legacy io bar
     *   region 1   --  msi-x bar
     *   region 2   --  virtio modern io bar (off by default)
     *   region 4+5 --  virtio modern memory (64bit) bar
     *
     */
    proxy-&amp;gt;legacy_io_bar_idx  = 0;
    proxy-&amp;gt;msix_bar_idx       = 1;
    proxy-&amp;gt;modern_io_bar_idx  = 2;
    proxy-&amp;gt;modern_mem_bar_idx = 4;

    proxy-&amp;gt;common.offset = 0x0;
    proxy-&amp;gt;common.size = 0x1000;
    proxy-&amp;gt;common.type = VIRTIO_PCI_CAP_COMMON_CFG;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) pci_request_selected_regions &amp;mdash; BAR 주소공간 예약&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 pci_request_selected_regions 함수가 virtio PCI proxy 장치의 BAR 주소 공간을 예약해 둔다.&lt;/p&gt;
&lt;pre id=&quot;code_1781421075191&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = pci_request_selected_regions(pci_dev, mdev-&amp;gt;modern_bars,
                       &quot;virtio-pci-modern&quot;);
    if (err)
        return err;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) vp_modern_map_capability &amp;mdash; capability를 커널 주소공간에 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vp_modern_map_capability 함수를 호출해서 대응되는 capability를 PCI proxy 장치 안의 BAR 공간에서 커널 주소 공간으로 매핑한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781421114897&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = -EINVAL;
    mdev-&amp;gt;common = vp_modern_map_capability(mdev, common,
                      sizeof(struct virtio_pci_common_cfg), 4,
                      0, sizeof(struct virtio_pci_common_cfg),
                      NULL, NULL);
    if (!mdev-&amp;gt;common)
        goto err_map_common;
    mdev-&amp;gt;isr = vp_modern_map_capability(mdev, isr, sizeof(u8), 1,
                         0, 1,
                         NULL, NULL);
    if (!mdev-&amp;gt;isr)
        goto err_map_isr;

    /* Read notify_off_multiplier from config space. */
    pci_read_config_dword(pci_dev,
        notify + offsetof(struct virtio_pci_notify_cap,
                  notify_off_multiplier),
        &amp;amp;mdev-&amp;gt;notify_offset_multiplier);
    /* Read notify length and offset from config space. */
    pci_read_config_dword(pci_dev,
        notify + offsetof(struct virtio_pci_notify_cap,
                  cap.length),
        &amp;amp;notify_length);

    pci_read_config_dword(pci_dev,
        notify + offsetof(struct virtio_pci_notify_cap,
                  cap.offset),
        &amp;amp;notify_offset);

    /* We don't know how many VQs we'll map, ahead of the time.
     * If notify length is small, map it all now.
     * Otherwise, map each VQ individually later.
     */
    if ((u64)notify_length + (notify_offset % PAGE_SIZE) &amp;lt;= PAGE_SIZE) {
        mdev-&amp;gt;notify_base = vp_modern_map_capability(mdev, notify,
                            2, 2,
                            0, notify_length,
                            &amp;amp;mdev-&amp;gt;notify_pa);
        if (!mdev-&amp;gt;notify_base)
            goto err_map_notify;
    } else {
        mdev-&amp;gt;notify_map_cap = notify;
    }

    /* Again, we don't know how much we should map, but PAGE_SIZE
     * is more than enough for all existing devices.
     */
    if (device) {
        mdev-&amp;gt;device = vp_modern_map_capability(mdev, device, 0, 4,
                            0, PAGE_SIZE,
                            &amp;amp;mdev-&amp;gt;device_len,
                            NULL);
        if (!mdev-&amp;gt;device)
            goto err_map_device;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 mp_dev(구조체 virtio_pci_modern_device *mdev = &amp;amp;vp_dev-&amp;gt;mdev;)의 common 멤버는 virtio_pci_common_cfg의 데이터를 커널 주소 공간으로 매핑한 거다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 해두면 나중에는 이 메모리 주소 공간을 통해 직접 common이라는 capability를 접근할 수 있고, 다른 capability(isr, notify, device)도 이와 비슷하게 처리된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PCI BAR 공간 &amp;rarr; 커널 주소 공간 매핑&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1129&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDlbw2/dJMcaffVxbV/GtglTttiMAKAVFmHcE8nL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDlbw2/dJMcaffVxbV/GtglTttiMAKAVFmHcE8nL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDlbw2/dJMcaffVxbV/GtglTttiMAKAVFmHcE8nL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDlbw2%2FdJMcaffVxbV%2FGtglTttiMAKAVFmHcE8nL1%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;675&quot; height=&quot;270&quot; data-origin-width=&quot;1129&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781421152848&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_map_capability - map a part of virtio pci capability
 * @mdev: the modern virtio-pci device
 * @off: offset of the capability
 * @minlen: minimal length of the capability
 * @align: align requirement
 * @start: start from the capability
 * @size: map size
 * @len: the length that is actually mapped
 * @pa: physical address of the capability
 *
 * Returns the io address of for the part of the capability
 */
static void __iomem *
vp_modern_map_capability(struct virtio_pci_modern_device *mdev, int off,
             size_t minlen, u32 align, u32 start, u32 size,
             size_t *len, resource_size_t *pa)
{
    struct pci_dev *dev = mdev-&amp;gt;pci_dev;
    u8 bar;
    u32 offset, length;
    void __iomem *p;

    pci_read_config_byte(dev, off + offsetof(struct virtio_pci_cap,
                          bar),
                 &amp;amp;bar);
    pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, offset),
                 &amp;amp;offset);
    pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, length),
                 &amp;amp;length);

    /* Check if the BAR may have changed since we requested the region. */
    if (bar &amp;gt;= PCI_STD_NUM_BARS || !(mdev-&amp;gt;modern_bars &amp;amp; (1 &amp;lt;&amp;lt; bar))) {
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: bar unexpectedly changed to %u\n&quot;, bar);
        return NULL;
    }

    if (length &amp;lt;= start) {
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: bad capability len %u (&amp;gt;%u expected)\n&quot;,
            length, start);
        return NULL;
    }

    if (length - start &amp;lt; minlen) {
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: bad capability len %u (&amp;gt;=%zu expected)\n&quot;,
            length, minlen);
        return NULL;
    }

    length -= start;

    if (start + offset &amp;lt; offset) {
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: map wrap-around %u+%u\n&quot;,
            start, offset);
        return NULL;
    }

    offset += start;

    if (offset &amp;amp; (align - 1)) {
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: offset %u not aligned to %u\n&quot;,
            offset, align);
        return NULL;
    }

    if (length &amp;gt; size)
        length = size;

    if (len)
        *len = length;

    if (minlen + offset &amp;lt; minlen ||
        minlen + offset &amp;gt; pci_resource_len(dev, bar)) {
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: map virtio %zu@%u &quot;
            &quot;out of range on bar %i length %lu\n&quot;,
            minlen, offset,
            bar, (unsigned long)pci_resource_len(dev, bar));
        return NULL;
    }

    p = pci_iomap_range(dev, bar, offset, length);
    if (!p)
        dev_err(&amp;amp;dev-&amp;gt;dev,
            &quot;virtio_pci: unable to map virtio %u@%u on bar %i\n&quot;,
            length, offset, bar);
    else if (pa)
        *pa = pci_resource_start(dev, bar) + offset;

    return p;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 사실상 virtio PCI proxy 장치의 BAR가 가상 머신 커널 주소 공간에 매핑된 거고, 이후로는 이 주소들을 직접 접근하기만 하면 virtio PCI proxy 장치에 대한 설정과 제어가 가능해진다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_modern_probe 함수 호출이 끝난 다음, virtio_pci_modern_probe 함수는 이어서 virtio_pci_device 안의 virtio_device의 멤버 vdev의 config 멤버를 설정한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;device라는 capability가 있으면 virtio_pci_config_ops로 설정하고, 그렇지 않으면 virtio_pci_config_nodev_ops로 설정한다.&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;그 뒤로는 vpdev, 즉 struct virtio_pci_device의 몇몇 콜백 함수를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config_vector는 virt 큐와 MSI 인터럽트 연결과 관련된 거라 vp_config_vector로 설정하고, setup_vq는 virtio 장치의 virt queue 설정에 쓰여서 setup_vq로 설정한다. del_vq는 virt queue 삭제에 쓰여서 del_vq로 설정한다.&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;virtio_pci_modern_probe 함수의 마무리 단계 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mmyRT/dJMcabR2GNN/YuL5JzCS6kNeMUpXvbNXS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mmyRT/dJMcabR2GNN/YuL5JzCS6kNeMUpXvbNXS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mmyRT/dJMcabR2GNN/YuL5JzCS6kNeMUpXvbNXS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmmyRT%2FdJMcabR2GNN%2FYuL5JzCS6kNeMUpXvbNXS0%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;412&quot; height=&quot;583&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;struct virtio_pci_device 데이터 구조 정리&lt;/h4&gt;
&lt;pre id=&quot;code_1781421253117&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Our device structure */
struct virtio_pci_device {
    struct virtio_device vdev;
    struct pci_dev *pci_dev;
    union {
        struct virtio_pci_legacy_device ldev;
        struct virtio_pci_modern_device mdev;
    };
    bool is_legacy;

    /* Where to read and clear interrupt */
    u8 __iomem *isr;

    /* a list of queues so we can dispatch IRQs */
    spinlock_t lock;
    struct list_head virtqueues;

    /* array of all queues for house-keeping */
    struct virtio_pci_vq_info **vqs;

    /* MSI-X support */
    int msix_enabled;
    int intx_enabled;
    cpumask_var_t *msix_affinity_masks;
    /* Name strings for interrupts. This size should be enough,
     * and I'm too lazy to allocate each name separately. */
    char (*msix_names)[256];
    /* Number of available vectors */
    unsigned int msix_vectors;
    /* Vectors allocated, excluding per-vq vectors if any */
    unsigned int msix_used_vectors;

    /* Whether we have vector per vq */
    bool per_vq_vectors;

    struct virtqueue *(*setup_vq)(struct virtio_pci_device *vp_dev,
                      struct virtio_pci_vq_info *info,
                      unsigned int idx,
                      void (*callback)(struct virtqueue *vq),
                      const char *name,
                      bool ctx,
                      u16 msix_vec);
    void (*del_vq)(struct virtio_pci_vq_info *info);

    u16 (*config_vector)(struct virtio_pci_device *vp_dev, u16 vector);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_modern_probe 실행이 끝난 후 관련 데이터 구조는 아래와 같이 엮인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1335&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mo5Bk/dJMcahrg2eK/ukyfqdfYMbZ5CWaFa98XF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mo5Bk/dJMcahrg2eK/ukyfqdfYMbZ5CWaFa98XF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mo5Bk/dJMcahrg2eK/ukyfqdfYMbZ5CWaFa98XF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmo5Bk%2FdJMcahrg2eK%2FukyfqdfYMbZ5CWaFa98XF0%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;1335&quot; height=&quot;586&quot; data-origin-width=&quot;1335&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;register_virtio_device &amp;mdash; virtio 장치를 시스템에 등록&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;다시 virtio_pci_probe 함수로 돌아가자. virtio_pci_probe 함수는 virtio_pci_modern_probe 함수 호출이 끝난 다음, 이어서 register_virtio_device를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781421311138&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rc = register_virtio_device(&amp;amp;vp_dev-&amp;gt;vdev);
    reg_dev = vp_dev;
    if (rc)
        goto err_register;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781421319770&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * register_virtio_device - register virtio device
 * @dev        : virtio device to be registered
 *
 * On error, the caller must call put_device on &amp;amp;@dev-&amp;gt;dev (and not kfree),
 * as another code path may have obtained a reference to @dev.
 *
 * Returns: 0 on sucess, -error on failure
 */
int register_virtio_device(struct virtio_device *dev)
{
    int err;

    dev-&amp;gt;dev.bus = &amp;amp;virtio_bus;
    device_initialize(&amp;amp;dev-&amp;gt;dev);

    /* Assign a unique device index and hence name. */
    err = ida_alloc(&amp;amp;virtio_index_ida, GFP_KERNEL);
    if (err &amp;lt; 0)
        goto out;

    dev-&amp;gt;index = err;
    err = dev_set_name(&amp;amp;dev-&amp;gt;dev, &quot;virtio%u&quot;, dev-&amp;gt;index);
    if (err)
        goto out_ida_remove;

    err = virtio_device_of_init(dev);
    if (err)
        goto out_ida_remove;

    spin_lock_init(&amp;amp;dev-&amp;gt;config_lock);
    dev-&amp;gt;config_enabled = false;
    dev-&amp;gt;config_change_pending = false;

    INIT_LIST_HEAD(&amp;amp;dev-&amp;gt;vqs);
    spin_lock_init(&amp;amp;dev-&amp;gt;vqs_list_lock);

    /* We always start by resetting the device, in case a previous
     * driver messed it up.  This also tests that code path a little. */
    virtio_reset_device(dev);

    /* Acknowledge that we've seen the device. */
    virtio_add_status(dev, VIRTIO_CONFIG_S_ACKNOWLEDGE);

    /*
     * device_add() causes the bus infrastructure to look for a matching
     * driver.
     */
    err = device_add(&amp;amp;dev-&amp;gt;dev);
    if (err)
        goto out_of_node_put;

    return 0;

out_of_node_put:
    of_node_put(dev-&amp;gt;dev.of_node);
out_ida_remove:
    ida_free(&amp;amp;virtio_index_ida, dev-&amp;gt;index);
out:
    virtio_add_status(dev, VIRTIO_CONFIG_S_FAILED);
    return err;
}
EXPORT_SYMBOL_GPL(register_virtio_device);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;앞에서 짚었듯이, vp_dev-&amp;gt;vdev의 타입은 struct virtio_device고, register_virtio_device 함수에 실제로 넘기는 인자는 vp_dev-&amp;gt;vdev의 주소, 즉 &amp;amp;vp_dev-&amp;gt;vdev다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;함수 이름과 인자 타입만 봐도 알 수 있듯이, register_virtio_device 함수는 virtio 장치 하나를 시스템에 등록하는 역할을 한다.&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;register_virtio_device의 전체 단계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;958&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KThaI/dJMcadWEwD4/iwyDcsQknKVxLdb17hutq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KThaI/dJMcadWEwD4/iwyDcsQknKVxLdb17hutq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KThaI/dJMcadWEwD4/iwyDcsQknKVxLdb17hutq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKThaI%2FdJMcadWEwD4%2FiwyDcsQknKVxLdb17hutq1%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;376&quot; height=&quot;685&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) Bus를 virtio_bus로 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio 장치의 Bus를 virtio_bus로 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781421363353&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dev-&amp;gt;dev.bus = &amp;amp;virtio_bus;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_bus는 시스템 초기화 시점에 register_virtio_driver 함수를 통해 시스템에 등록된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 장치 이름 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 장치의 이름을 &quot;virtio0&quot;, &quot;virtio1&quot; 같은 문자열로 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425044572&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Assign a unique device index and hence name. */
    err = ida_alloc(&amp;amp;virtio_index_ida, GFP_KERNEL);
    if (err &amp;lt; 0)
        goto out;

    dev-&amp;gt;index = err;
    err = dev_set_name(&amp;amp;dev-&amp;gt;dev, &quot;virtio%u&quot;, dev-&amp;gt;index);
    if (err)
        goto out_ida_remove;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781425057345&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * dev_set_name - set a device name
 * @dev: device
 * @fmt: format string for the device's name
 */
int dev_set_name(struct device *dev, const char *fmt, ...)
{
    va_list vargs;
    int err;

    va_start(vargs, fmt);
    err = kobject_set_name_vargs(&amp;amp;dev-&amp;gt;kobj, fmt, vargs);
    va_end(vargs);
    return err;
}
EXPORT_SYMBOL_GPL(dev_set_name);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) virtio_reset_device로 장치 리셋&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 virtio_reset_device 함수를 호출해 장치를 리셋한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425077175&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* We always start by resetting the device, in case a previous
     * driver messed it up.  This also tests that code path a little. */
    virtio_reset_device(dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) device_add로 장치를 시스템에 등록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 device_add 함수를 호출해 장치를 시스템에 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425099916&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
     * device_add() causes the bus infrastructure to look for a matching
     * driver.
     */
    err = device_add(&amp;amp;dev-&amp;gt;dev);
    if (err)
        goto out_of_node_put;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서, 옛 버전 코드에서는 device_register 함수를 호출했다. device_register 함수는 장치 드라이버와 관련이 좀 더 크니까, 여기서 그 역할을 간단히 짚고 가자.&lt;/p&gt;
&lt;pre id=&quot;code_1781425119558&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * device_register - register a device with the system.
 * @dev: pointer to the device structure
 *
 * This happens in two clean steps - initialize the device
 * and add it to the system. The two steps can be called
 * separately, but this is the easiest and most common.
 * I.e. you should only call the two helpers separately if
 * have a clearly defined need to use and refcount the device
 * before it is added to the hierarchy.
 *
 * For more information, see the kerneldoc for device_initialize()
 * and device_add().
 *
 * NOTE: _Never_ directly free @dev after calling this function, even
 * if it returned an error! Always use put_device() to give up the
 * reference initialized in this function instead.
 */
int device_register(struct device *dev)
{
    device_initialize(dev);
    return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;device_register 함수는 시스템에 장치 하나를 등록한다. 두 가지 간단한 단계로 나뉘는데&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;장치 초기화(device_initialize(dev))&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;시스템에 추가하는 단계(device_add(dev))&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 단계는 따로 따로 호출해도 되지만, 같이 묶어서 device_register 함수를 쓰는 게 가장 간단하고 가장 흔한 방법이다.&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;옛 버전 vs 새 버전 호출 방식 비교&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;759&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CQtjS/dJMcabq2dZl/QF7UrF1NvzjWlvswTGStj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CQtjS/dJMcabq2dZl/QF7UrF1NvzjWlvswTGStj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CQtjS/dJMcabq2dZl/QF7UrF1NvzjWlvswTGStj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCQtjS%2FdJMcabq2dZl%2FQF7UrF1NvzjWlvswTGStj1%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;393&quot; height=&quot;471&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;759&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 부분 코드에서도 알 수 있듯이,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;옛 버전 커널 코드에서는 정말로 device_register 함수를 직접 호출했지만,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;새 버전 커널 코드는 여기서 register_virtio_device 함수의 앞쪽에서 먼저 device_initialize(&amp;amp;dev-&amp;gt;dev)를 호출하고, 그 후 여기서 device_add(&amp;amp;dev-&amp;gt;dev)를 호출한다. 즉 분리 호출 방식을 쓴 거다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device_register 함수는 device_add 함수를 호출해서 장치를 시스템에 추가하고, 동시에 사용자 공간으로 uevent 메시지를 한 번 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 uevent 메시지 안에는 virtio 장치의 vendor id, device id가 담겨 있다. udev가 이 메시지를 받고 나면, virtio 장치에 대응하는 드라이버를 로드한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음, device_add 함수는 bus_probe_device 함수를 호출하고, 최종적으로 Bus의 probe 함수와 장치의 probe 함수, 즉 virtio_dev_probe 함수와 virtballoon_probe 함수를 호출하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425247121&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * device_add - add device to device hierarchy.
 * @dev: device.
 *
 * This is part 2 of device_register(), though may be called
 * separately _iff_ device_initialize() has been called separately.
 *
 * This adds @dev to the kobject hierarchy via kobject_add(), adds it
 * to the global and sibling lists for the device, then
 * adds it to the other relevant subsystems of the driver model.
 *
 * Do not call this routine or device_register() more than once for
 * any device structure.  The driver model core is not designed to work
 * with devices that get unregistered and then spring back to life.
 * (Among other things, it's very hard to guarantee that all references
 * to the previous incarnation of @dev have been dropped.)  Allocate
 * and register a fresh new struct device instead.
 *
 * NOTE: _Never_ directly free @dev after calling this function, even
 * if it returned an error! Always use put_device() to give up your
 * reference instead.
 *
 * Rule of thumb is: if device_add() succeeds, you should call
 * device_del() when you want to get rid of it. If device_add() has
 * *not* succeeded, use *only* put_device() to drop the reference
 * count.
 */
int device_add(struct device *dev)
{
    struct subsys_private *sp;
    struct device *parent;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;
    struct kobject *glue_dir = NULL;

    dev = get_device(dev);
    if (!dev)
        goto done;

    if (!dev-&amp;gt;p) {
        error = device_private_init(dev);
        if (error)
            goto done;
    }

    /*
     * for statically allocated devices, which should all be converted
     * some day, we need to initialize the name. We prevent reading back
     * the name, and force the use of dev_name()
     */
    if (dev-&amp;gt;init_name) {
        error = dev_set_name(dev, &quot;%s&quot;, dev-&amp;gt;init_name);
        dev-&amp;gt;init_name = NULL;
    }

    if (dev_name(dev))
        error = 0;
    /* subsystems can specify simple device enumeration */
    else if (dev-&amp;gt;bus &amp;amp;&amp;amp; dev-&amp;gt;bus-&amp;gt;dev_name)
        error = dev_set_name(dev, &quot;%s%u&quot;, dev-&amp;gt;bus-&amp;gt;dev_name, dev-&amp;gt;id);
    else
        error = -EINVAL;
    if (error)
        goto name_error;

    pr_debug(&quot;device: '%s': %s\n&quot;, dev_name(dev), __func__);

    parent = get_device(dev-&amp;gt;parent);
    kobj = get_device_parent(dev, parent);
    if (IS_ERR(kobj)) {
        error = PTR_ERR(kobj);
        goto parent_error;
    }
    if (kobj)
        dev-&amp;gt;kobj.parent = kobj;

    /* use parent numa_node */
    if (parent &amp;amp;&amp;amp; (dev_to_node(dev) == NUMA_NO_NODE))
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    error = kobject_add(&amp;amp;dev-&amp;gt;kobj, dev-&amp;gt;kobj.parent, NULL);
    if (error) {
        glue_dir = kobj;
        goto Error;
    }

    /* notify platform of device entry */
    device_platform_notify(dev);

    error = device_create_file(dev, &amp;amp;dev_attr_uevent);
    if (error)
        goto attrError;

    error = device_add_class_symlinks(dev);
    if (error)
        goto SymlinkError;
    error = device_add_attrs(dev);
    if (error)
        goto AttrsError;
    error = bus_add_device(dev);
    if (error)
        goto BusError;
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    device_pm_add(dev);

    if (MAJOR(dev-&amp;gt;devt)) {
        error = device_create_file(dev, &amp;amp;dev_attr_dev);
        if (error)
            goto DevAttrError;

        error = device_create_sys_dev_entry(dev);
        if (error)
            goto SysEntryError;

        devtmpfs_create_node(dev);
    }

    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    bus_notify(dev, BUS_NOTIFY_ADD_DEVICE);
    kobject_uevent(&amp;amp;dev-&amp;gt;kobj, KOBJ_ADD);

    /*
     * Check if any of the other devices (consumers) have been waiting for
     * this device (supplier) to be added so that they can create a device
     * link to it.
     *
     * This needs to happen after device_pm_add() because device_link_add()
     * requires the supplier be registered before it's called.
     *
     * But this also needs to happen before bus_probe_device() to make sure
     * waiting consumers can link to it before the driver is bound to the
     * device and the driver sync_state callback is called for this device.
     */
    if (dev-&amp;gt;fwnode &amp;amp;&amp;amp; !dev-&amp;gt;fwnode-&amp;gt;dev) {
        dev-&amp;gt;fwnode-&amp;gt;dev = dev;
        fw_devlink_link_device(dev);
    }

    bus_probe_device(dev);

    /*
     * If all driver registration is done and a newly added device doesn't
     * match with any driver, don't block its consumers from probing in
     * case the consumer device is able to operate without this supplier.
     */
    if (dev-&amp;gt;fwnode &amp;amp;&amp;amp; fw_devlink_drv_reg_done &amp;amp;&amp;amp; !dev-&amp;gt;fwnode-&amp;gt;can_match)
        fw_devlink_unblock_consumers(dev);

    if (parent)
        klist_add_tail(&amp;amp;dev-&amp;gt;p-&amp;gt;knode_parent,
                   &amp;amp;parent-&amp;gt;p-&amp;gt;klist_children);

    sp = class_to_subsys(dev-&amp;gt;class);
    if (sp) {
        mutex_lock(&amp;amp;sp-&amp;gt;mutex);
        /* tie the class to the device */
        klist_add_tail(&amp;amp;dev-&amp;gt;p-&amp;gt;knode_class, &amp;amp;sp-&amp;gt;klist_devices);

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf, &amp;amp;sp-&amp;gt;interfaces, node)
            if (class_intf-&amp;gt;add_dev)
                class_intf-&amp;gt;add_dev(dev);
        mutex_unlock(&amp;amp;sp-&amp;gt;mutex);
        subsys_put(sp);
    }
done:
    put_device(dev);
    return error;
SysEntryError:
    if (MAJOR(dev-&amp;gt;devt))
        device_remove_file(dev, &amp;amp;dev_attr_dev);
DevAttrError:
    device_pm_remove(dev);
    dpm_sysfs_remove(dev);
DPMError:
    dev-&amp;gt;driver = NULL;
    bus_remove_device(dev);
BusError:
    device_remove_attrs(dev);
AttrsError:
    device_remove_class_symlinks(dev);
SymlinkError:
    device_remove_file(dev, &amp;amp;dev_attr_uevent);
attrError:
    device_platform_notify_remove(dev);
    kobject_uevent(&amp;amp;dev-&amp;gt;kobj, KOBJ_REMOVE);
    glue_dir = get_glue_dir(dev);
    kobject_del(&amp;amp;dev-&amp;gt;kobj);
Error:
    cleanup_glue_dir(dev, glue_dir);
parent_error:
    put_device(parent);
name_error:
    kfree(dev-&amp;gt;p);
    dev-&amp;gt;p = NULL;
    goto done;
}
EXPORT_SYMBOL_GPL(device_add);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그중에서 핵심은&lt;/p&gt;
&lt;pre id=&quot;code_1781425275037&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    bus_notify(dev, BUS_NOTIFY_ADD_DEVICE);
    kobject_uevent(&amp;amp;dev-&amp;gt;kobj, KOBJ_ADD);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&lt;/p&gt;
&lt;pre id=&quot;code_1781425285162&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bus_probe_device(dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;전체 호출 시퀀스 정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2273&quot; data-origin-height=&quot;1695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bceHnb/dJMcaiKomJc/vkPbtaHoUU8YYRCi3SVdL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bceHnb/dJMcaiKomJc/vkPbtaHoUU8YYRCi3SVdL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bceHnb/dJMcaiKomJc/vkPbtaHoUU8YYRCi3SVdL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbceHnb%2FdJMcaiKomJc%2FvkPbtaHoUU8YYRCi3SVdL0%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;2273&quot; height=&quot;1695&quot; data-origin-width=&quot;2273&quot; data-origin-height=&quot;1695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기까지가 virtio_pci_probe 함수 전체 분석이다. 정리하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1887&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGcGj6/dJMcac4vyrA/lgBYmEwpvEVyubyRdmjkXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGcGj6/dJMcac4vyrA/lgBYmEwpvEVyubyRdmjkXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGcGj6/dJMcac4vyrA/lgBYmEwpvEVyubyRdmjkXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGcGj6%2FdJMcac4vyrA%2FlgBYmEwpvEVyubyRdmjkXk%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;1887&quot; height=&quot;94&quot; data-origin-width=&quot;1887&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size23&quot;&gt;3) virtqueue 설정 (init_vqs &amp;middot; find_vqs &amp;middot; setup_vq &amp;middot; vring_create)&lt;/h3&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtballoon_probe 함수 분석&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;966&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btG9jH/dJMcaaZVYpv/3R85IJWDvYRTItxD94EQq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btG9jH/dJMcaaZVYpv/3R85IJWDvYRTItxD94EQq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btG9jH/dJMcaaZVYpv/3R85IJWDvYRTItxD94EQq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtG9jH%2FdJMcaaZVYpv%2F3R85IJWDvYRTItxD94EQq0%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;1355&quot; height=&quot;966&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;966&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이제 virtio balloon 디바이스의 초기화 과정을 예로 들어, virtio 디바이스의 초기화 과정을 분석한다. 즉 앞서 다룬 virtio 드라이버가 디바이스를 초기화하는 과정 중 &quot;(9) 디바이스 관련 초기화 작업 수행&quot; 단계다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(9) 디바이스 관련 초기화 작업 수행&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;디바이스 관련 초기화 작업에는 디바이스의 virtqueue 발견, virtio 디바이스의 설정 공간 읽기/쓰기 등이 포함된다. 이런 작업들은 모두 virtio_dev_probe 함수에서 드라이버의 probe 함수(즉 drv-&amp;gt;probe(dev))를 호출함으로써 완료된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425413962&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = drv-&amp;gt;probe(dev);
if (err)
    goto err;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio balloon의 경우에는 virtballoon_probe 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425428204&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int virtballoon_probe(struct virtio_device *vdev)
{
    struct virtio_balloon *vb;
    int err;

    if (!vdev-&amp;gt;config-&amp;gt;get) {
        dev_err(&amp;amp;vdev-&amp;gt;dev, &quot;%s failure: config access disabled\n&quot;,
            __func__);
        return -EINVAL;
    }

    vdev-&amp;gt;priv = vb = kzalloc(sizeof(*vb), GFP_KERNEL);
    if (!vb) {
        err = -ENOMEM;
        goto out;
    }

    INIT_WORK(&amp;amp;vb-&amp;gt;update_balloon_stats_work, update_balloon_stats_func);
    INIT_WORK(&amp;amp;vb-&amp;gt;update_balloon_size_work, update_balloon_size_func);
    spin_lock_init(&amp;amp;vb-&amp;gt;stop_update_lock);
    mutex_init(&amp;amp;vb-&amp;gt;balloon_lock);
    init_waitqueue_head(&amp;amp;vb-&amp;gt;acked);
    vb-&amp;gt;vdev = vdev;

    balloon_devinfo_init(&amp;amp;vb-&amp;gt;vb_dev_info);

    err = init_vqs(vb);
    if (err)
        goto out_free_vb;

#ifdef CONFIG_BALLOON_COMPACTION
    vb-&amp;gt;vb_dev_info.migratepage = virtballoon_migratepage;
#endif
    if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
        /*
         * There is always one entry reserved for cmd id, so the ring
         * size needs to be at least two to report free page hints.
         */
        if (virtqueue_get_vring_size(vb-&amp;gt;free_page_vq) &amp;lt; 2) {
            err = -ENOSPC;
            goto out_del_vqs;
        }
        vb-&amp;gt;balloon_wq = alloc_workqueue(&quot;balloon-wq&quot;,
                    WQ_FREEZABLE | WQ_CPU_INTENSIVE, 0);
        if (!vb-&amp;gt;balloon_wq) {
            err = -ENOMEM;
            goto out_del_vqs;
        }
        INIT_WORK(&amp;amp;vb-&amp;gt;report_free_page_work, report_free_page_func);
        vb-&amp;gt;cmd_id_received_cache = VIRTIO_BALLOON_CMD_ID_STOP;
        vb-&amp;gt;cmd_id_active = cpu_to_virtio32(vb-&amp;gt;vdev,
                        VIRTIO_BALLOON_CMD_ID_STOP);
        vb-&amp;gt;cmd_id_stop = cpu_to_virtio32(vb-&amp;gt;vdev,
                        VIRTIO_BALLOON_CMD_ID_STOP);
        spin_lock_init(&amp;amp;vb-&amp;gt;free_page_list_lock);
        INIT_LIST_HEAD(&amp;amp;vb-&amp;gt;free_page_list);
        /*
         * We're allowed to reuse any free pages, even if they are
         * still to be processed by the host.
         */
        err = virtio_balloon_register_shrinker(vb);
        if (err)
            goto out_del_balloon_wq;
    }

    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM)) {
        vb-&amp;gt;oom_nb.notifier_call = virtio_balloon_oom_notify;
        vb-&amp;gt;oom_nb.priority = VIRTIO_BALLOON_OOM_NOTIFY_PRIORITY;
        err = register_oom_notifier(&amp;amp;vb-&amp;gt;oom_nb);
        if (err &amp;lt; 0)
            goto out_unregister_shrinker;
    }

    if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_PAGE_POISON)) {
        /* Start with poison val of 0 representing general init */
        __u32 poison_val = 0;

        /*
         * Let the hypervisor know that we are expecting a
         * specific value to be written back in balloon pages.
         *
         * If the PAGE_POISON value was larger than a byte we would
         * need to byte swap poison_val here to guarantee it is
         * little-endian. However for now it is a single byte so we
         * can pass it as-is.
         */
        if (!want_init_on_free())
            memset(&amp;amp;poison_val, PAGE_POISON, sizeof(poison_val));

        virtio_cwrite_le(vb-&amp;gt;vdev, struct virtio_balloon_config,
                 poison_val, &amp;amp;poison_val);
    }

    vb-&amp;gt;pr_dev_info.report = virtballoon_free_page_report;
    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_REPORTING)) {
        unsigned int capacity;

        capacity = virtqueue_get_vring_size(vb-&amp;gt;reporting_vq);
        if (capacity &amp;lt; PAGE_REPORTING_CAPACITY) {
            err = -ENOSPC;
            goto out_unregister_oom;
        }

        /*
         * The default page reporting order is @pageblock_order, which
         * corresponds to 512MB in size on ARM64 when 64KB base page
         * size is used. The page reporting won't be triggered if the
         * freeing page can't come up with a free area like that huge.
         * So we specify the page reporting order to 5, corresponding
         * to 2MB. It helps to avoid THP splitting if 4KB base page
         * size is used by host.
         *
         * Ideally, the page reporting order is selected based on the
         * host's base page size. However, it needs more work to report
         * that value. The hard-coded order would be fine currently.
         */
#if defined(CONFIG_ARM64) &amp;amp;&amp;amp; defined(CONFIG_ARM64_64K_PAGES)
        vb-&amp;gt;pr_dev_info.order = 5;
#endif

        err = page_reporting_register(&amp;amp;vb-&amp;gt;pr_dev_info);
        if (err)
            goto out_unregister_oom;
    }

    virtio_device_ready(vdev);

    if (towards_target(vb))
        virtballoon_changed(vdev);
    return 0;

out_unregister_oom:
    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_DEFLATE_ON_OOM))
        unregister_oom_notifier(&amp;amp;vb-&amp;gt;oom_nb);
out_unregister_shrinker:
    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
        virtio_balloon_unregister_shrinker(vb);
out_del_balloon_wq:
    if (virtio_has_feature(vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
        destroy_workqueue(vb-&amp;gt;balloon_wq);
out_del_vqs:
    vdev-&amp;gt;config-&amp;gt;del_vqs(vdev);
out_free_vb:
    kfree(vb);
out:
    return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio balloon 디바이스는 virtio_balloon 구조체로 표현된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425455318&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_balloon {
    struct virtio_device *vdev;
    struct virtqueue *inflate_vq, *deflate_vq, *stats_vq, *free_page_vq;

    /* Balloon's own wq for cpu-intensive work items */
    struct workqueue_struct *balloon_wq;
    /* The free page reporting work item submitted to the balloon wq */
    struct work_struct report_free_page_work;

    /* The balloon servicing is delegated to a freezable workqueue. */
    struct work_struct update_balloon_stats_work;
    struct work_struct update_balloon_size_work;

    /* Prevent updating balloon when it is being canceled. */
    spinlock_t stop_update_lock;
    bool stop_update;
    /* Bitmap to indicate if reading the related config fields are needed */
    unsigned long config_read_bitmap;

    /* The list of allocated free pages, waiting to be given back to mm */
    struct list_head free_page_list;
    spinlock_t free_page_list_lock;
    /* The number of free page blocks on the above list */
    unsigned long num_free_page_blocks;
    /*
     * The cmd id received from host.
     * Read it via virtio_balloon_cmd_id_received to get the latest value
     * sent from host.
     */
    u32 cmd_id_received_cache;
    /* The cmd id that is actively in use */
    __virtio32 cmd_id_active;
    /* Buffer to store the sign stop */
    __virtio32 cmd_id_stop;

    /* Waiting for host to ack the pages we released. */
    wait_queue_head_t acked;

    /* Number of balloon pages we've told the Host we're not using. */
    unsigned int num_pages;
    /*
     * The pages we've told the Host we're not using are enqueued
     * at vb_dev_info-&amp;gt;pages list.
     * Each page on this list adds VIRTIO_BALLOON_PAGES_PER_PAGE
     * to num_pages above.
     */
    struct balloon_dev_info vb_dev_info;

    /* Synchronize access/update to this struct virtio_balloon elements */
    struct mutex balloon_lock;

    /* The array of pfns we tell the Host about. */
    unsigned int num_pfns;
    __virtio32 pfns[VIRTIO_BALLOON_ARRAY_PFNS_MAX];

    /* Memory statistics */
    struct virtio_balloon_stat stats[VIRTIO_BALLOON_S_NR];

    /* Shrinker to return free pages - VIRTIO_BALLOON_F_FREE_PAGE_HINT */
    struct shrinker *shrinker;

    /* OOM notifier to deflate on OOM - VIRTIO_BALLOON_F_DEFLATE_ON_OOM */
    struct notifier_block oom_nb;

    /* Free page reporting device */
    struct virtqueue *reporting_vq;
    struct page_reporting_dev_info pr_dev_info;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_balloon 구조체(struct virtio_balloon) 안에는 해당 virtio balloon 디바이스와 밀접하게 관련된 데이터 멤버들이 들어있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) virtballoon_probe 함수는 먼저 virtio_balloon 구조체 객체 하나를 할당한 후 vb에 부여하고, virtio_device의 priv에도 이 구조체 객체의 주소를 저장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425482894&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_balloon *vb;
&amp;hellip;&amp;hellip;
vdev-&amp;gt;priv = vb = kzalloc(sizeof(*vb), GFP_KERNEL);
if (!vb) {
    err = -ENOMEM;
    goto out;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(2) 이어서 할당된 virtio_balloon 객체의 멤버에 대한 초기화를 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 이 함수의 나머지 코드가 그것이다. 그중 두 개의 중요한 함수가 있는데, init_vqs()와 virtio_device_ready()다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(10) DRIVER_OK 상태 비트 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DRIVER_OK 상태 비트 설정은 보통 구체적인 디바이스 드라이버의 probe 함수에서, virtio_device_ready 함수를 호출함으로써 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio balloon 디바이스의 경우, 바로 위에서 본 virtballoon_probe 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425528893&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_device_ready(vdev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;앞의 함수 init_vqs는 virtqueue와 vring을 초기화하는 데 사용되며, virtio 드라이버와 virtio 디바이스는 virtqueue를 통해 데이터를 통신한다. 아래에서 init_vqs 함수를 자세히 분석한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio 전체 아키텍처&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br73TC/dJMcabLoiLa/di3poLTla3B6HDSiYRtZH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br73TC/dJMcabLoiLa/di3poLTla3B6HDSiYRtZH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br73TC/dJMcabLoiLa/di3poLTla3B6HDSiYRtZH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr73TC%2FdJMcabLoiLa%2Fdi3poLTla3B6HDSiYRtZH1%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;183&quot; height=&quot;643&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot; data-pm-slice=&quot;0 0 []&quot;&gt;init_vqs 함수 분석&lt;/h4&gt;
&lt;pre id=&quot;code_1781425568119&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int init_vqs(struct virtio_balloon *vb)
{
    struct virtqueue *vqs[VIRTIO_BALLOON_VQ_MAX];
    vq_callback_t *callbacks[VIRTIO_BALLOON_VQ_MAX];
    const char *names[VIRTIO_BALLOON_VQ_MAX];
    int err;

    /*
     * Inflateq and deflateq are used unconditionally. The names[]
     * will be NULL if the related feature is not enabled, which will
     * cause no allocation for the corresponding virtqueue in find_vqs.
     */
    callbacks[VIRTIO_BALLOON_VQ_INFLATE] = balloon_ack;
    names[VIRTIO_BALLOON_VQ_INFLATE] = &quot;inflate&quot;;
    callbacks[VIRTIO_BALLOON_VQ_DEFLATE] = balloon_ack;
    names[VIRTIO_BALLOON_VQ_DEFLATE] = &quot;deflate&quot;;
    callbacks[VIRTIO_BALLOON_VQ_STATS] = NULL;
    names[VIRTIO_BALLOON_VQ_STATS] = NULL;
    callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
    names[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
    names[VIRTIO_BALLOON_VQ_REPORTING] = NULL;

    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_STATS_VQ)) {
        names[VIRTIO_BALLOON_VQ_STATS] = &quot;stats&quot;;
        callbacks[VIRTIO_BALLOON_VQ_STATS] = stats_request;
    }

    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
        names[VIRTIO_BALLOON_VQ_FREE_PAGE] = &quot;free_page_vq&quot;;
        callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
    }

    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_REPORTING)) {
        names[VIRTIO_BALLOON_VQ_REPORTING] = &quot;reporting_vq&quot;;
        callbacks[VIRTIO_BALLOON_VQ_REPORTING] = balloon_ack;
    }

    err = virtio_find_vqs(vb-&amp;gt;vdev, VIRTIO_BALLOON_VQ_MAX, vqs,
                  callbacks, names, NULL);
    if (err)
        return err;

    vb-&amp;gt;inflate_vq = vqs[VIRTIO_BALLOON_VQ_INFLATE];
    vb-&amp;gt;deflate_vq = vqs[VIRTIO_BALLOON_VQ_DEFLATE];
    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_STATS_VQ)) {
        struct scatterlist sg;
        unsigned int num_stats;
        vb-&amp;gt;stats_vq = vqs[VIRTIO_BALLOON_VQ_STATS];

        /*
         * Prime this virtqueue with one buffer so the hypervisor can
         * use it to signal us later (it can't be broken yet!).
         */
        num_stats = update_balloon_stats(vb);

        sg_init_one(&amp;amp;sg, vb-&amp;gt;stats, sizeof(vb-&amp;gt;stats[0]) * num_stats);
        err = virtqueue_add_outbuf(vb-&amp;gt;stats_vq, &amp;amp;sg, 1, vb,
                       GFP_KERNEL);
        if (err) {
            dev_warn(&amp;amp;vb-&amp;gt;vdev-&amp;gt;dev, &quot;%s: add stat_vq failed\n&quot;,
                 __func__);
            return err;
        }
        virtqueue_kick(vb-&amp;gt;stats_vq);
    }

    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT))
        vb-&amp;gt;free_page_vq = vqs[VIRTIO_BALLOON_VQ_FREE_PAGE];

    if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_REPORTING))
        vb-&amp;gt;reporting_vq = vqs[VIRTIO_BALLOON_VQ_REPORTING];

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) init_vqs 함수는 먼저 callbacks 포인터 배열과 names 포인터 배열을 초기화한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781425587056&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtqueue *vqs[VIRTIO_BALLOON_VQ_MAX];
vq_callback_t *callbacks[VIRTIO_BALLOON_VQ_MAX];
const char *names[VIRTIO_BALLOON_VQ_MAX];
int err;

/*
 * Inflateq and deflateq are used unconditionally. The names[]
 * will be NULL if the related feature is not enabled, which will
 * cause no allocation for the corresponding virtqueue in find_vqs.
 */
callbacks[VIRTIO_BALLOON_VQ_INFLATE] = balloon_ack;
names[VIRTIO_BALLOON_VQ_INFLATE] = &quot;inflate&quot;;
callbacks[VIRTIO_BALLOON_VQ_DEFLATE] = balloon_ack;
names[VIRTIO_BALLOON_VQ_DEFLATE] = &quot;deflate&quot;;
callbacks[VIRTIO_BALLOON_VQ_STATS] = NULL;
names[VIRTIO_BALLOON_VQ_STATS] = NULL;
callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
names[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
names[VIRTIO_BALLOON_VQ_REPORTING] = NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;먼저 VIRTIO_BALLOON_VQ_STATS의 정의부터 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781425602940&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum virtio_balloon_vq {
    VIRTIO_BALLOON_VQ_INFLATE,
    VIRTIO_BALLOON_VQ_DEFLATE,
    VIRTIO_BALLOON_VQ_STATS,
    VIRTIO_BALLOON_VQ_FREE_PAGE,
    VIRTIO_BALLOON_VQ_REPORTING,
    VIRTIO_BALLOON_VQ_MAX
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;정의에서 알 수 있듯이,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VIRTIO_BALLOON_VQ_INFLATE의 값은 0, VIRTIO_BALLOON_VQ_DEFLATE의 값은 1, VIRTIO_BALLOON_VQ_STATS의 값은 2, VIRTIO_BALLOON_VQ_FREE_PAGE의 값은 3, VIRTIO_BALLOON_VQ_REPORTING의 값은 4, VIRTIO_BALLOON_VQ_MAX의 값은 5다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 주석에 따르면, inflateq와 deflateq는 무조건 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 관련 feature가 활성화되지 않으면 대응하는 names[i]는 NULL이 될 것이고, 이것은 find_vqs 함수에서 해당하는 virtqueue를 할당하지 않게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석을 보면 뒤의 코드도 이해할 수 있다. inldateq와 deflateq는 무조건 사용되기 때문에, 이들이 대응하는 names[VIRTIO_BALLOON_VQ_INFLATE](즉 names[0])와 names[VIRTIO_BALLOON_VQ_DEFLATE](즉 names[1])는 처음부터 NULL이 아니라 각각 &quot;inflate&quot;와 &quot;deflate&quot;로 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시에 각각의 callback도 모두 balloon_ack로 설정된다.&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;(2) 이어서 나머지 VIRTIO_BALLOON_F_STATS_VQ、VIRTIO_BALLOON_VQ_FREE_PAGE、VIRTIO_BALLOON_VQ_REPORTING 특성이 존재하는지 판단한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781425653357&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_STATS_VQ)) {
    names[VIRTIO_BALLOON_VQ_STATS] = &quot;stats&quot;;
    callbacks[VIRTIO_BALLOON_VQ_STATS] = stats_request;
}

if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
    names[VIRTIO_BALLOON_VQ_FREE_PAGE] = &quot;free_page_vq&quot;;
    callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
}

if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_REPORTING)) {
    names[VIRTIO_BALLOON_VQ_REPORTING] = &quot;reporting_vq&quot;;
    callbacks[VIRTIO_BALLOON_VQ_REPORTING] = balloon_ack;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위에서 설명한 대로, 이 세 항목은 모두 무조건 사용되는 feature가 아니다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그래서 처음 초기화될 때는 기본적으로 사용 안 함으로 간주되어, 대응하는 names[i]와 callbacks[i]를 모두 NULL로 설정한 것이다. 그러고 나서 여기서 해당 특성이 활성화되었는지 판단하는데, 어떤 항목이 활성화되었으면 그 names[i]를 해당 값으로 설정한다. 물론 callbacks[i]도 마찬가지다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;callbacks/names 배열 초기화 상태 정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhBej0/dJMcaci6Xxm/TUdvyaZkB31ZwsbTCgDiK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhBej0/dJMcaci6Xxm/TUdvyaZkB31ZwsbTCgDiK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhBej0/dJMcaci6Xxm/TUdvyaZkB31ZwsbTCgDiK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhBej0%2FdJMcaci6Xxm%2FTUdvyaZkB31ZwsbTCgDiK0%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;437&quot; height=&quot;365&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781425679570&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * virtio_has_feature - helper to determine if this device has this feature.
 * @vdev: the device
 * @fbit: the feature bit
 */
static inline bool virtio_has_feature(const struct virtio_device *vdev,
                      unsigned int fbit)
{
    if (fbit &amp;lt; VIRTIO_TRANSPORT_F_START)
        virtio_check_driver_offered_feature(vdev, fbit);

    return __virtio_test_bit(vdev, fbit);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781425689740&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * Virtio feature bits VIRTIO_TRANSPORT_F_START through
 * VIRTIO_TRANSPORT_F_END are reserved for the transport
 * being used (e.g. virtio_ring, virtio_pci etc.), the
 * rest are per-device feature bits.
 */
#define VIRTIO_TRANSPORT_F_START    28
#define VIRTIO_TRANSPORT_F_END      41&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서 virtio_has_feature 함수의 매개변수 fbit에 대응하는 실제 인자는 각각 VIRTIO_BALLOON_VQ_STATS (2), VIRTIO_BALLOON_VQ_FREE_PAGE (3), VIRTIO_BALLOON_VQ_REPORTING (4)인데, 모두 VIRTIO_TRANSPORT_F_START (28)보다 작다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그래서 모두 virtio_check_driver_offered_feature 함수를 거치게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425715825&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void virtio_check_driver_offered_feature(const struct virtio_device *vdev,
                     unsigned int fbit)
{
    unsigned int i;
    struct virtio_driver *drv = drv_to_virtio(vdev-&amp;gt;dev.driver);

    for (i = 0; i &amp;lt; drv-&amp;gt;feature_table_size; i++)
        if (drv-&amp;gt;feature_table[i] == fbit)
            return;

    if (drv-&amp;gt;feature_table_legacy) {
        for (i = 0; i &amp;lt; drv-&amp;gt;feature_table_size_legacy; i++)
            if (drv-&amp;gt;feature_table_legacy[i] == fbit)
                return;
    }

    BUG();
}
EXPORT_SYMBOL_GPL(virtio_check_driver_offered_feature);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_check_driver_offered_feature 함수는 vdev(struct virtio_device 타입)에 해당하는 drv(struct virtio_driver 타입)의 feature_table[]에 대응되는 항목이 있는지 검사한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;있으면 무탈하게 진행되고, 없으면 BUG()를 보고한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_check_driver_offered_feature 함수의 검사를 거친 후, virtio_has_feature 함수는 __virtio_test_bit 함수를 호출하고 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781425772684&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * __virtio_test_bit - helper to test feature bits. For use by transports.
 *                     Devices should normally use virtio_has_feature,
 *                     which includes more checks.
 * @vdev: the device
 * @fbit: the feature bit
 */
static inline bool __virtio_test_bit(const struct virtio_device *vdev,
                     unsigned int fbit)
{
    /* Did you forget to fix assumptions on max features? */
    if (__builtin_constant_p(fbit))
        BUILD_BUG_ON(fbit &amp;gt;= 64);
    else
        BUG_ON(fbit &amp;gt;= 64);

    return vdev-&amp;gt;features &amp;amp; BIT_ULL(fbit);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 함수는 비교적 이해하기 쉽다. vdev-&amp;gt;feature에서 해당 비트가 1로 설정되어 있는지 검사하는 것이다. 1로 설정되어 있으면 해당 feature가 이미 활성화된 것이므로 참(True)을 반환하고, 그렇지 않으면 해당 feature가 활성화되지 않은 것이므로 거짓(False)을 반환한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 init_vqs 함수로 돌아온다. 각 feature에 대한 초기화, 검사 및 설정을 거친 후, 이어서 init_vqs 함수는 virtio_find_vqs 함수를 호출하는데, 이 함수에 대한 분석은 아래에서 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;virtio_find_vqs 함수 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 절에서는 virtballoon_probe 함수와 그 안의 두 가지 중요한 함수 init_vqs()와 virtio_device_ready()를 설명했고, init_vqs 함수의 앞 두 단계까지 분석했다. 여기서는 이 함수에 대한 분석을 계속한다.&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;(3) init_vqs 함수는 각 feature에 대한 초기화, 검사, 설정을 마친 후, 이어서 virtio_find_vqs 함수를 호출한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781425998423&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = virtio_find_vqs(vb-&amp;gt;vdev, VIRTIO_BALLOON_VQ_MAX, vqs,
            callbacks, names, NULL);
if (err)
    return err;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781426011248&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline
int virtio_find_vqs(struct virtio_device *vdev, unsigned nvqs,
            struct virtqueue *vqs[], vq_callback_t *callbacks[],
            const char * const names[],
            struct irq_affinity *desc)
{
    return vdev-&amp;gt;config-&amp;gt;find_vqs(vdev, nvqs, vqs, callbacks, names, NULL, desc);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;사실 구 버전 코드에서는 init_vqs 함수 안에서 virtio_config_ops의 find_vqs 콜백을 직접 호출했었는데, 새 버전 코드에서는 그걸 virtio_find_vqs로 감싸 둔 거다.&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;virtio_pci_device 핵심 데이터 구조 관계도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2378&quot; data-origin-height=&quot;1613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuzY4x/dJMcagZ7cgW/h1LKxvM4kMqfUZymixwil1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuzY4x/dJMcagZ7cgW/h1LKxvM4kMqfUZymixwil1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuzY4x/dJMcagZ7cgW/h1LKxvM4kMqfUZymixwil1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuzY4x%2FdJMcagZ7cgW%2Fh1LKxvM4kMqfUZymixwil1%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;2378&quot; height=&quot;1613&quot; data-origin-width=&quot;2378&quot; data-origin-height=&quot;1613&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_config_ops의 초기화는 두 군데가 있는데,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;/drivers/virtio/virtio_pci_legacy.c&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;/drivers/virtio/virtio_pci_modern.c&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;modern 쪽을 예로 들어보면, struct virtio_config_ops virtio_pci_config_ops 안에서 find_vqs 멤버에 대응되는 함수는 vp_modern_find_vqs()다.&lt;/p&gt;
&lt;pre id=&quot;code_1781426147721&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int vp_modern_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
                  struct virtqueue *vqs[],
                  vq_callback_t *callbacks[],
                  const char * const names[], const bool *ctx,
                  struct irq_affinity *desc)
{
    struct virtio_pci_device *vp_dev = to_vp_device(vdev);
    struct virtqueue *vq;
    int rc = vp_find_vqs(vdev, nvqs, vqs, callbacks, names, ctx, desc);

    if (rc)
        return rc;

    /* Select and activate all queues. Has to be done last: once we do
     * this, there's no way to go back except reset.
     */
    list_for_each_entry(vq, &amp;amp;vdev-&amp;gt;vqs, list)
        vp_modern_set_queue_enable(&amp;amp;vp_dev-&amp;gt;mdev, vq-&amp;gt;index, true);

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781426161581&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = virtio_find_vqs(vb-&amp;gt;vdev, VIRTIO_BALLOON_VQ_MAX, vqs,
            callbacks, names, NULL);
if (err)
    return err;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_modern_find_vqs 함수는 동일한 매개변수로 vp_find_vqs 함수를 호출했다.&lt;/p&gt;
&lt;pre id=&quot;code_1781426180682&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int rc = vp_find_vqs(vdev, nvqs, vqs, callbacks, names, ctx, desc);

if (rc)
    return rc;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781426191348&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* the config-&amp;gt;find_vqs() implementation */
int vp_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
        struct virtqueue *vqs[], vq_callback_t *callbacks[],
        const char * const names[], const bool *ctx,
        struct irq_affinity *desc)
{
    int err;

    /* Try MSI-X with one vector per queue. */
    err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);
    if (!err)
        return 0;
    /* Fallback: MSI-X with one vector for config, one shared for queues. */
    err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);
    if (!err)
        return 0;
    /* Is there an interrupt? If not give up. */
    if (!(to_vp_device(vdev)-&amp;gt;pci_dev-&amp;gt;irq))
        return err;
    /* Finally fall back to regular interrupts. */
    return vp_find_vqs_intx(vdev, nvqs, vqs, callbacks, names, ctx);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;구 버전 Linux 커널의 KVM 코드에서는 vp_find_vqs 함수가 본질적으로는 vp_try_to_find_ops() 함수 하나만 호출했었다. 그런데 새 버전 코드에서는 변화가 좀 커서, 주로 두 개의 함수, 즉 vp_find_vqs_msix()와 vp_find_vqs_intx()를 호출하게 됐다.&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;vp_find_vqs 인터럽트 방식 선택 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uSjpX/dJMcaip8nXE/VMkLSRJlAdhgrKTsAkoxpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uSjpX/dJMcaip8nXE/VMkLSRJlAdhgrKTsAkoxpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uSjpX/dJMcaip8nXE/VMkLSRJlAdhgrKTsAkoxpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuSjpX%2FdJMcaip8nXE%2FVMkLSRJlAdhgrKTsAkoxpK%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;444&quot; height=&quot;516&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781426215986&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int vp_find_vqs_msix(struct virtio_device *vdev, unsigned int nvqs,
        struct virtqueue *vqs[],
        vq_callback_t *callbacks[],
        const char * const names[], bool per_vq_vectors,
        const bool *ctx,
        struct irq_affinity *desc)
{
    struct virtio_pci_device *vp_dev = to_vp_device(vdev);
    u16 msix_vec;
    int i, err, nvectors, allocated_vectors, queue_idx = 0;

    vp_dev-&amp;gt;vqs = kcalloc(nvqs, sizeof(*vp_dev-&amp;gt;vqs), GFP_KERNEL);
    if (!vp_dev-&amp;gt;vqs)
        return -ENOMEM;

    if (per_vq_vectors) {
        /* Best option: one for change interrupt, one per vq. */
        nvectors = 1;
        for (i = 0; i &amp;lt; nvqs; ++i)
            if (names[i] &amp;amp;&amp;amp; callbacks[i])
                ++nvectors;
    } else {
        /* Second best: one for change, shared for all vqs. */
        nvectors = 2;
    }

    err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors,
                      per_vq_vectors ? desc : NULL);
    if (err)
        goto error_find;

    vp_dev-&amp;gt;per_vq_vectors = per_vq_vectors;
    allocated_vectors = vp_dev-&amp;gt;msix_used_vectors;
    for (i = 0; i &amp;lt; nvqs; ++i) {
        if (!names[i]) {
            vqs[i] = NULL;
            continue;
        }

        if (!callbacks[i])
            msix_vec = VIRTIO_MSI_NO_VECTOR;
        else if (vp_dev-&amp;gt;per_vq_vectors)
            msix_vec = allocated_vectors++;
        else
            msix_vec = VP_MSIX_VQ_VECTOR;
        vqs[i] = vp_setup_vq(vdev, queue_idx++, callbacks[i], names[i],
                     ctx ? ctx[i] : false,
                     msix_vec);
        if (IS_ERR(vqs[i])) {
            err = PTR_ERR(vqs[i]);
            goto error_find;
        }

        if (!vp_dev-&amp;gt;per_vq_vectors || msix_vec == VIRTIO_MSI_NO_VECTOR)
            continue;

        /* allocate per-vq irq if available and necessary */
        snprintf(vp_dev-&amp;gt;msix_names[msix_vec],
             sizeof *vp_dev-&amp;gt;msix_names,
             &quot;%s-%s&quot;,
             dev_name(&amp;amp;vp_dev-&amp;gt;vdev.dev), names[i]);
        err = request_irq(pci_irq_vector(vp_dev-&amp;gt;pci_dev, msix_vec),
                  vring_interrupt, 0,
                  vp_dev-&amp;gt;msix_names[msix_vec],
                  vqs[i]);
        if (err)
            goto error_find;
    }
    return 0;

error_find:
    vp_del_vqs(vdev);
    return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;코드를 봐서는, vp_find_vqs_msix 함수는 구 버전 KVM 코드의 vp_try_to_find_ops 함수와 매우 비슷하다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) vp_find_vqs_msix 함수는 먼저 kcalloc 함수를 통해 struct virtio_pci_vq_info를 가리키는 포인터 nvqs개를 할당하고, virtio_pci_device의 vqs 멤버에 부여한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(struct virtio_pci_vq_info **vqs;) 각 virtio_pci_vq_info는 virtqueue의 정보를 기록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781426246699&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vp_dev-&amp;gt;vqs = kcalloc(nvqs, sizeof(*vp_dev-&amp;gt;vqs), GFP_KERNEL);
if (!vp_dev-&amp;gt;vqs)
    return -ENOMEM;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;nvqs의 실제 인자는 VIRTIO_BALLOON_VQ_MAX로, 앞 절에서 말했듯 이 값은 5다.&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;(2) vp_find_vqs_msix 함수는 이어서 nvectors를 계산한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781426272785&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (per_vq_vectors) {
    /* Best option: one for change interrupt, one per vq. */
    nvectors = 1;
    for (i = 0; i &amp;lt; nvqs; ++i)
        if (names[i] &amp;amp;&amp;amp; callbacks[i])
            ++nvectors;
} else {
    /* Second best: one for change, shared for all vqs. */
    nvectors = 2;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;nvectors는 총 필요한 MSIx vector를 나타낸다. vp_find_vqs 함수가 vp_find_vqs_msix 함수를 두 번 호출하기 때문에,&lt;/p&gt;
&lt;pre id=&quot;code_1781426287364&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Try MSI-X with one vector per queue. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);
if (!err)
    return 0;
/* Fallback: MSI-X with one vector for config, one shared for queues. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);
if (!err)
    return 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그래서 여기서 앞 번 호출로 얻은 nvqs 값은 [2,4] 중 하나가 된다(이전 글에서 말한 조건부 feature가 활성화되었는지 여부에 따라 결정된다). 그리고 뒤 번 호출로 얻은 nvqs 값은 2다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 분석을 계속하기 전에, virtio의 인터럽트에 대해 먼저 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio에는 두 가지 인터럽트 타입이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;change 인터럽트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스의 설정 정보가 변경되면(config가 변경됨), 인터럽트가 하나 발생하는데(change 인터럽트라고 부른다), 인터럽트 처리 프로그램은 대응되는 처리 함수를 호출해야 한다(드라이버가 정의해야 함).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;vq 인터럽트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스가 큐에 정보를 쓸 때, 인터럽트가 하나 발생하는데(vq 인터럽트라고 부른다), 인터럽트 처리 함수는 대응하는 큐의 콜백 함수를 호출해야 한다(드라이버가 정의해야 함).&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;virtio에는 세 가지 인터럽트 처리 방식이 있다.&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;virtio 인터럽트 처리 방식 비교&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1949&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCFG2y/dJMcabExbpU/U9Oz7Krq7SzgLzH27A5lj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCFG2y/dJMcabExbpU/U9Oz7Krq7SzgLzH27A5lj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCFG2y/dJMcabExbpU/U9Oz7Krq7SzgLzH27A5lj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCFG2y%2FdJMcabExbpU%2FU9Oz7Krq7SzgLzH27A5lj1%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;835&quot; height=&quot;235&quot; data-origin-width=&quot;1949&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) msix 인터럽트를 사용하지 않고, 일반 인터럽트를 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;change 인터럽트와 모든 vq 인터럽트가 하나의 인터럽트 irq를 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터럽트 처리 함수는 vp_interrupt이고, vp_interrupt 함수 안에는 change 인터럽트와 vq 인터럽트의 처리가 포함되어 있다.&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;2) msix 인터럽트를 사용하지만, vector가 두 개만 있는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 vector 중, 하나는 change 인터럽트에 대응하고, 다른 하나는 모든 큐의 vq 인터럽트에 대응한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;change 인터럽트 처리 함수는 vp_config_changed이고, vq 인터럽트 처리 함수는 vp_vring_interrupt다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대응되는 게 vp_find_vqs 함수가 vp_find_vqs_msix 함수를 두 번째로 호출하는 코드다&lt;/p&gt;
&lt;pre id=&quot;code_1781426383194&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Fallback: MSI-X with one vector for config, one shared for queues. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);
if (!err)
    return 0;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781426391916&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;else {
    /* Second best: one for change, shared for all vqs. */
    nvectors = 2;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) msix 인터럽트를 사용하고, n+1개의 vector가 있는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n+1개 vector 중, 하나는 change 인터럽트에 대응하고, n개는 각각 n개 큐의 vq 인터럽트에 대응한다. 즉 각 vq가 하나의 vector를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;change 인터럽트 처리 함수는 vp_config_changed이고, vq 인터럽트 처리 함수는 vring_interrupt다.&lt;/p&gt;
&lt;pre id=&quot;code_1781426411311&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum virtio_balloon_vq {
    VIRTIO_BALLOON_VQ_INFLATE,
    VIRTIO_BALLOON_VQ_DEFLATE,
    VIRTIO_BALLOON_VQ_STATS,
    VIRTIO_BALLOON_VQ_FREE_PAGE,
    VIRTIO_BALLOON_VQ_REPORTING,
    VIRTIO_BALLOON_VQ_MAX
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;대응되는 게 vp_find_vqs 함수가 vp_find_vqs_msix 함수를 첫 번째로 호출하는 코드다&lt;/p&gt;
&lt;pre id=&quot;code_1781426428404&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Try MSI-X with one vector per queue. */
err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);
if (!err)
    return 0;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781426435875&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (per_vq_vectors) {
    /* Best option: one for change interrupt, one per vq. */
    nvectors = 1;
    for (i = 0; i &amp;lt; nvqs; ++i)
        if (names[i] &amp;amp;&amp;amp; callbacks[i])
            ++nvectors;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_find_vqs 함수의 호출 순서에 따라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 인터럽트 처리 방식 3(msix 인터럽트 사용, n+1개 vector)을 사용해보고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 방식 2(msix 인터럽트 사용하지만 vector가 두 개만 있는 경우),&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 방식 1(msix 인터럽트를 사용하지 않고 일반 인터럽트 사용)을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) vp_find_vqs_msix 함수는 이어서 vp_request_msix_vectors 함수를 호출한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781426516103&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors,
                  per_vq_vectors ? desc : NULL);
if (err)
    goto error_find;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781426525181&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
                   bool per_vq_vectors, struct irq_affinity *desc)
{
    struct virtio_pci_device *vp_dev = to_vp_device(vdev);
    const char *name = dev_name(&amp;amp;vp_dev-&amp;gt;vdev.dev);
    unsigned int flags = PCI_IRQ_MSIX;
    unsigned int i, v;
    int err = -ENOMEM;

    vp_dev-&amp;gt;msix_vectors = nvectors;

    vp_dev-&amp;gt;msix_names = kmalloc_array(nvectors,
                       sizeof(*vp_dev-&amp;gt;msix_names),
                       GFP_KERNEL);
    if (!vp_dev-&amp;gt;msix_names)
        goto error;
    vp_dev-&amp;gt;msix_affinity_masks
        = kcalloc(nvectors, sizeof(*vp_dev-&amp;gt;msix_affinity_masks),
              GFP_KERNEL);
    if (!vp_dev-&amp;gt;msix_affinity_masks)
        goto error;
    for (i = 0; i &amp;lt; nvectors; ++i)
        if (!alloc_cpumask_var(&amp;amp;vp_dev-&amp;gt;msix_affinity_masks[i],
                       GFP_KERNEL))
            goto error;

    if (desc) {
        flags |= PCI_IRQ_AFFINITY;
        desc-&amp;gt;pre_vectors++; /* virtio config vector */
    }

    err = pci_alloc_irq_vectors_affinity(vp_dev-&amp;gt;pci_dev, nvectors,
                         nvectors, flags, desc);
    if (err &amp;lt; 0)
        goto error;
    vp_dev-&amp;gt;msix_enabled = 1;

    /* Set the vector used for configuration */
    v = vp_dev-&amp;gt;msix_used_vectors;
    snprintf(vp_dev-&amp;gt;msix_names[v], sizeof *vp_dev-&amp;gt;msix_names,
         &quot;%s-config&quot;, name);
    err = request_irq(pci_irq_vector(vp_dev-&amp;gt;pci_dev, v),
              vp_config_changed, 0, vp_dev-&amp;gt;msix_names[v],
              vp_dev);
    if (err)
        goto error;
    ++vp_dev-&amp;gt;msix_used_vectors;

    v = vp_dev-&amp;gt;config_vector(vp_dev, v);
    /* Verify we had enough resources to assign the vector */
    if (v == VIRTIO_MSI_NO_VECTOR) {
        err = -EBUSY;
        goto error;
    }

    if (!per_vq_vectors) {
        /* Shared vector for all VQs */
        v = vp_dev-&amp;gt;msix_used_vectors;
        snprintf(vp_dev-&amp;gt;msix_names[v], sizeof *vp_dev-&amp;gt;msix_names,
             &quot;%s-virtqueues&quot;, name);
        err = request_irq(pci_irq_vector(vp_dev-&amp;gt;pci_dev, v),
                  vp_vring_interrupt, 0, vp_dev-&amp;gt;msix_names[v],
                  vp_dev);
        if (err)
            goto error;
        ++vp_dev-&amp;gt;msix_used_vectors;
    }
    return 0;
error:
    return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;핵심 함수 호출 순서 정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mRpFL/dJMcahx00rq/w7BIUl4hoRjA7xxTF1vKX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mRpFL/dJMcahx00rq/w7BIUl4hoRjA7xxTF1vKX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mRpFL/dJMcahx00rq/w7BIUl4hoRjA7xxTF1vKX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmRpFL%2FdJMcahx00rq%2Fw7BIUl4hoRjA7xxTF1vKX0%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;2000&quot; height=&quot;1678&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기까지가 virtio balloon 디바이스의 초기화 과정 전체 분석이다. 핵심은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;virtballoon_probe() 가 디바이스별 진입점이고, virtio_balloon 구조체를 할당/초기화한다.&lt;/li&gt;
&lt;li&gt;init_vqs() 가 virtqueue 배열을 준비하는데, inflateq/deflateq는 무조건 사용되고 나머지 세 개(stats, free_page, reporting)는 feature 활성화 여부에 따라 결정된다.&lt;/li&gt;
&lt;li&gt;virtio_find_vqs() &amp;rarr; vp_modern_find_vqs() &amp;rarr; vp_find_vqs() &amp;rarr; vp_find_vqs_msix() 의 호출 체인을 거치는데, 이 중에서 인터럽트 방식 3 &amp;rarr; 2 &amp;rarr; 1 순으로 시도한다.&lt;/li&gt;
&lt;li&gt;vp_request_msix_vectors() 가 실제로 MSI-X vector를 할당하고 IRQ를 등록한다. 이 함수에 대한 자세한 분석은 다음 회로 미뤄진다.&lt;/li&gt;
&lt;li&gt;모든 초기화가 끝나면 virtio_device_ready() 로 DRIVER_OK 비트를 세팅해서 디바이스가 사용 가능 상태가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;vp_find_vqs_msix 와 vp_setup_vq&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 init_vqs 함수에서 호출한 virtio_find_vqs 함수를 따라 들어가서, virtio_find_vqs 함수 안에서 호출하는 vdev-&amp;gt;config-&amp;gt;find_vqs(vdev, nvqs, vqs, callbacks, names, NULL, desc) (즉 virtio_config_ops의 find_vqs 콜백)를 통해, 이 콜백이 가리키는 함수 vp_modern_find_vqs()까지 따라갔고, 다시 그것이 소속된 vp_find_vqs 함수까지 따라간다. 호출 흐름은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blf8j4/dJMb99NrZJf/p4W1YnGzI5k69b9AiAKBLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blf8j4/dJMb99NrZJf/p4W1YnGzI5k69b9AiAKBLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blf8j4/dJMb99NrZJf/p4W1YnGzI5k69b9AiAKBLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblf8j4%2FdJMb99NrZJf%2Fp4W1YnGzI5k69b9AiAKBLK%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;182&quot; height=&quot;356&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_find_vqs 함수는 인터럽트 방식을 세 가지 우선순위로 시도한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z7xug/dJMcadI2Oeu/06Hk7HjbbjlqLoPsL1aS30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z7xug/dJMcadI2Oeu/06Hk7HjbbjlqLoPsL1aS30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z7xug/dJMcadI2Oeu/06Hk7HjbbjlqLoPsL1aS30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ7xug%2FdJMcadI2Oeu%2F06Hk7HjbbjlqLoPsL1aS30%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;399&quot; height=&quot;497&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 vp_find_vqs_msix 함수까지 이야기했고, 그중에서 앞 두 단계를 다뤘다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;세 번째 단계에서 vp_request_msix_vectors 함수를 호출하는 부분에 이르러서, 다시 한 층 더 따라 들어갔다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이제부터 vp_request_msix_vectors 함수에 대해 자세히 분석한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781426743523&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
                   bool per_vq_vectors)
{
    struct virtio_pci_device *vp_dev = to_vp_device(vdev);
    const char *name = dev_name(&amp;amp;vp_dev-&amp;gt;vdev.dev);
    unsigned i, v;
    int err = -ENOMEM;

    vp_dev-&amp;gt;msix_vectors = nvectors;

    vp_dev-&amp;gt;msix_entries = kmalloc(nvectors * sizeof *vp_dev-&amp;gt;msix_entries,
                       GFP_KERNEL);
    if (!vp_dev-&amp;gt;msix_entries)
        goto error;
    vp_dev-&amp;gt;msix_names = kmalloc(nvectors * sizeof *vp_dev-&amp;gt;msix_names,
                     GFP_KERNEL);
    if (!vp_dev-&amp;gt;msix_names)
        goto error;
    vp_dev-&amp;gt;msix_affinity_masks
        = kzalloc(nvectors * sizeof *vp_dev-&amp;gt;msix_affinity_masks,
              GFP_KERNEL);
    if (!vp_dev-&amp;gt;msix_affinity_masks)
        goto error;
    for (i = 0; i &amp;lt; nvectors; ++i)
        if (!alloc_cpumask_var(&amp;amp;vp_dev-&amp;gt;msix_affinity_masks[i],
                       GFP_KERNEL))
            goto error;

    for (i = 0; i &amp;lt; nvectors; ++i)
        vp_dev-&amp;gt;msix_entries[i].entry = i;

    err = pci_enable_msix_exact(vp_dev-&amp;gt;pci_dev,
                    vp_dev-&amp;gt;msix_entries, nvectors);
    if (err)
        goto error;
    vp_dev-&amp;gt;msix_enabled = 1;

    /* Set the vector used for configuration */
    v = vp_dev-&amp;gt;msix_used_vectors;
    snprintf(vp_dev-&amp;gt;msix_names[v], sizeof *vp_dev-&amp;gt;msix_names,
         &quot;%s-config&quot;, name);
    err = request_irq(vp_dev-&amp;gt;msix_entries[v].vector,
              vp_config_changed, 0, vp_dev-&amp;gt;msix_names[v],
              vp_dev);
    if (err)
        goto error;
    ++vp_dev-&amp;gt;msix_used_vectors;

    v = vp_dev-&amp;gt;config_vector(vp_dev, v);
    /* Verify we had enough resources to assign the vector */
    if (v == VIRTIO_MSI_NO_VECTOR) {
        err = -EBUSY;
        goto error;
    }

    if (!per_vq_vectors) {
        /* Shared vector for all VQs */
        v = vp_dev-&amp;gt;msix_used_vectors;
        snprintf(vp_dev-&amp;gt;msix_names[v], sizeof *vp_dev-&amp;gt;msix_names,
             &quot;%s-virtqueues&quot;, name);
        err = request_irq(vp_dev-&amp;gt;msix_entries[v].vector,
                  vp_vring_interrupt, 0, vp_dev-&amp;gt;msix_names[v],
                  vp_dev);
        if (err)
            goto error;
        ++vp_dev-&amp;gt;msix_used_vectors;
    }
    return 0;
error:
    vp_free_vectors(vdev);
    return err;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;옛 버전과 새 버전 vp_request_msix_vectors 함수의 가장 큰 차이는, 새 버전에서는 vp_dev-&amp;gt;msix_entries 관련 코드가 사라졌다는 점이다. 이는 새 버전의 virtio_pci_device 구조체 안에 더 이상 msix_entries라는 멤버가 없기 때문에 그런 것이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛 버전의 vp_request_msix_vectors 함수는 다음 작업을 완료했다 (virtio 디바이스 인터럽트 분석 참고).&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;nvectors 개의 msix 인터럽트에 vector를 할당하고, 그중 1개의 vector를 vp_config_changed라는 change 인터럽트 처리 함수를 지정하는 데 사용한다.&lt;/li&gt;
&lt;li&gt;만약 per_vq_vectors가 false라면, nvectors는 곧 2가 되고, 남는 또 하나의 vector를 n개 vq에서 공유로 사용하는 vq 인터럽트 처리 함수 vp_vring_interrupt를 지정하는 데 쓴다.&lt;/li&gt;
&lt;li&gt;만약 per_vq_vectors가 true라면, 아래쪽 코드에서 매 개 vq별로 하나씩 vector를 지정하고, vq 인터럽트 처리 함수를 vring_interrupt로 지정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;801&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcbjMq/dJMcadI2OeO/cSkUU9reKqoBa59pNndLQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcbjMq/dJMcadI2OeO/cSkUU9reKqoBa59pNndLQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcbjMq/dJMcadI2OeO/cSkUU9reKqoBa59pNndLQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcbjMq%2FdJMcadI2OeO%2FcSkUU9reKqoBa59pNndLQK%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;334&quot; height=&quot;514&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;801&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;새 버전과 옛 버전 코드의 차이가 기능적으로는 큰 연관이 없기 때문에, 신/구 버전 vp_request_msix_vectors 함수의 기능은 위에서 서술한 것과 기본적으로 일치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) vp_find_vqs_msix 함수&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781427061722&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vp_dev-&amp;gt;per_vq_vectors = per_vq_vectors;
allocated_vectors = vp_dev-&amp;gt;msix_used_vectors;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 코드의 뜻은 vp_find_vqs_msix 함수의 매개변수 bool per_vq_vectors를 vp_dev(virtio PCI 디바이스)의 per_vq_vectors 멤버에 할당하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 코드는 vp_dev-&amp;gt;msix_used_vectors 즉 사용된 인터럽트 벡터 개수를 allocated_vectors에 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vp_dev-&amp;gt;msix_used_vectors는 위쪽의 vp_request_msix_vectors 함수에서 생성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427099733&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Set the vector used for configuration */
v = vp_dev-&amp;gt;msix_used_vectors;
snprintf(vp_dev-&amp;gt;msix_names[v], sizeof *vp_dev-&amp;gt;msix_names,
     &quot;%s-config&quot;, name);
err = request_irq(pci_irq_vector(vp_dev-&amp;gt;pci_dev, v),
          vp_config_changed, 0, vp_dev-&amp;gt;msix_names[v],
          vp_dev);
if (err)
    goto error;
++vp_dev-&amp;gt;msix_used_vectors;

v = vp_dev-&amp;gt;config_vector(vp_dev, v);
/* Verify we had enough resources to assign the vector */
if (v == VIRTIO_MSI_NO_VECTOR) {
    err = -EBUSY;
    goto error;
}

if (!per_vq_vectors) {
    /* Shared vector for all VQs */
    v = vp_dev-&amp;gt;msix_used_vectors;
    snprintf(vp_dev-&amp;gt;msix_names[v], sizeof *vp_dev-&amp;gt;msix_names,
         &quot;%s-virtqueues&quot;, name);
    err = request_irq(pci_irq_vector(vp_dev-&amp;gt;pci_dev, v),
              vp_vring_interrupt, 0, vp_dev-&amp;gt;msix_names[v],
              vp_dev);
    if (err)
        goto error;
    ++vp_dev-&amp;gt;msix_used_vectors;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) 다음으로, vp_find_vqs_msix 함수가 루프에 진입하여, 각 사용 가능한 vq에 대해 설정하고 인터럽트를 요청한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781427160956&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum virtio_balloon_vq {
    VIRTIO_BALLOON_VQ_INFLATE,
    VIRTIO_BALLOON_VQ_DEFLATE,
    VIRTIO_BALLOON_VQ_STATS,
    VIRTIO_BALLOON_VQ_FREE_PAGE,
    VIRTIO_BALLOON_VQ_REPORTING,
    VIRTIO_BALLOON_VQ_MAX
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781427168794&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (i = 0; i &amp;lt; nvqs; ++i) {
    if (!names[i]) {
        vqs[i] = NULL;
        continue;
    }

    if (!callbacks[i])
        msix_vec = VIRTIO_MSI_NO_VECTOR;
    else if (vp_dev-&amp;gt;per_vq_vectors)
        msix_vec = allocated_vectors++;
    else
        msix_vec = VP_MSIX_VQ_VECTOR;
    vqs[i] = vp_setup_vq(vdev, queue_idx++, callbacks[i], names[i],
                 ctx ? ctx[i] : false,
                 msix_vec);
    if (IS_ERR(vqs[i])) {
        err = PTR_ERR(vqs[i]);
        goto error_find;
    }

    if (!vp_dev-&amp;gt;per_vq_vectors || msix_vec == VIRTIO_MSI_NO_VECTOR)
        continue;

    /* allocate per-vq irq if available and necessary */
    snprintf(vp_dev-&amp;gt;msix_names[msix_vec],
        sizeof *vp_dev-&amp;gt;msix_names,
        &quot;%s-%s&quot;,
        dev_name(&amp;amp;vp_dev-&amp;gt;vdev.dev), names[i]);
    err = request_irq(pci_irq_vector(vp_dev-&amp;gt;pci_dev, msix_vec),
                vring_interrupt, 0,
                vp_dev-&amp;gt;msix_names[msix_vec],
                vqs[i]);
    if (err)
        goto error_find;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;조건 없는 feature가 2개 있는데, 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427189337&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtqueue *vqs[VIRTIO_BALLOON_VQ_MAX];
vq_callback_t *callbacks[VIRTIO_BALLOON_VQ_MAX];
const char *names[VIRTIO_BALLOON_VQ_MAX];
int err;

/*
 * Inflateq and deflateq are used unconditionally. The names[]
 * will be NULL if the related feature is not enabled, which will
 * cause no allocation for the corresponding virtqueue in find_vqs.
 */
callbacks[VIRTIO_BALLOON_VQ_INFLATE] = balloon_ack;
names[VIRTIO_BALLOON_VQ_INFLATE] = &quot;inflate&quot;;
callbacks[VIRTIO_BALLOON_VQ_DEFLATE] = balloon_ack;
names[VIRTIO_BALLOON_VQ_DEFLATE] = &quot;deflate&quot;;
callbacks[VIRTIO_BALLOON_VQ_STATS] = NULL;
names[VIRTIO_BALLOON_VQ_STATS] = NULL;
callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
names[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
callbacks[VIRTIO_BALLOON_VQ_REPORTING] = NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;조건이 있는 feature는 3개 있는데, 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427205754&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_STATS_VQ)) {
    names[VIRTIO_BALLOON_VQ_STATS] = &quot;stats&quot;;
    callbacks[VIRTIO_BALLOON_VQ_STATS] = stats_request;
}

if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
    names[VIRTIO_BALLOON_VQ_FREE_PAGE] = &quot;free_page_vq&quot;;
    callbacks[VIRTIO_BALLOON_VQ_FREE_PAGE] = NULL;
}

if (virtio_has_feature(vb-&amp;gt;vdev, VIRTIO_BALLOON_F_REPORTING)) {
    names[VIRTIO_BALLOON_VQ_REPORTING] = &quot;reporting_vq&quot;;
    callbacks[VIRTIO_BALLOON_VQ_REPORTING] = balloon_ack;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;만약 어떤 feature가 활성화되지 않았다면, 그것에 대응하는 names[i]가 NULL이 되고, 그러면 그에 대응하는 vqs[i]도 NULL로 설정하고 그 특성을 건너뛴다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427221669&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!names[i]) {
    vqs[i] = NULL;
    continue;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서 더 아래로 갈 수 있는 것은 활성화된 feature뿐이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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 data-pm-slice=&quot;0 0 []&quot;&gt;하나는 그에 대응하는 callbacks[i]가 NULL이 아닌 경우인데, 이는 대다수의 feature에 해당된다.&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;또 하나는 비록 names[i]는 비어있지 않지만 callbacks[i]가 NULL인 경우인데, 현재 이런 상황은VIRTIO_BALLOON_VQ_FREE_PAGE에서만 나타난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후자의 경우에는 msix_vec을 VIRTIO_MSI_NO_VECTOR로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전자의 경우에는, 또 위에서 다룬 virtio의 인터럽트 처리 방식에 따라 구별해서 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;msix 인터럽트를 사용하고, n+1개의 vector가 있는&quot; 경우라면, 매 루프마다 한 번씩 allocated_vectors를 1씩 증가시키며 동시에 msix_vec에 할당한다. 그렇지 않으면 msix_vec을 VP_MSIX_VQ_VECTOR로 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427295515&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!callbacks[i])
    msix_vec = VIRTIO_MSI_NO_VECTOR;
else if (vp_dev-&amp;gt;per_vq_vectors)
    msix_vec = allocated_vectors++;
else
    msix_vec = VP_MSIX_VQ_VECTOR;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;루프 내의 분기 흐름을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;1058&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xuyEK/dJMcabR2GPG/xhiu5UjzB6XmQO1xxQdXnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xuyEK/dJMcabR2GPG/xhiu5UjzB6XmQO1xxQdXnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xuyEK/dJMcabR2GPG/xhiu5UjzB6XmQO1xxQdXnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxuyEK%2FdJMcabR2GPG%2Fxhiu5UjzB6XmQO1xxQdXnk%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;531&quot; height=&quot;705&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;1058&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;다음으로, 루프 안에서 첫 번째 핵심 함수에 도달하게 된다. vp_setup_vq다&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;vp_setup_vq 함수 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 vp_find_vqs_msix 함수의 5단계까지 이야기했는데, 루프에 진입해서 각 사용 가능한 vq에 대해 설정하고 인터럽트를 요청하는 부분이다. 이번 파트에서는 첫 번째 핵심 함수 vp_setup_vq를 분석한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427336654&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct virtqueue *vp_setup_vq(struct virtio_device *vdev, unsigned int index,
                     void (*callback)(struct virtqueue *vq),
                     const char *name,
                     bool ctx,
                     u16 msix_vec)
{
    struct virtio_pci_device *vp_dev = to_vp_device(vdev);
    struct virtio_pci_vq_info *info = kmalloc(sizeof *info, GFP_KERNEL);
    struct virtqueue *vq;
    unsigned long flags;

    /* fill out our structure that represents an active queue */
    if (!info)
        return ERR_PTR(-ENOMEM);

    vq = vp_dev-&amp;gt;setup_vq(vp_dev, info, index, callback, name, ctx,
                  msix_vec);
    if (IS_ERR(vq))
        goto out_info;

    info-&amp;gt;vq = vq;
    if (callback) {
        spin_lock_irqsave(&amp;amp;vp_dev-&amp;gt;lock, flags);
        list_add(&amp;amp;info-&amp;gt;node, &amp;amp;vp_dev-&amp;gt;virtqueues);
        spin_unlock_irqrestore(&amp;amp;vp_dev-&amp;gt;lock, flags);
    } else {
        INIT_LIST_HEAD(&amp;amp;info-&amp;gt;node);
    }

    vp_dev-&amp;gt;vqs[index] = info;
    return vq;

out_info:
    kfree(info);
    return vq;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_setup_vq 함수는 virtqueue를 초기화한다. 이 함수 안에서 구체적인 virtio_pci_vq_info 구조체 객체 하나를 할당해서, 하나의 virtqueue 정보를 표현한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427387639&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_pci_vq_info *info = kmalloc(sizeof *info, GFP_KERNEL);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그 객체를 매개변수로 해서, virtio_pci_device의 setup_vq 콜백 함수를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427402773&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vq = vp_dev-&amp;gt;setup_vq(vp_dev, info, index, callback, name, ctx,
              msix_vec);
if (IS_ERR(vq))
    goto out_info;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;setup_vq 콜백 함수 역시 virtio_pci_modern_probe 함수 안에서 설정된 것이다&lt;/p&gt;
&lt;pre id=&quot;code_1781427448142&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct virtqueue *setup_vq(struct virtio_pci_device *vp_dev,
                  struct virtio_pci_vq_info *info,
                  unsigned int index,
                  void (*callback)(struct virtqueue *vq),
                  const char *name,
                  bool ctx,
                  u16 msix_vec)
{
    struct virtio_pci_modern_device *mdev = &amp;amp;vp_dev-&amp;gt;mdev;
    bool (*notify)(struct virtqueue *vq);
    struct virtqueue *vq;
    u16 num;
    int err;

    if (__virtio_test_bit(&amp;amp;vp_dev-&amp;gt;vdev, VIRTIO_F_NOTIFICATION_DATA))
        notify = vp_notify_with_data;
    else
        notify = vp_notify;

    if (index &amp;gt;= vp_modern_get_num_queues(mdev))
        return ERR_PTR(-EINVAL);

    /* Check if queue is either not available or already active. */
    num = vp_modern_get_queue_size(mdev, index);
    if (!num || vp_modern_get_queue_enable(mdev, index))
        return ERR_PTR(-ENOENT);

    info-&amp;gt;msix_vector = msix_vec;

    /* create the vring */
    vq = vring_create_virtqueue(index, num,
                    SMP_CACHE_BYTES, &amp;amp;vp_dev-&amp;gt;vdev,
                    true, true, ctx,
                    notify, callback, name);
    if (!vq)
        return ERR_PTR(-ENOMEM);

    vq-&amp;gt;num_max = num;

    err = vp_active_vq(vq, msix_vec);
    if (err)
        goto err;

    vq-&amp;gt;priv = (void __force *)vp_modern_map_vq_notify(mdev, index, NULL);
    if (!vq-&amp;gt;priv) {
        err = -ENOMEM;
        goto err;
    }

    return vq;

err:
    vring_del_virtqueue(vq);
    return ERR_PTR(err);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) setup_vq 함수는 먼저 virtio PCI 디바이스가 VIRTIO_F_NOTIFICATION_DATA 특성을 갖고 있는지 검사한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781427478539&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (__virtio_test_bit(&amp;amp;vp_dev-&amp;gt;vdev, VIRTIO_F_NOTIFICATION_DATA))
    notify = vp_notify_with_data;
else
    notify = vp_notify;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781427487286&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * This feature indicates that the driver passes extra data (besides
 * identifying the virtqueue) in its device notifications.
 */
#define VIRTIO_F_NOTIFICATION_DATA  38&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;만약 디바이스가 VIRTIO_F_NOTIFICATION_DATA를 지원한다면, notify할 때 (추가) 데이터를 같이 전달한다는 뜻이라서, notify 함수 포인터를 vp_notify_with_data로 설정한다. 즉 vp_notify_with_data 함수를 가리키게 한다. 그렇지 않으면 vp_notify 함수를 가리키게 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clFeTe/dJMcaiKomN8/xk4tqVfXUzegmK0kquP2L1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clFeTe/dJMcaiKomN8/xk4tqVfXUzegmK0kquP2L1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clFeTe/dJMcaiKomN8/xk4tqVfXUzegmK0kquP2L1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclFeTe%2FdJMcaiKomN8%2Fxk4tqVfXUzegmK0kquP2L1%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;349&quot; height=&quot;396&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781427507565&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static bool vp_notify_with_data(struct virtqueue *vq)
{
    u32 data = vring_notification_data(vq);

    iowrite32(data, (void __iomem *)vq-&amp;gt;priv);

    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781427516527&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* the notify function used when creating a virt queue */
bool vp_notify(struct virtqueue *vq)
{
    /* we write the queue's selector into the notification register to
     * signal the other end */
    iowrite16(vq-&amp;gt;index, (void __iomem *)vq-&amp;gt;priv);
    return true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 다음으로, vp_modern_get_num_queues 함수를 호출해서 virtqueues의 길이(개수)를 얻어온다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781427540821&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (index &amp;gt;= vp_modern_get_num_queues(mdev))
    return ERR_PTR(-EINVAL);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781427551208&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_get_num_queues - get the number of virtqueues
 * @mdev: the modern virtio-pci device
 *
 * Returns the number of virtqueues
 */
u16 vp_modern_get_num_queues(struct virtio_pci_modern_device *mdev)
{
    return vp_ioread16(&amp;amp;mdev-&amp;gt;common-&amp;gt;num_queues);
}
EXPORT_SYMBOL_GPL(vp_modern_get_num_queues);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) 다음으로, vp_modern_get_queue_size 함수를 호출해서 한 virtqueue의 크기를 얻어온다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781427569936&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Check if queue is either not available or already active. */
num = vp_modern_get_queue_size(mdev, index);
if (!num || vp_modern_get_queue_enable(mdev, index))
    return ERR_PTR(-ENOENT);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781427579184&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_get_queue_size - get size for a virtqueue
 * @mdev: the modern virtio-pci device
 * @index: the queue index
 *
 * Returns the size of the virtqueue
 */
u16 vp_modern_get_queue_size(struct virtio_pci_modern_device *mdev,
                u16 index)
{
    vp_iowrite16(index, &amp;amp;mdev-&amp;gt;common-&amp;gt;queue_select);

    return vp_ioread16(&amp;amp;mdev-&amp;gt;common-&amp;gt;queue_size);
}
EXPORT_SYMBOL_GPL(vp_modern_get_queue_size);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_modern_get_queue_size 함수는 먼저 어떤 virtqueue를 선택하고, 그다음 그것의 크기를 얻어온다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427598578&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * vp_modern_get_queue_enable - enable a virtqueue
 * @mdev: the modern virtio-pci device
 * @index: the queue index
 *
 * Returns whether a virtqueue is enabled or not
 */
bool vp_modern_get_queue_enable(struct virtio_pci_modern_device *mdev,
                u16 index)
{
    vp_iowrite16(index, &amp;amp;mdev-&amp;gt;common-&amp;gt;queue_select);

    return vp_ioread16(&amp;amp;mdev-&amp;gt;common-&amp;gt;queue_enable);
}
EXPORT_SYMBOL_GPL(vp_modern_get_queue_enable);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vp_modern_get_queue_enable 함수는 선택된 virtqueue가 활성화되어 있는지 얻어온다.&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;여기서 특별히 짚고 넘어가야 하는데, 옛 버전 코드의 setup_vq 함수에서는 시작하자마자 먼저 virtio_pci_device의 common 멤버를 얻어왔다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427621327&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_pci_common_cfg __iomem *cfg = vp_dev-&amp;gt;common;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;새 버전에서는 비록 직접 구체적인 common-&amp;gt;x를 사용하고, 별도로 중간 변수를 써서 virtio_pci_device의 common 멤버를 따로 저장하지는 않지만, 의미는 같다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이건 virtio PCI proxy 디바이스에서 설정에 사용되는 MMIO 영역인데, 다음 그림 가운데 부분처럼 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAtsr9/dJMcageNidt/o1gflkYGyDlG1fice6bH41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAtsr9/dJMcageNidt/o1gflkYGyDlG1fice6bH41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAtsr9/dJMcageNidt/o1gflkYGyDlG1fice6bH41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAtsr9%2FdJMcageNidt%2Fo1gflkYGyDlG1fice6bH41%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;792&quot; height=&quot;561&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 주소들을 직접 읽고 쓰면 QEMU의 virtio_pci_common_read/write 콜백 함수로 진입한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기서 common의 각 오프셋과 그에 대응하는 레지스터 이름을 나열해서 대조하기 편하도록 했다&lt;/p&gt;
&lt;pre id=&quot;code_1781427668450&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Macro versions of offsets for the Old Timers! */
#define VIRTIO_PCI_CAP_VNDR     0
#define VIRTIO_PCI_CAP_NEXT     1
#define VIRTIO_PCI_CAP_LEN      2
#define VIRTIO_PCI_CAP_CFG_TYPE 3
#define VIRTIO_PCI_CAP_BAR      4
#define VIRTIO_PCI_CAP_OFFSET   8
#define VIRTIO_PCI_CAP_LENGTH   12

#define VIRTIO_PCI_NOTIFY_CAP_MULT  16

#define VIRTIO_PCI_COMMON_DFSELECT  0
#define VIRTIO_PCI_COMMON_DF        4
#define VIRTIO_PCI_COMMON_GFSELECT  8
#define VIRTIO_PCI_COMMON_GF        12
#define VIRTIO_PCI_COMMON_MSIX      16
#define VIRTIO_PCI_COMMON_NUMQ      18
#define VIRTIO_PCI_COMMON_STATUS    20
#define VIRTIO_PCI_COMMON_CFGGENERATION 21
#define VIRTIO_PCI_COMMON_Q_SELECT  22
#define VIRTIO_PCI_COMMON_Q_SIZE    24
#define VIRTIO_PCI_COMMON_Q_MSIX    26
#define VIRTIO_PCI_COMMON_Q_ENABLE  28
#define VIRTIO_PCI_COMMON_Q_NOFF    30
#define VIRTIO_PCI_COMMON_Q_DESCLO  32
#define VIRTIO_PCI_COMMON_Q_DESCHI  36
#define VIRTIO_PCI_COMMON_Q_AVAILLO 40
#define VIRTIO_PCI_COMMON_Q_AVAILHI 44
#define VIRTIO_PCI_COMMON_Q_USEDLO  48
#define VIRTIO_PCI_COMMON_Q_USEDHI  52
#define VIRTIO_PCI_COMMON_Q_NDATA   56
#define VIRTIO_PCI_COMMON_Q_RESET   58&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;현재 위치 - virtqueue 생성 흐름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 virtqueue 생성 흐름의 다음 단계(빨간 사각형 안)에 위치한다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;2006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EBqx1/dJMcacpXv0t/r1W0wANWQdFrkJ41B58UPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EBqx1/dJMcacpXv0t/r1W0wANWQdFrkJ41B58UPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EBqx1/dJMcacpXv0t/r1W0wANWQdFrkJ41B58UPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEBqx1%2FdJMcacpXv0t%2Fr1W0wANWQdFrkJ41B58UPK%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;190&quot; height=&quot;1381&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;2006&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;전체 vp_find_vqs부터 vp_setup_vq까지의 호출 흐름을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;1754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N57ff/dJMcagFUDVr/JAmxeZ3UkA0pjjqupIuJMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N57ff/dJMcagFUDVr/JAmxeZ3UkA0pjjqupIuJMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N57ff/dJMcagFUDVr/JAmxeZ3UkA0pjjqupIuJMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN57ff%2FdJMcagFUDVr%2FJAmxeZ3UkA0pjjqupIuJMk%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;1367&quot; height=&quot;1754&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;1754&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 setup_vq 함수의 앞 3단계를 분석했고, 이제 나머지 단계를 이어서 분석한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;다시 복습을 하면&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(1) VIRTIO_F_NOTIFICATION_DATA 특성 검사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setup_vq 함수는 먼저 virtio PCI 디바이스가 VIRTIO_F_NOTIFICATION_DATA 특성을 가지는지 검사한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427791016&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (__virtio_test_bit(&amp;amp;vp_dev-&amp;gt;vdev, VIRTIO_F_NOTIFICATION_DATA))
    notify = vp_notify_with_data;
else
    notify = vp_notify;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(2) virtqueue의 개수 얻어오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로, vp_modern_get_num_queues 함수를 호출해서 virtqueue의 길이(개수)를 얻어온다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427805725&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (index &amp;gt;= vp_modern_get_num_queues(mdev))
    return ERR_PTR(-EINVAL);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(3) virtqueue의 크기 얻어오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로, vp_modern_get_queue_size 함수를 호출해서 한 virtqueue의 크기를 얻어온다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427819787&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Check if queue is either not available or already active. */
num = vp_modern_get_queue_size(mdev, index);
if (!num || vp_modern_get_queue_enable(mdev, index))
    return ERR_PTR(-ENOENT);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(4) vring_create_virtqueue 호출해서 virtqueue 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로, vring_create_virtqueue 함수를 호출해서 실제로 virtqueue를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781427835027&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtqueue *vring_create_virtqueue(
    unsigned int index,
    unsigned int num,
    unsigned int vring_align,
    struct virtio_device *vdev,
    bool weak_barriers,
    bool may_reduce_num,
    bool context,
    bool (*notify)(struct virtqueue *),
    void (*callback)(struct virtqueue *),
    const char *name)
{
    if (virtio_has_feature(vdev, VIRTIO_F_RING_PACKED))
        return vring_create_virtqueue_packed(index, num, vring_align,
                vdev, weak_barriers, may_reduce_num,
                context, notify, callback, name, vdev-&amp;gt;dev.parent);

    return vring_create_virtqueue_split(index, num, vring_align,
            vdev, weak_barriers, may_reduce_num,
            context, notify, callback, name, vdev-&amp;gt;dev.parent);
}
EXPORT_SYMBOL_GPL(vring_create_virtqueue);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vring_create_virtqueue 함수는 virtio device가 VIRTIO_F_RING_PACKED라는 feature를 가지고 있는지에 따라 분기 처리를 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxW3AA/dJMcagsjVVE/ShPBqUfvIvjK432Glk8Fo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxW3AA/dJMcagsjVVE/ShPBqUfvIvjK432Glk8Fo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxW3AA/dJMcagsjVVE/ShPBqUfvIvjK432Glk8Fo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxW3AA%2FdJMcagsjVVE%2FShPBqUfvIvjK432Glk8Fo0%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;422&quot; height=&quot;346&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781427862719&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* This feature indicates support for the packed virtqueue layout. */
#define VIRTIO_F_RING_PACKED        34&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vring_create_virtqueue 함수는 백엔드 디바이스가 VIRTIO_F_RING_PACKED라는 feature를 지원하는지에 따라 분기 처리를 한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 특성을 지원하면 vring_create_virtqueue_packed 함수를 호출하고, 그렇지 않으면 vring_create_virtqueue_split 함수를 호출한다. 하나씩 살펴보자.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vring_create_virtqueue_packed 함수&lt;/p&gt;
&lt;pre id=&quot;code_1781427898082&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct virtqueue *vring_create_virtqueue_packed(
    unsigned int index,
    unsigned int num,
    unsigned int vring_align,
    struct virtio_device *vdev,
    bool weak_barriers,
    bool may_reduce_num,
    bool context,
    bool (*notify)(struct virtqueue *),
    void (*callback)(struct virtqueue *),
    const char *name,
    struct device *dma_dev)
{
    struct vring_virtqueue_packed vring_packed = {};
    struct vring_virtqueue *vq;
    int err;

    if (vring_alloc_queue_packed(&amp;amp;vring_packed, vdev, num, dma_dev))
        goto err_ring;

    vq = kmalloc(sizeof(*vq), GFP_KERNEL);
    if (!vq)
        goto err_vq;

    vq-&amp;gt;vq.callback = callback;
    vq-&amp;gt;vq.vdev = vdev;
    vq-&amp;gt;vq.name = name;
    vq-&amp;gt;vq.index = index;
    vq-&amp;gt;vq.reset = false;
    vq-&amp;gt;we_own_ring = true;
    vq-&amp;gt;notify = notify;
    vq-&amp;gt;weak_barriers = weak_barriers;
#ifdef CONFIG_VIRTIO_HARDEN_NOTIFICATION
    vq-&amp;gt;broken = true;
#else
    vq-&amp;gt;broken = false;
#endif
    vq-&amp;gt;packed_ring = true;
    vq-&amp;gt;dma_dev = dma_dev;
    vq-&amp;gt;use_dma_api = vring_use_dma_api(vdev);
    vq-&amp;gt;premapped = false;
    vq-&amp;gt;do_unmap = vq-&amp;gt;use_dma_api;

    vq-&amp;gt;indirect = virtio_has_feature(vdev, VIRTIO_RING_F_INDIRECT_DESC) &amp;amp;&amp;amp;
        !context;
    vq-&amp;gt;event = virtio_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX);

    if (virtio_has_feature(vdev, VIRTIO_F_ORDER_PLATFORM))
        vq-&amp;gt;weak_barriers = false;

    err = vring_alloc_state_extra_packed(&amp;amp;vring_packed);
    if (err)
        goto err_state_extra;

    virtqueue_vring_init_packed(&amp;amp;vring_packed, !!callback);

    virtqueue_init(vq, num);
    virtqueue_vring_attach_packed(vq, &amp;amp;vring_packed);

    spin_lock(&amp;amp;vdev-&amp;gt;vqs_list_lock);
    list_add_tail(&amp;amp;vq-&amp;gt;vq.list, &amp;amp;vdev-&amp;gt;vqs);
    spin_unlock(&amp;amp;vdev-&amp;gt;vqs_list_lock);

    return &amp;amp;vq-&amp;gt;vq;

err_state_extra:
    kfree(vq);
err_vq:
    vring_free_packed(&amp;amp;vring_packed, vdev, dma_dev);
err_ring:
    return NULL;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vring_create_virtqueue_packed 함수는 Linux 커널에서 packed ring 기반의 virtqueue를 하나 생성하는 데 사용되고, 해당 virtqueue를 가리키는 포인터를 반환한다.&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;index: virtqueue의 인덱스 번호를 나타낸다.&lt;/li&gt;
&lt;li&gt;num: virtqueue의 개수를 나타낸다.&lt;/li&gt;
&lt;li&gt;vdev: virtio_device 구조체를 가리키는 포인터로, virtqueue와 연관된 디바이스를 나타낸다.&lt;/li&gt;
&lt;li&gt;notify: 함수 포인터(콜백) 하나로, 데이터 전송 완료 후 호스트에 알릴 때 쓴다.&lt;/li&gt;
&lt;li&gt;callback: 함수 포인터(콜백) 하나로, 호스트의 알림을 받았을 때 그에 맞는 동작을 실행하는 데 쓴다.&lt;/li&gt;
&lt;li&gt;name: virtqueue의 이름을 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 성공적으로 실행되면, vring_virtqueue 구조체를 가리키는 포인터를 반환하는데, 이 구조체에는 packed ring을 관리하는 데 필요한 정보들(descriptor 테이블, 사용 가능 ring, 사용된 ring 등)이 들어 있다.&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;vring_create_virtqueue_split 함수&lt;/p&gt;
&lt;pre id=&quot;code_1781427934847&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static struct virtqueue *vring_create_virtqueue_split(
    unsigned int index,
    unsigned int num,
    unsigned int vring_align,
    struct virtio_device *vdev,
    bool weak_barriers,
    bool may_reduce_num,
    bool context,
    bool (*notify)(struct virtqueue *),
    void (*callback)(struct virtqueue *),
    const char *name,
    struct device *dma_dev)
{
    struct vring_virtqueue_split vring_split = {};
    struct virtqueue *vq;
    int err;

    err = vring_alloc_queue_split(&amp;amp;vring_split, vdev, num, vring_align,
                  may_reduce_num, dma_dev);
    if (err)
        return NULL;

    vq = __vring_new_virtqueue(index, &amp;amp;vring_split, vdev, weak_barriers,
                   context, notify, callback, name, dma_dev);
    if (!vq) {
        vring_free_split(&amp;amp;vring_split, vdev, dma_dev);
        return NULL;
    }

    to_vvq(vq)-&amp;gt;we_own_ring = true;

    return vq;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vring_create_virtqueue_split 함수는 Linux 커널에서 split ring 기반의 virtqueue를 하나 생성하는 데 사용되고, 해당 virtqueue를 가리키는 포인터를 반환한다.&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;index: virtqueue의 인덱스 번호를 나타낸다.&lt;/li&gt;
&lt;li&gt;num: virtqueue의 개수를 나타낸다.&lt;/li&gt;
&lt;li&gt;vdev: virtio_device 구조체를 가리키는 포인터로, virtqueue와 연관된 디바이스를 나타낸다.&lt;/li&gt;
&lt;li&gt;notify: 함수 포인터(콜백) 하나로, 데이터 전송 완료 후 호스트에 알릴 때 쓴다.&lt;/li&gt;
&lt;li&gt;callback: 함수 포인터(콜백) 하나로, 호스트의 알림을 받았을 때 그에 맞는 동작을 실행하는 데 쓴다.&lt;/li&gt;
&lt;li&gt;name: virtqueue의 이름을 지정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 성공적으로 실행되면, virtqueue 구조체를 가리키는 포인터를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;※ split ring과 packed ring&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;split ring은 descriptor ring, available ring, used ring 이렇게 3개의 데이터 구조를 사용해서 표현하는데, 이게 바로 split ring이라고 불리는 이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프런트엔드는 생산자로서 새로 만든 descriptor를 백엔드가 쓸 수 있도록 available ring에 업데이트하고, 그 다음 백엔드가 available ring에서 자신이 사용 가능한 descriptor를 읽어와서 사용하고, 사용을 끝낸 후에는 다 쓴 descriptor를 used ring에 써서 프런트엔드가 처리하도록 해서, 이 메모리들을 순환적으로 사용하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 사용이라는 건, 다른 곳에서 데이터를 이 descriptor가 기술하는 메모리 영역으로 복사해 오는 것일 수도 있고, 이 descriptor가 기술하는 메모리 영역에서 다른 곳으로 데이터를 복사해 가는 것일 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwhWGk/dJMcabR2GRC/Qq5YOYFRWAwqFf9Lq4Ym9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwhWGk/dJMcabR2GRC/Qq5YOYFRWAwqFf9Lq4Ym9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwhWGk/dJMcabR2GRC/Qq5YOYFRWAwqFf9Lq4Ym9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwhWGk%2FdJMcabR2GRC%2FQq5YOYFRWAwqFf9Lq4Ym9k%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;1778&quot; height=&quot;424&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위 그림에서 보듯이, split ring은 프런트엔드와 백엔드 사이에 데이터를 주고받기 위해 descriptor table, avail ring, used ring 세 개의 데이터 구조를 동시에 다뤄야 한다.&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;packed ring은 Virtio Spec 1.1에서 제시한 새로운 ring 구조로, 프런트엔드와 백엔드 사이의 통신 로직은 split ring과 기본적으로 비슷하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ring 구조에 약간의 변화가 생겼는데, split ring에서 원래 분리되어 있던 ring 구조를 합쳐서 전부 하나의 ring으로 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 동작 면에서는 확실히 좀 더 복잡해졌지만, 프런트엔드와 백엔드에서 패킷을 주고받을 때 packed ring 모드에서는 ring 구조 하나만 접근하면 되기 때문에, split ring이 매번 ring 3개를 조작해야 하는 것에 비해, packed ring은 접근해야 할 메모리 공간을 줄여서, cache 입장에서 더 친화적이다.&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;다음 그림은 packed ring의 ring 구조다(이 ring은 프런트엔드와 백엔드 모두에서 볼 수 있다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwgOTk/dJMcab5Cy9t/TrR5UbNGaTNvPyyoGn9Ji1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwgOTk/dJMcab5Cy9t/TrR5UbNGaTNvPyyoGn9Ji1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwgOTk/dJMcab5Cy9t/TrR5UbNGaTNvPyyoGn9Ji1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwgOTk%2FdJMcab5Cy9t%2FTrR5UbNGaTNvPyyoGn9Ji1%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;359&quot; height=&quot;390&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-sourcepos=&quot;6021:1-6021:34;196505-196538&quot; data-ke-size=&quot;size16&quot;&gt;descriptor 안의 flag 비트 구성은 다음과 같다:&lt;/p&gt;
&lt;div data-sourcepos=&quot;6023:1-6032:14;196540-196692&quot;&gt;
&lt;table style=&quot;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;&lt;b&gt;비트 위치&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;next&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;reserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;indirect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4~6&lt;/td&gt;
&lt;td&gt;reserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;avail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8~14&lt;/td&gt;
&lt;td&gt;reserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;used&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;6034:1-6037:268;196694-197300&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;6034:1-6034:73;196694-196766&quot;&gt;descriptor 안의 플래그 비트를 통해 이 descriptor의 소유권이 프런트엔드에 있는지 백엔드에 있는지를 판단한다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;6035:1-6035:76;196767-196842&quot;&gt;백엔드는 두 개의 포인터를 사용해서 생산과 소비의 진척을 추적하는데, 각각 last_avail_idx와 last_used_idx다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;6036:1-6036:190;196843-197032&quot;&gt;프런트엔드는 생산자로서 이 descriptor들을 초기화하고, 백엔드는 소비자로서 이 descriptor들을 계속 가져다 쓰고, 사용을 끝낸 후에는 다 쓴 descriptor를 다시 이 ring에 써넣고, 프런트엔드는 이 descriptor가 다시 써진 걸 관찰하고 이 descriptor를 갱신해서 다음 번에 계속 쓸 수 있게 한다.&lt;/li&gt;
&lt;li data-sourcepos=&quot;6037:1-6037:268;197033-197300&quot;&gt;한 가지 언급할 만한 점은, 프런트엔드는 ring의 순서대로 이 descriptor들을 조작하고, 백엔드 역시 완성된 순서대로 이 descriptor들을 조작한다는 것이고, 게다가 in-order 모드에서 descriptor의 가용성은 반드시 연속적인데, 만약 X번 descriptor가 사용 불가능하다면, 기본적으로 X+1번 descriptor도 똑같이 사용 불가능하다. 이렇게 프런트엔드와 백엔드의 협력 메커니즘이 packed ring의 기본 정보 전달 방식을 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HhICz/dJMcahSf2wn/yXmta9uWir6sTEdPjWK3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HhICz/dJMcahSf2wn/yXmta9uWir6sTEdPjWK3Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HhICz/dJMcahSf2wn/yXmta9uWir6sTEdPjWK3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHhICz%2FdJMcahSf2wn%2FyXmta9uWir6sTEdPjWK3Kk%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;477&quot; height=&quot;269&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;현재 virtqueue 생성 흐름의 다음 단계(빨간 네모 상자 안)에 위치한다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;1590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/df163K/dJMcahSf2ws/WYW6PuefYMFPoboxkrOhSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/df163K/dJMcahSf2ws/WYW6PuefYMFPoboxkrOhSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/df163K/dJMcahSf2ws/WYW6PuefYMFPoboxkrOhSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdf163K%2FdJMcahSf2ws%2FWYW6PuefYMFPoboxkrOhSK%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;492&quot; height=&quot;1132&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;1590&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;vring_create_virtqueue_split 함수에서 사용되는 각 구조체 상세 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 setup_vq의 4단계(vring_create_virtqueue 호출)를 다뤘는데, 거기서 두 함수가 등장했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;vring_create_virtqueue_packed()&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;vring_create_virtqueue_split()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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;
&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;(1) virtio_device 구조&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781428398541&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * struct virtio_device - representation of a device using virtio
 * @index: unique position on the virtio bus
 * @failed: saved value for VIRTIO_CONFIG_S_FAILED bit (for restore)
 * @config_enabled: configuration change reporting enabled
 * @config_change_pending: configuration change reported while disabled
 * @config_lock: protects configuration change reporting
 * @vqs_list_lock: protects @vqs.
 * @dev: underlying device.
 * @id: the device type identification (used to match it with a driver).
 * @config: the configuration ops for this device.
 * @vringh_config: configuration ops for host vrings.
 * @vqs: the list of virtqueues for this device.
 * @features: the features supported by both driver and device.
 * @priv: private pointer for the driver's use.
 */
struct virtio_device {
    int index;
    bool failed;
    bool config_enabled;
    bool config_change_pending;
    spinlock_t config_lock;
    spinlock_t vqs_list_lock;
    struct device dev;
    struct virtio_device_id id;
    const struct virtio_config_ops *config;
    const struct vringh_config_ops *vringh_config;
    struct list_head vqs;
    u64 features;
    void *priv;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;struct virtio_device는 Linux 커널에서 virtio 디바이스를 표현하는 데 사용되는 데이터 구조로, virtio 디바이스와 관련된 각종 정보와 동작 함수들이 들어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2987&quot; data-origin-height=&quot;1812&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLLPkg/dJMcaiwUMJK/kYERjGovZqV8nB91augXT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLLPkg/dJMcaiwUMJK/kYERjGovZqV8nB91augXT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLLPkg/dJMcaiwUMJK/kYERjGovZqV8nB91augXT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLLPkg%2FdJMcaiwUMJK%2FkYERjGovZqV8nB91augXT1%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;2987&quot; height=&quot;1812&quot; data-origin-width=&quot;2987&quot; data-origin-height=&quot;1812&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;struct virtio_device의 각 멤버에 대한 상세 설명은 다음과 같다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int index: virtio 버스(bus) 상의 유일한 위치다.&lt;/li&gt;
&lt;li&gt;bool failed: VIRTIO_CONFIG_S_FAILED 비트의 저장값이다 (복구용).&lt;/li&gt;
&lt;li&gt;bool config_enabled: 설정(정보) 변경 보고를 활성화할지 여부다.&lt;/li&gt;
&lt;li&gt;bool config_change_pending: 비활성화되어 있을 때, 설정(정보) 변경을 보고할지 여부다.&lt;/li&gt;
&lt;li&gt;spinlock_t config_lock: 설정 변경 보고를 보호하는 스핀락이다.&lt;/li&gt;
&lt;li&gt;spinlock_t vqs_list_lock: vqs(멤버)를 보호하는 스핀락이다.&lt;/li&gt;
&lt;li&gt;struct device dev: 기반 디바이스로, 즉 Linux 디바이스 모델의 struct device 구조체이고, virtio 디바이스를 커널에서 추상화한 것을 표현한다.&lt;/li&gt;
&lt;li&gt;struct virtio_device_id id: 디바이스 타입 식별자다 (이를 드라이버 프로그램과 매칭하는 데 사용).&lt;/li&gt;
&lt;li&gt;const struct virtio_config_ops *config: 이 디바이스의 설정 동작이다.&lt;/li&gt;
&lt;li&gt;const struct vringh_config_ops *vringh_config: 호스트 vrings의 설정 동작이다.&lt;/li&gt;
&lt;li&gt;struct list_head vqs: 이 디바이스의 virtqueues 리스트다.&lt;/li&gt;
&lt;li&gt;u64 features: 드라이버 프로그램과 디바이스가 모두 지원하는 기능이다.&lt;/li&gt;
&lt;li&gt;void *priv: 드라이버 프로그램이 사용하기 위한 전용 포인터다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(2) virtio_device_id 구조&lt;/p&gt;
&lt;pre id=&quot;code_1781428463523&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_device_id {
    __u32 device;
    __u32 vendor;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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;device: 디바이스 ID.&lt;/li&gt;
&lt;li&gt;vendor: 벤더(제조사) ID.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 device 멤버는 현재 virtio_device의 용도를 식별한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428485055&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define VIRTIO_ID_NET            1 /* virtio net */
#define VIRTIO_ID_BLOCK          2 /* virtio block */
#define VIRTIO_ID_CONSOLE        3 /* virtio console */
#define VIRTIO_ID_RNG            4 /* virtio rng */
#define VIRTIO_ID_BALLOON        5 /* virtio balloon */
#define VIRTIO_ID_IOMEM          6 /* virtio ioMemory */
#define VIRTIO_ID_RPMSG          7 /* virtio remote processor messaging */
#define VIRTIO_ID_SCSI           8 /* virtio scsi */
#define VIRTIO_ID_9P             9 /* 9p virtio console */
#define VIRTIO_ID_MAC80211_WLAN  10 /* virtio WLAN MAC */
#define VIRTIO_ID_RPROC_SERIAL   11 /* virtio remoteproc serial link */
#define VIRTIO_ID_CAIF           12 /* Virtio caif */
#define VIRTIO_ID_MEMORY_BALLOON 13 /* virtio memory balloon */
#define VIRTIO_ID_GPU            16 /* virtio GPU */
#define VIRTIO_ID_CLOCK          17 /* virtio clock/timer */
#define VIRTIO_ID_INPUT          18 /* virtio input */
#define VIRTIO_ID_VSOCK          19 /* virtio vsock transport */
#define VIRTIO_ID_CRYPTO         20 /* virtio crypto */
#define VIRTIO_ID_SIGNAL_DIST    21 /* virtio signal distribution device */
#define VIRTIO_ID_PSTORE         22 /* virtio pstore device */
#define VIRTIO_ID_IOMMU          23 /* virtio IOMMU */
#define VIRTIO_ID_MEM            24 /* virtio mem */
#define VIRTIO_ID_SOUND          25 /* virtio sound */
#define VIRTIO_ID_FS             26 /* virtio filesystem */
#define VIRTIO_ID_PMEM           27 /* virtio pmem */
#define VIRTIO_ID_RPMB           28 /* virtio rpmb */
#define VIRTIO_ID_MAC80211_HWSIM 29 /* virtio mac80211-hwsim */
#define VIRTIO_ID_VIDEO_ENCODER  30 /* virtio video encoder */
#define VIRTIO_ID_VIDEO_DECODER  31 /* virtio video decoder */
#define VIRTIO_ID_SCMI           32 /* virtio SCMI */
#define VIRTIO_ID_NITRO_SEC_MOD  33 /* virtio nitro secure module*/
#define VIRTIO_ID_I2C_ADAPTER    34 /* virtio i2c adapter */
#define VIRTIO_ID_WATCHDOG       35 /* virtio watchdog */
#define VIRTIO_ID_CAN            36 /* virtio can */
#define VIRTIO_ID_DMABUF         37 /* virtio dmabuf */
#define VIRTIO_ID_PARAM_SERV     38 /* virtio parameter server */
#define VIRTIO_ID_AUDIO_POLICY   39 /* virtio audio policy */
#define VIRTIO_ID_BT             40 /* virtio bluetooth */
#define VIRTIO_ID_GPIO           41 /* virtio gpio */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;우리가 현재 예시로 들고 있는 balloon 디바이스가 바로 그 중 하나다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428507841&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define VIRTIO_ID_BALLOON        5 /* virtio balloon */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(3) virtio_config_ops 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;struct virtio_config_ops도 앞에서 다룬 적이 있는데, 설정(하나의) virtio 디바이스의 동작들이 들어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428607209&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct virtio_config_ops {
    void (*get)(struct virtio_device *vdev, unsigned offset,
            void *buf, unsigned len);
    void (*set)(struct virtio_device *vdev, unsigned offset,
            const void *buf, unsigned len);
    u32 (*generation)(struct virtio_device *vdev);
    u8 (*get_status)(struct virtio_device *vdev);
    void (*set_status)(struct virtio_device *vdev, u8 status);
    void (*reset)(struct virtio_device *vdev);
    int (*find_vqs)(struct virtio_device *, unsigned nvqs,
            struct virtqueue *vqs[], vq_callback_t *callbacks[],
            const char * const names[], const bool *ctx,
            struct irq_affinity *desc);
    void (*del_vqs)(struct virtio_device *);
    void (*synchronize_cbs)(struct virtio_device *);
    u64 (*get_features)(struct virtio_device *vdev);
    int (*finalize_features)(struct virtio_device *vdev);
    const char *(*bus_name)(struct virtio_device *vdev);
    int (*set_vq_affinity)(struct virtqueue *vq,
                  const struct cpumask *cpu_mask);
    const struct cpumask *(*get_vq_affinity)(struct virtio_device *vdev,
            int index);
    bool (*get_shm_region)(struct virtio_device *vdev,
                  struct virtio_shm_region *region, u8 id);
    int (*disable_vq_and_reset)(struct virtqueue *vq);
    int (*enable_vq_after_reset)(struct virtqueue *vq);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;struct virtio_config_ops의 각 멤버에 대한 상세 설명은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void (*get)(struct virtio_device *vdev, unsigned offset, void *buf, unsigned len)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 설정 필드의 값을 읽는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428565066&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
offset: 설정 필드의 오프셋.
buf: 필드 값을 써넣을 버퍼.
len: 버퍼의 길이.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void (*set)(struct virtio_device *vdev, unsigned offset, const void *buf, unsigned len)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 설정 필드에 써넣는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428619667&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
offset: 설정 필드의 오프셋.
buf: 그 안에서 필드 값을 읽어올 버퍼.
len: 버퍼의 길이.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;u32 (*generation)(struct virtio_device *vdev)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 설정 생성 카운터다 (옵션).&lt;/p&gt;
&lt;pre id=&quot;code_1781428638289&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;u8 (*get_status)(struct virtio_device *vdev)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 상태 바이트를 읽는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428653807&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
상태 바이트를 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void (*set_status)(struct virtio_device *vdev, u8 status)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 상태 바이트를 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428669363&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
status: 새 상태 바이트.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void (*reset)(struct virtio_device *vdev)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 디바이스를 리셋한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428685968&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
디바이스를 리셋한 후에는, 반드시 상태와 기능 협상을 다시 진행해야 한다. 디바이스는 자신의 vq/config 콜백에서 리셋할 수 없고, 추가/삭제와 병행해서 리셋할 수도 없다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int (*find_vqs)(struct virtio_device *, unsigned nvqs, struct virtqueue *vqs[], vq_callback_t *callbacks[], const char * const names[], const bool *ctx, struct irq_affinity *desc)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, virtqueues를 찾아서 인스턴스화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428701644&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
nvqs: 찾을 virtqueues의 수량.
vqs: 호출 성공 후, 새로운 virtqueues를 포함한다.
callbacks: 콜백 배열로, 모든 virtqueue마다 하나씩 포함하는데, 콜백이 필요 없는 vqs에 대해서는 NULL로 설정한다.
names: virtqueue 이름 배열(주로 디버깅용)로, 드라이버 프로그램이 사용하지 않는 vqs의 NULL 항목을 포함한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void (*del_vqs)(struct virtio_device *)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, find_vqs()로 찾은 사용하지 않는 virtqueues를 해제한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428717634&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;void (*synchronize_cbs)(struct virtio_device *)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, virtqueue 콜백과 동기화한다 (옵션).&lt;/p&gt;
&lt;pre id=&quot;code_1781428732558&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;이 함수는 그 이전의 큐 상의 모든 메모리 동작이, 그 이후에 호출되는 vring_interrupt()에 대해 보일 수 있도록 보장한다.
파라미터:
vdev: virtio_device.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;u64 (*get_features)(struct virtio_device *vdev)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 해당 디바이스의 특성 비트 배열을 가져온다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428751707&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
앞쪽 64개의 특성 비트를 반환한다 (우리가 현재 필요한 전부다).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int (*finalize_features)(struct virtio_device *vdev)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 어떤 디바이스 특성을 사용할지 확정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428767839&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
이것은 드라이버 프로그램 특성(특징) 비트를 디바이스로 보낸다: 필요하다면 dev-&amp;gt;feature 비트를 변경할 수 있다.
주의, 비록 이 이름으로 불리지만, 임의의 횟수만큼 호출될 수 있다.
성공하면 0을 반환하고, 실패하면 오류 상태를 반환한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;const char *(*bus_name)(struct virtio_device *vdev)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 디바이스와 연관된 버스 이름을 반환한다 (옵션).&lt;/p&gt;
&lt;pre id=&quot;code_1781428782914&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
이것은 버스 이름을 가리키는 포인터를 반환하는데, pci_name을 본뜬 포인터이고, 그러면 호출자는 그 이름에서 복제할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int (*set_vq_affinity)(struct virtqueue *vq, const struct cpumask *cpu_mask)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, virtqueue의 친화성을 설정한다 (옵션). 즉, 지정된 가상 큐(Virtual Queue)와 특정 CPU 코어 사이의 affinity를 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428839397&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vq: virtqueue.
cpu_mask: CPU 마스크.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;const struct cpumask *(*get_vq_affinity)(struct virtio_device *vdev, int index);&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, virtqueue의 affinity 를 가져온다 (옵션). 즉, 지정된 가상 큐(Virtual Queue)와 특정 CPU 코어 사이의 affinity를 가져온다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428870575&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vq: virtqueue.
index: 큐의 인덱스 값.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bool (*get_shm_region)(struct virtio_device *vdev, struct virtio_shm_region *region, u8 id)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 인덱스에 기반해서 공유 메모리 영역을 가져온다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428887024&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vdev: virtio_device.
region: 공유 메모리 영역.
id: 인덱스 값.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int (*disable_vq_and_reset)(struct virtqueue *vq)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 큐를 단독으로 리셋한다 (옵션).&lt;/p&gt;
&lt;pre id=&quot;code_1781428903516&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vq: virtqueue.
성공하면 0을 반환하고, 실패하면 오류 상태를 반환한다.
disable_vq_and_reset은 콜백이 비활성화되고 동기화되도록 보장한다.
콜백 외에, 호출자는 virtqueue의 어떤 함수도 vring에 접근하지 않도록 보증해야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;int (*enable_vq_after_reset)(struct virtqueue *vq)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수(포인터)로, 리셋된 큐를 활성화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781428918868&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;파라미터:
vq: virtqueue.
성공하면 0을 반환하고, 실패하면 오류 상태를 반환한다.
만약 disable_vq_and_reset이 설정되어 있다면, enable_vq_after_reset도 반드시 설정해야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3980&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ehOn/dJMcaaZVYrc/LLiU4Jtc3kbr8jAoU7sYuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ehOn/dJMcaaZVYrc/LLiU4Jtc3kbr8jAoU7sYuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ehOn/dJMcaaZVYrc/LLiU4Jtc3kbr8jAoU7sYuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ehOn%2FdJMcaaZVYrc%2FLLiU4Jtc3kbr8jAoU7sYuk%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;3980&quot; height=&quot;145&quot; data-origin-width=&quot;3980&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 다룬 내용을 큰 흐름으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;1530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZFjy3/dJMcahx00sL/NGPbBPspvqaFKQq1daQkjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZFjy3/dJMcahx00sL/NGPbBPspvqaFKQq1daQkjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZFjy3/dJMcahx00sL/NGPbBPspvqaFKQq1daQkjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZFjy3%2FdJMcahx00sL%2FNGPbBPspvqaFKQq1daQkjk%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;822&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;1530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>System Programming/Hypervisor</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/722</guid>
      <comments>https://1000sj.tistory.com/722#entry722comment</comments>
      <pubDate>Mon, 9 Mar 2026 15:51:39 +0900</pubDate>
    </item>
    <item>
      <title>QEMU 코드 분석 #7 VirtIO 디바이스 초기화 &amp;mdash; QEMU 백엔드 realize &amp;amp; device_plugged</title>
      <link>https://1000sj.tistory.com/714</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;virtio&amp;nbsp;디바이스(balloon&amp;nbsp;예시)가&amp;nbsp;QEMU에서&amp;nbsp;realize되어&amp;nbsp;virtio&amp;nbsp;버스와&amp;nbsp;PCI&amp;nbsp;대리&amp;nbsp;디바이스에&amp;nbsp;연결되는&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;1) QEMU virtio realize 호출 체인&lt;br /&gt;2) virtio_device_realize 와 virtio_balloon_device_realize&lt;br /&gt;3) virtio_bus_device_plugged 와 virtio_pci_device_plugged&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size23&quot;&gt;1) QEMU virtio realize 호출 체인 (balloon 예시)&lt;/h3&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio-balloon 장치가 만들어질 때 realize 함수가 상속 체인을 따라 연쇄 호출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781396096884&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_balloon_pci_realize  (VirtioPCIClass 레벨)
   &amp;darr; 안에서 parent의 realize 호출
virtio_pci_realize          (PCIDeviceClass 레벨)
   &amp;darr;
virtio_pci_dc_realize / pci_qdev_realize  (DeviceClass 레벨, PCI 등록)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;자식 클래스의 realize가 자기 일을 한 뒤 부모(parent_dc_realize)의 realize를 호출하면서,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;가장 바깥(balloon-pci)부터 안쪽(pci_qdev_realize)까지 내려가며 장치를 단계적으로 완성하는 흐름이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJknmW/dJMcadI2OiL/srExwSbIhnS1YkiCmKrGjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJknmW/dJMcadI2OiL/srExwSbIhnS1YkiCmKrGjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJknmW/dJMcadI2OiL/srExwSbIhnS1YkiCmKrGjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJknmW%2FdJMcadI2OiL%2FsrExwSbIhnS1YkiCmKrGjk%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;418&quot; height=&quot;589&quot; data-origin-width=&quot;697&quot; data-origin-height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(1) virtio_pci_do_realize 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;TYPE_VIRTIO_BALLOON이 실체화될 때 device_set_realized 함수가 가장 먼저 DeviceClass-&amp;gt;realize 즉 virtio_pci_dc_realize 함수를 호출한다. (hw/virtio/virtio-pci.c)&lt;/p&gt;
&lt;pre id=&quot;code_1781396340382&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_dc_realize(DeviceState *qdev, Error **errp)
{
    VirtioPCIClass *vpciklass = VIRTIO_PCI_GET_CLASS(qdev);
    VirtIOPCIProxy *proxy = VIRTIO_PCI(qdev);
    PCIDevice *pci_dev = &amp;amp;proxy-&amp;gt;pci_dev;

    if (!(proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_DISABLE_PCIE) &amp;amp;&amp;amp;
        virtio_pci_modern(proxy)) {
        pci_dev-&amp;gt;cap_present |= QEMU_PCI_CAP_EXPRESS;
    }

    vpciklass-&amp;gt;parent_dc_realize(qdev, errp);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_dc_realize 함수는 먼저 virtio PCI 대리 디바이스가 VIRTIO_PCI_FLAG_DISABLE_PCIE 특성을 가지는지 판단한다. 이 특성이 있으면 virtio PCI 대리 디바이스가 PCIe의 인터페이스를 보여주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음 vpciklass-&amp;gt;parent_dc_realize 함수를 호출하는데, 앞의 분석으로 알 수 있듯이 이 콜백 함수는 pci_qdev_realize다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(2) pci_qdev_realize 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(hw/pci/pci.c)&lt;/p&gt;
&lt;pre id=&quot;code_1781396384708&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void pci_qdev_realize(DeviceState *qdev, Error **errp)
{
    PCIDevice *pci_dev = (PCIDevice *)qdev;
    PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
    ObjectClass *klass = OBJECT_CLASS(pc);
    Error *local_err = NULL;
    bool is_default_rom;
    uint16_t class_id;

    /*
     * capped by systemd (see: udev-builtin-net_id.c)
     * as it's the only known user honor it to avoid users
     * misconfigure QEMU and then wonder why acpi-index doesn't work
     */
    if (pci_dev-&amp;gt;acpi_index &amp;gt; ONBOARD_INDEX_MAX) {
        error_setg(errp,
                   &quot;acpi-index should be less or equal to %u&quot;,
                   ONBOARD_INDEX_MAX);
        return;
    }

    /*
     * make sure that acpi-index is unique across all present PCI devices
     */
    if (pci_dev-&amp;gt;acpi_index) {
        GSequence *used_indexes = pci_acpi_index_list();

        if (g_sequence_lookup(used_indexes,
                              GINT_TO_POINTER(pci_dev-&amp;gt;acpi_index),
                              g_cmp_uint32, NULL)) {
            error_setg(errp, &quot;a PCI device with acpi-index = %&quot; PRIu32
                       &quot; already exist&quot;, pci_dev-&amp;gt;acpi_index);
            return;
        }
        g_sequence_insert_sorted(used_indexes,
                                 GINT_TO_POINTER(pci_dev-&amp;gt;acpi_index),
                                 g_cmp_uint32, NULL);
    }

    if (pci_dev-&amp;gt;romsize != -1 &amp;amp;&amp;amp; !is_power_of_2(pci_dev-&amp;gt;romsize)) {
        error_setg(errp, &quot;ROM size %u is not a power of two&quot;, pci_dev-&amp;gt;romsize);
        return;
    }

    /* initialize cap_present for pci_is_express() and pci_config_size(),
     * Note that hybrid PCIs are not set automatically and need to manage
     * QEMU_PCI_CAP_EXPRESS manually */
    if (object_class_dynamic_cast(klass, INTERFACE_PCIE_DEVICE) &amp;amp;&amp;amp;
       !object_class_dynamic_cast(klass, INTERFACE_CONVENTIONAL_PCI_DEVICE)) {
        pci_dev-&amp;gt;cap_present |= QEMU_PCI_CAP_EXPRESS;
    }

    if (object_class_dynamic_cast(klass, INTERFACE_CXL_DEVICE)) {
        pci_dev-&amp;gt;cap_present |= QEMU_PCIE_CAP_CXL;
    }

    pci_dev = do_pci_register_device(pci_dev,
                                     object_get_typename(OBJECT(qdev)),
                                     pci_dev-&amp;gt;devfn, errp);
    if (pci_dev == NULL)
        return;

    if (pc-&amp;gt;realize) {
        pc-&amp;gt;realize(pci_dev, &amp;amp;local_err);
        if (local_err) {
            error_propagate(errp, local_err);
            do_pci_unregister_device(pci_dev);
            return;
        }
    }

    /*
     * A PCIe Downstream Port that do not have ARI Forwarding enabled must
     * associate only Device 0 with the device attached to the bus
     * representing the Link from the Port (PCIe base spec rev 4.0 ver 0.3,
     * sec 7.3.1).
     * With ARI, PCI_SLOT() can return non-zero value as the traditional
     * 5-bit Device Number and 3-bit Function Number fields in its associated
     * Routing IDs, Requester IDs and Completer IDs are interpreted as a
     * single 8-bit Function Number. Hence, ignore ARI capable devices.
     */
    if (pci_is_express(pci_dev) &amp;amp;&amp;amp;
        !pcie_find_capability(pci_dev, PCI_EXT_CAP_ID_ARI) &amp;amp;&amp;amp;
        pcie_has_upstream_port(pci_dev) &amp;amp;&amp;amp;
        PCI_SLOT(pci_dev-&amp;gt;devfn)) {
        warn_report(&quot;PCI: slot %d is not valid for %s,&quot;
                    &quot; parent device only allows plugging into slot 0.&quot;,
                    PCI_SLOT(pci_dev-&amp;gt;devfn), pci_dev-&amp;gt;name);
    }

    if (pci_dev-&amp;gt;failover_pair_id) {
        if (!pci_bus_is_express(pci_get_bus(pci_dev))) {
            error_setg(errp, &quot;failover primary device must be on &quot;
                             &quot;PCIExpress bus&quot;);
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        class_id = pci_get_word(pci_dev-&amp;gt;config + PCI_CLASS_DEVICE);
        if (class_id != PCI_CLASS_NETWORK_ETHERNET) {
            error_setg(errp, &quot;failover primary device is not an &quot;
                             &quot;Ethernet device&quot;);
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        if ((pci_dev-&amp;gt;cap_present &amp;amp; QEMU_PCI_CAP_MULTIFUNCTION)
            || (PCI_FUNC(pci_dev-&amp;gt;devfn) != 0)) {
            error_setg(errp, &quot;failover: primary device must be in its own &quot;
                             &quot;PCI slot&quot;);
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        qdev-&amp;gt;allow_unplug_during_migration = true;
    }

    /* rom loading */
    is_default_rom = false;
    if (pci_dev-&amp;gt;romfile == NULL &amp;amp;&amp;amp; pc-&amp;gt;romfile != NULL) {
        pci_dev-&amp;gt;romfile = g_strdup(pc-&amp;gt;romfile);
        is_default_rom = true;
    }

    pci_add_option_rom(pci_dev, is_default_rom, &amp;amp;local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        pci_qdev_unrealize(DEVICE(pci_dev));
        return;
    }

    pci_set_power(pci_dev, true);

    pci_dev-&amp;gt;msi_trigger = pci_msi_trigger;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;pci_qdev_realize 함수는 virtio PCI proxy 디바이스를 PCI 버스에 등록한다.&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;virtio 디바이스의 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 디바이스는 먼저 PCI 디바이스를 하나 생성해야 한다. 이를 virtio PCI proxy 디바이스라고 부르고, 이 proxy 디바이스는 PCI 버스에 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서, virtio proxy 디바이스가 다시 virtio 버스를 하나 생성한다. 이렇게 하면 virtio 디바이스가 이 virtio 버스에 연결될 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 virtio PCI proxy 디바이스 타입의 정의를 살펴보자. (hw/virtio/virtio-pci.c)&lt;/p&gt;
&lt;pre id=&quot;code_1781396535791&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static const TypeInfo virtio_pci_info = {
    .name          = TYPE_VIRTIO_PCI,
    .parent        = TYPE_PCI_DEVICE,
    .instance_size = sizeof(VirtIOPCIProxy),
    .class_init    = virtio_pci_class_init,
    .class_size    = sizeof(VirtioPCIClass),
    .abstract      = true,
};
......
static const TypeInfo virtio_pci_bus_info = {
    .name          = TYPE_VIRTIO_PCI_BUS,
    .parent        = TYPE_VIRTIO_BUS,
    .instance_size = sizeof(VirtioPCIBusState),
    .class_size    = sizeof(VirtioPCIBusClass),
    .class_init    = virtio_pci_bus_class_init,
};

static void virtio_pci_register_types(void)
{
    /* Base types: */
    type_register_static(&amp;amp;virtio_pci_bus_info);
    type_register_static(&amp;amp;virtio_pci_info);
}

type_init(virtio_pci_register_types)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;초기 단계: PCI 버스 위의 virtio proxy 디바이스들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pci_qdev_realize 함수가 끝났을 때까지 디바이스들의 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1Yk5i/dJMcaijnaVk/K2QVeU54Pf9jqZ9xA0gpX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1Yk5i/dJMcaijnaVk/K2QVeU54Pf9jqZ9xA0gpX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1Yk5i/dJMcaijnaVk/K2QVeU54Pf9jqZ9xA0gpX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1Yk5i%2FdJMcaijnaVk%2FK2QVeU54Pf9jqZ9xA0gpX0%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;501&quot; height=&quot;278&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pci_qdev_realize 함수는 주로 세 가지 작업을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 먼저 do_pci_register_device 함수를 호출해 등록을 진행한다. do_pci_register_device 함수는 PCI 버스에 디바이스를 등록하는 등의 초기화 작업을 마친다. 구체적으로&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;만약 지정된 devfn이 -1이면, 버스에서 슬롯을 알아서 선택해서 슬롯을 얻은 후 PCIDevice의 devfn(즉 pci_dev-&amp;gt;devfn)에 저장한다. 만약 디바이스 명령행에서 addr를 지정했다면, addr가 디바이스의 devfn이 된다.&lt;/li&gt;
&lt;li&gt;그다음 PCIDevice 구조체 내의 각 도메인을 설정하는데, pci_init_bus_master 함수를 호출해 PCIDevice의 Address 멤버 bus_master_as와 그에 대응하는 MR을 초기화한다.&lt;/li&gt;
&lt;li&gt;그 이후, pci_config_alloc 함수를 호출해 PCI 디바이스의 설정 공간을 할당한다. cmask는 관련 능력을 검사하는 데 쓰고, wmask는 읽기&amp;middot;쓰기를 제어하는 데 쓰고, w1cmask는 RW1C를 구현하는 데 쓴다. 이로부터 일부 초기화 설정, 예를 들어 vendor_id 등을 완성한다.&lt;/li&gt;
&lt;li&gt;그런 다음 디바이스의 config_read와 config_write 함수를 설정한다. 만약 관련된 하위 클래스가 스스로 설정하지 않았다면 기본값인 pci_default_read/write_config 함수를 사용한다.&lt;/li&gt;
&lt;li&gt;마지막으로, 디바이스를 bus-&amp;gt;devices 수에 복제해 넣는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 다음으로, pci_qdev_realize 함수는 PCI 디바이스가 속한 class의 realize 함수, 즉 pc-&amp;gt;realize 함수를 호출하는데, 다시 말해 virtio_pci_realize 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) 마지막으로, pci_add_option_rom 함수를 호출해 PCI 디바이스의 ROM을 로드한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QhHSX/dJMcaiwUMK1/Pm0Do9fca6OITIGtn7DGsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QhHSX/dJMcaiwUMK1/Pm0Do9fca6OITIGtn7DGsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QhHSX/dJMcaiwUMK1/Pm0Do9fca6OITIGtn7DGsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQhHSX%2FdJMcaiwUMK1%2FPm0Do9fca6OITIGtn7DGsk%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;345&quot; height=&quot;296&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;(3) virtio_pci_realize 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(hw/virtio/virtio-pci.c)&lt;/p&gt;
&lt;pre id=&quot;code_1781396645675&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
    VirtioPCIClass *k = VIRTIO_PCI_GET_CLASS(pci_dev);
    bool pcie_port = pci_bus_is_express(pci_get_bus(pci_dev)) &amp;amp;&amp;amp;
                     !pci_bus_is_root(pci_get_bus(pci_dev));

    if (kvm_enabled() &amp;amp;&amp;amp; !kvm_has_many_ioeventfds()) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /* fd-based ioevents can't be synchronized in record/replay */
    if (replay_mode != REPLAY_MODE_NONE) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /*
     * virtio pci bar layout used by default.
     * subclasses can re-arrange things if needed.
     *
     *   region 0   --  virtio legacy io bar
     *   region 1   --  msi-x bar
     *   region 2   --  virtio modern io bar (off by default)
     *   region 4+5 --  virtio modern memory (64bit) bar
     */
    proxy-&amp;gt;legacy_io_bar_idx  = 0;
    proxy-&amp;gt;msix_bar_idx       = 1;
    proxy-&amp;gt;modern_io_bar_idx  = 2;
    proxy-&amp;gt;modern_mem_bar_idx = 4;

    proxy-&amp;gt;common.offset = 0x0;
    proxy-&amp;gt;common.size = 0x1000;
    proxy-&amp;gt;common.type = VIRTIO_PCI_CAP_COMMON_CFG;

    proxy-&amp;gt;isr.offset = 0x1000;
    proxy-&amp;gt;isr.size = 0x1000;
    proxy-&amp;gt;isr.type = VIRTIO_PCI_CAP_ISR_CFG;

    proxy-&amp;gt;device.offset = 0x2000;
    proxy-&amp;gt;device.size = 0x1000;
    proxy-&amp;gt;device.type = VIRTIO_PCI_CAP_DEVICE_CFG;

    proxy-&amp;gt;notify.offset = 0x3000;
    proxy-&amp;gt;notify.size = virtio_pci_queue_mem_mult(proxy) * VIRTIO_QUEUE_MAX;
    proxy-&amp;gt;notify.type = VIRTIO_PCI_CAP_NOTIFY_CFG;

    proxy-&amp;gt;notify_pio.offset = 0x0;
    proxy-&amp;gt;notify_pio.size = 0x4;
    proxy-&amp;gt;notify_pio.type = VIRTIO_PCI_CAP_NOTIFY_CFG;

    /* subclasses can enforce modern, so do this unconditionally */
    memory_region_init(&amp;amp;proxy-&amp;gt;modern_bar, OBJECT(proxy), &quot;virtio-pci&quot;,
                       /* PCI BAR regions must be powers of 2 */
                       pow2ceil(proxy-&amp;gt;notify.offset + proxy-&amp;gt;notify.size));

    if (proxy-&amp;gt;disable_legacy == ON_OFF_AUTO_AUTO) {
        proxy-&amp;gt;disable_legacy = pcie_port ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
    }

    if (!virtio_pci_modern(proxy) &amp;amp;&amp;amp; !virtio_pci_legacy(proxy)) {
        error_setg(errp, &quot;device cannot work as neither modern nor legacy mode&quot;
                   &quot; is enabled&quot;);
        error_append_hint(errp, &quot;Set either disable-modern or disable-legacy&quot;
                          &quot; to off\n&quot;);
        return;
    }

    if (pcie_port &amp;amp;&amp;amp; pci_is_express(pci_dev)) {
        int pos;
        uint16_t last_pcie_cap_offset = PCI_CONFIG_SPACE_SIZE;

        pos = pcie_endpoint_cap_init(pci_dev, 0);
        assert(pos &amp;gt; 0);

        pos = pci_add_capability(pci_dev, PCI_CAP_ID_PM, 0,
                                 PCI_PM_SIZEOF, errp);
        if (pos &amp;lt; 0) {
            return;
        }

        pci_dev-&amp;gt;exp.pm_cap = pos;

        /*
         * Indicates that this function complies with revision 1.2 of the
         * PCI Power Management Interface Specification.
         */
        pci_set_word(pci_dev-&amp;gt;config + pos + PCI_PM_PMC, 0x3);

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_AER) {
            pcie_aer_init(pci_dev, PCI_ERR_VER, last_pcie_cap_offset,
                          PCI_ERR_SIZEOF, NULL);
            last_pcie_cap_offset += PCI_ERR_SIZEOF;
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_DEVERR) {
            /* Init error enabling flags */
            pcie_cap_deverr_init(pci_dev);
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_LNKCTL) {
            /* Init Link Control Register */
            pcie_cap_lnkctl_init(pci_dev);
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_PM) {
            /* Init Power Management Control Register */
            pci_set_word(pci_dev-&amp;gt;wmask + pos + PCI_PM_CTRL,
                         PCI_PM_CTRL_STATE_MASK);
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_ATS) {
            pcie_ats_init(pci_dev, last_pcie_cap_offset,
                          proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_ATS_PAGE_ALIGNED);
            last_pcie_cap_offset += PCI_EXT_CAP_ATS_SIZEOF;
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_FLR) {
            /* Set Function Level Reset capability bit */
            pcie_cap_flr_init(pci_dev);
        }
    } else {
        /*
         * make future invocations of pci_is_express() return false
         * and pci_config_size() return PCI_CONFIG_SPACE_SIZE.
         */
        pci_dev-&amp;gt;cap_present &amp;amp;= ~QEMU_PCI_CAP_EXPRESS;
    }

    virtio_pci_bus_new(&amp;amp;proxy-&amp;gt;bus, sizeof(proxy-&amp;gt;bus), proxy);
    if (k-&amp;gt;realize) {
        k-&amp;gt;realize(proxy, errp);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(1) VirtIOPCIProxy 디바이스 객체 얻기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_realize 함수는 먼저 virtioPCIProxy 디바이스, 즉 VirtIOPCIProxy 구조체의 객체를 얻는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781396676815&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(2) VirtIOPCIProxy의 BAR 인덱스 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음, virtio_pci_realize 함수가 VirtIOPCIProxy 디바이스의 여러 BAR 데이터를 초기화하고, 이 BAR들의 인덱스 번호를 설정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781396694042&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
     * virtio pci bar layout used by default.
     * subclasses can re-arrange things if needed.
     *
     *   region 0   --  virtio legacy io bar
     *   region 1   --  msi-x bar
     *   region 2   --  virtio modern io bar (off by default)
     *   region 4+5 --  virtio modern memory (64bit) bar
     */
    proxy-&amp;gt;legacy_io_bar_idx  = 0;
    proxy-&amp;gt;msix_bar_idx       = 1;
    proxy-&amp;gt;modern_io_bar_idx  = 2;
    proxy-&amp;gt;modern_mem_bar_idx = 4;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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;legacy I/O 주소는 0이고&lt;/li&gt;
&lt;li&gt;msi-x 주소는 1이고&lt;/li&gt;
&lt;li&gt;modern IO 주소는 2이고&lt;/li&gt;
&lt;li&gt;modern MMIO 주소는 4다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 legacy와 modern은 서로 다른 virtio 버전을 가리킨다.&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;BAR 레이아웃 시각화&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs5M6M/dJMcabq2d60/kCdewZHO0Ejy6Po0yDngx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs5M6M/dJMcabq2d60/kCdewZHO0Ejy6Po0yDngx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs5M6M/dJMcabq2d60/kCdewZHO0Ejy6Po0yDngx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs5M6M%2FdJMcabq2d60%2FkCdewZHO0Ejy6Po0yDngx1%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;293&quot; height=&quot;348&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(3) VirtIOPCIRegion 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_realize 함수는 또한 여러 VirtIOPCIRegion(VirtIOPCIRegion은 virtio 디바이스의 설정 공간 정보를 나타내는 데 쓴다)을 초기화한다. VirtIOPCIProxy의 common, isr, device, notify 멤버들이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781396729294&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;proxy-&amp;gt;common.offset = 0x0;
    proxy-&amp;gt;common.size = 0x1000;
    proxy-&amp;gt;common.type = VIRTIO_PCI_CAP_COMMON_CFG;

    proxy-&amp;gt;isr.offset = 0x1000;
    proxy-&amp;gt;isr.size = 0x1000;
    proxy-&amp;gt;isr.type = VIRTIO_PCI_CAP_ISR_CFG;

    proxy-&amp;gt;device.offset = 0x2000;
    proxy-&amp;gt;device.size = 0x1000;
    proxy-&amp;gt;device.type = VIRTIO_PCI_CAP_DEVICE_CFG;

    proxy-&amp;gt;notify.offset = 0x3000;
    proxy-&amp;gt;notify.size = virtio_pci_queue_mem_mult(proxy) * VIRTIO_QUEUE_MAX;
    proxy-&amp;gt;notify.type = VIRTIO_PCI_CAP_NOTIFY_CFG;

    proxy-&amp;gt;notify_pio.offset = 0x0;
    proxy-&amp;gt;notify_pio.size = 0x4;
    proxy-&amp;gt;notify_pio.type = VIRTIO_PCI_CAP_NOTIFY_CFG;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtIOPCIRegion은 VirtIOPCIProxy 디바이스의 modern MMIO에 관한 정보를 저장한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 VirtIOProxy의 modern MMIO 중에서 처음 시작 영역은 common 영역인데, 그 크기는 0x1000이고 범위는 [0x0, 0x1000]이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그다음 isr 영역인데, 크기 역시 0x1000이고 범위는 [0x1000, 0x2000]이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그다음은 device 영역인데, 크기 역시 0x1000이고 범위는 [0x2000, 0x3000]이다. 마지막은 notify 영역인데, 그 크기는 virtio_pci_queue_mem_mult(proxy) * VIRTIO_QUEUE_MAX이고 범위는 [0x3000, 0x3000 + virtio_pci_queue_mem_mult(proxy) * VIRTIO_QUEUE_MAX]다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;modern MMIO 메모리 맵&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTmryf/dJMcahY23IT/Jdvw3bahGTJ5OUMnwB58f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTmryf/dJMcahY23IT/Jdvw3bahGTJ5OUMnwB58f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTmryf/dJMcahY23IT/Jdvw3bahGTJ5OUMnwB58f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTmryf%2FdJMcahY23IT%2FJdvw3bahGTJ5OUMnwB58f0%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;185&quot; height=&quot;583&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;(4) modern_bar MemoryRegion 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VirtIOPCIProxy의 modern MMIO에 대응하는 MemoryRegion은 VirtIOPCIProxy의 modern_bar 멤버에 저장된다. 여기에 또 하나의 MemoryRegion이 modern_cfg 멤버에 저장된다(이 부분은 오래된 버전 코드에 있고, 새로운 버전 코드에서는 이미 없어졌다).&lt;/p&gt;
&lt;pre id=&quot;code_1781396783212&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* subclasses can enforce modern, so do this unconditionally */
    memory_region_init(&amp;amp;proxy-&amp;gt;modern_bar, OBJECT(proxy), &quot;virtio-pci&quot;,
                       /* PCI BAR regions must be powers of 2 */
                       pow2ceil(proxy-&amp;gt;notify.offset + proxy-&amp;gt;notify.size));

    if (proxy-&amp;gt;disable_legacy == ON_OFF_AUTO_AUTO) {
        proxy-&amp;gt;disable_legacy = pcie_port ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
    }

    if (!virtio_pci_modern(proxy) &amp;amp;&amp;amp; !virtio_pci_legacy(proxy)) {
        error_setg(errp, &quot;device cannot work as neither modern nor legacy mode&quot;
                   &quot; is enabled&quot;);
        error_append_hint(errp, &quot;Set either disable-modern or disable-legacy&quot;
                          &quot; to off\n&quot;);
        return;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;5) virtio-bus 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_realize 함수는 virtio_pci_bus_new 함수를 호출해 virtio-bus를 생성하고, 현재 virtio PCI proxy 디바이스 아래에 연결한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781396819716&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_pci_bus_new(&amp;amp;proxy-&amp;gt;bus, sizeof(proxy-&amp;gt;bus), proxy);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 시점에서 디바이스들의 관계는 아래와 같이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hio1U/dJMcaaFHmxa/EKXlK2iT3TN9ikcjWT6nu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hio1U/dJMcaaFHmxa/EKXlK2iT3TN9ikcjWT6nu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hio1U/dJMcaaFHmxa/EKXlK2iT3TN9ikcjWT6nu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHio1U%2FdJMcaaFHmxa%2FEKXlK2iT3TN9ikcjWT6nu1%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;426&quot; height=&quot;301&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-sourcepos=&quot;630:1-630:26;21424-21449&quot; data-ke-size=&quot;size16&quot;&gt;(6) 하위 클래스 realize 호출&lt;/p&gt;
&lt;p data-sourcepos=&quot;632:1-632:100;21451-21550&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_realize 함수는 마지막에 k-&amp;gt;realize 함수 포인터가 가리키는 함수를 호출한다. k는 VirtioPCIClass 타입이므로, 앞의 표에 따라&lt;/p&gt;
&lt;div data-sourcepos=&quot;634:1-638:67;21552-21756&quot;&gt;
&lt;table style=&quot;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;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;realize 함수&lt;/td&gt;
&lt;td&gt;parent_dc_realize 함수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeviceClass&lt;/td&gt;
&lt;td&gt;virtio_pci_dc_realize&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCIDeviceClass&lt;/td&gt;
&lt;td&gt;virtio_pci_realize&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VirtioPCIClass&lt;/td&gt;
&lt;td&gt;virtio_balloon_pci_realize&lt;/td&gt;
&lt;td&gt;pci_qdev_realize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;640:1-640:64;21758-21821&quot; data-ke-size=&quot;size16&quot;&gt;k-&amp;gt;realize 함수 포인터는 실제로 virtio_balloon_pci_realize 함수를 가리킨다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;640:1-640:64;21758-21821&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-sourcepos=&quot;640:1-640:64;21758-21821&quot; data-ke-size=&quot;size20&quot;&gt;(4) virtio_balloon_pci_realize 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(hw/virtio/virtio-balloon-pci.c)&lt;/p&gt;
&lt;pre id=&quot;code_1781396930499&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_balloon_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
{
    VirtIOBalloonPCI *dev = VIRTIO_BALLOON_PCI(vpci_dev);
    DeviceState *vdev = DEVICE(&amp;amp;dev-&amp;gt;vdev);

    vpci_dev-&amp;gt;class_code = PCI_CLASS_OTHERS;

    qdev_realize(vdev, BUS(&amp;amp;vpci_dev-&amp;gt;bus), errp);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_balloon_pci_realize 함수는 먼저 VIRTIO_BALLOON_PCI 매크로를 통해 VirtIOPCIProxy 타입의 디바이스를 VirtIOBalloonPCI 디바이스로 변환한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;부모 클래스에서 자식 클래스로 변환하는 것에 해당한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781396949301&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtIOBalloonPCI *dev = VIRTIO_BALLOON_PCI(vpci_dev);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781396971390&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * virtio-balloon-pci: This extends VirtioPCIProxy.
 */
#define TYPE_VIRTIO_BALLOON_PCI &quot;virtio-balloon-pci-base&quot;
DECLARE_INSTANCE_CHECKER(VirtIOBalloonPCI, VIRTIO_BALLOON_PCI,
                         TYPE_VIRTIO_BALLOON_PCI)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781396981178&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED VirtIOBalloonPCI* VIRTIO_BALLOON_PCI(const void *obj)
{
    return OBJECT_CHECK(VirtIOBalloonPCI, obj, &quot;virtio-balloon-pci-base&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781396991175&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline G_GNUC_UNUSED VirtIOBalloonPCI* VIRTIO_BALLOON_PCI(const void *obj)
{
    return ((VirtIOBalloonPCI*)object_dynamic_cast_assert ((ObjectClass *)(obj), (&quot;virtio-balloon-pci-base&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이어서, 디바이스 VirtIOBalloonPCI의 VirtIOBalloon 부분을 얻는데, 이게 바로 실제 virtio balloon 디바이스다.&lt;/p&gt;
&lt;pre id=&quot;code_1781397009827&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DeviceState *vdev = DEVICE(&amp;amp;dev-&amp;gt;vdev);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781402945049&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define TYPE_DEVICE &quot;device&quot;
OBJECT_DECLARE_TYPE(DeviceState, DeviceClass, DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781402953659&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * OBJECT_DECLARE_TYPE:
 * @InstanceType: instance struct name
 * @ClassType: class struct name
 * @MODULE_OBJ_NAME: the object name in uppercase with underscore separators
 *
 * This macro is typically used in a header file, and will:
 *
 *   - create the typedefs for the object and class structs
 *   - register the type for use with g_autoptr
 *   - provide three standard type cast functions
 *
 * The object struct and class struct need to be declared manually.
 */
#define OBJECT_DECLARE_TYPE(InstanceType, ClassType, MODULE_OBJ_NAME) \
    typedef struct InstanceType InstanceType; \
    typedef struct ClassType ClassType; \
    \
    G_DEFINE_AUTOPTR_CLEANUP_FUNC(InstanceType, object_unref) \
    \
    DECLARE_OBJ_CHECKERS(InstanceType, ClassType, \
                         MODULE_OBJ_NAME, TYPE_##MODULE_OBJ_NAME)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;위쪽의 값을 대입하면, 우선 다음과 같이 펼쳐진다.&lt;/p&gt;
&lt;pre id=&quot;code_1781402974502&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef struct DeviceState DeviceState;
typedef struct DeviceClass DeviceClass;

G_DEFINE_AUTOPTR_CLEANUP_FUNC(DeviceState, object_unref)

DECLARE_OBJ_CHECKERS(DeviceState, DeviceClass, DEVICE, TYPE_DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;DECLARE_OBJ_CHECKERS 매크로의 정의도 include/qom/object.h에 있는데, 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781402984403&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * DECLARE_OBJ_CHECKERS:
 * @InstanceType: instance struct name
 * @ClassType: class struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the three standard type cast functions for a
 * QOM type.
 */
#define DECLARE_OBJ_CHECKERS(InstanceType, ClassType, OBJ_NAME, TYPENAME) \
    DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    \
    DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781403013795&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef struct DeviceState DeviceState;
typedef struct DeviceClass DeviceClass;

G_DEFINE_AUTOPTR_CLEANUP_FUNC(DeviceState, object_unref)

DECLARE_INSTANCE_CHECKER(DeviceState, DEVICE, TYPE_DEVICE)

DECLARE_CLASS_CHECKERS(DeviceClass, DEVICE, TYPE_DEVICE)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781403024767&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * DECLARE_INSTANCE_CHECKER:
 * @InstanceType: instance struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the instance type cast functions for a
 * QOM type.
 */
#define DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    static inline G_GNUC_UNUSED InstanceType * \
    OBJ_NAME(const void *obj) \
    { return OBJECT_CHECK(InstanceType, obj, TYPENAME); }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781403033686&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * DECLARE_CLASS_CHECKERS:
 * @ClassType: class struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the class type cast functions for a
 * QOM type.
 */
#define DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME) \
    static inline G_GNUC_UNUSED ClassType * \
    OBJ_NAME##_GET_CLASS(const void *obj) \
    { return OBJECT_GET_CLASS(ClassType, obj, TYPENAME); } \
    \
    static inline G_GNUC_UNUSED ClassType * \
    OBJ_NAME##_CLASS(const void *klass) \
    { return OBJECT_CLASS_CHECK(ClassType, klass, TYPENAME); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이로써, 위 식은 더 펼쳐서 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781403044020&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef struct DeviceState DeviceState;
typedef struct DeviceClass DeviceClass;

G_DEFINE_AUTOPTR_CLEANUP_FUNC(DeviceState, object_unref)

static inline G_GNUC_UNUSED DeviceState *DEVICE(const void *obj)
{
    return OBJECT_CHECK(DeviceState, obj, TYPE_DEVICE);
}

static inline G_GNUC_UNUSED DeviceClass* DEVICE_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(DeviceClass, obj, TYPE_DEVICE);
}

static inline G_GNUC_UNUSED DeviceClass* DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(DeviceClass, klass, TYPE_DEVICE);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;다시 TYPE_DEVICE의 실제 값을 대입하면, 최종적으로 다음과 같이 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781403069581&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef struct DeviceState DeviceState;
typedef struct DeviceClass DeviceClass;

G_DEFINE_AUTOPTR_CLEANUP_FUNC(DeviceState, object_unref)

static inline G_GNUC_UNUSED DeviceState *DEVICE(const void *obj)
{
    return OBJECT_CHECK(DeviceState, obj, &quot;device&quot;);
}

static inline G_GNUC_UNUSED DeviceClass* DEVICE_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(DeviceClass, obj, &quot;device&quot;);
}

static inline G_GNUC_UNUSED DeviceClass* DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(DeviceClass, klass, &quot;device&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOVFhx/dJMcad3nKYD/Xp2f2rc43LrSz1af2NVIH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOVFhx/dJMcad3nKYD/Xp2f2rc43LrSz1af2NVIH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOVFhx/dJMcad3nKYD/Xp2f2rc43LrSz1af2NVIH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOVFhx%2FdJMcad3nKYD%2FXp2f2rc43LrSz1af2NVIH0%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;609&quot; height=&quot;344&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;DEVICE 매크로를 통해 VirtIOBalloonPCI 디바이스의 VirtIOBalloon 부분을 얻는데, 이게 바로 실제 virtio balloon 디바이스다.&lt;/p&gt;
&lt;pre id=&quot;code_1781403094924&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DeviceState *vdev = DEVICE(&amp;amp;dev-&amp;gt;vdev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtIOBalloonPCI 구조체 관계도&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxZoW4/dJMcaar8DI7/XLs6F5et0zKeXLamlJuqi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxZoW4/dJMcaar8DI7/XLs6F5et0zKeXLamlJuqi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxZoW4/dJMcaar8DI7/XLs6F5et0zKeXLamlJuqi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxZoW4%2FdJMcaar8DI7%2FXLs6F5et0zKeXLamlJuqi1%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;489&quot; data-origin-width=&quot;1051&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;(5) qdev_realize 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이어서, hw/core/qdev.c의 qdev_realize 함수를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781403125322&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;qdev_realize(vdev, BUS(&amp;amp;vpci_dev-&amp;gt;bus), errp);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781403150312&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bool qdev_realize(DeviceState *dev, BusState *bus, Error **errp)
{
    assert(!dev-&amp;gt;realized &amp;amp;&amp;amp; !dev-&amp;gt;parent_bus);

    if (bus) {
        if (!qdev_set_parent_bus(dev, bus, errp)) {
            return false;
        }
    } else {
        assert(!DEVICE_GET_CLASS(dev)-&amp;gt;bus_type);
    }

    return object_property_set_bool(OBJECT(dev), &quot;realized&quot;, true, errp);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;qdev_realize 함수는 주로 두 가지 일을 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;virtio balloon 디바이스의 버스를 VirtIOPCIProxy 디바이스의 bus 멤버로 설정한다. 즉, 이 virtio balloon 디바이스를 virtio 버스에 연결한다.&lt;/li&gt;
&lt;li&gt;object_property_bool 함수를 호출해, virtio balloon 디바이스를 실체화한다. 이는 virtio_device_realize 함수의 실행을 유발한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 예전 버전에서는 qdev_realize 함수가 없었고, 그 안의 두 가지 작업은 virtio_balloon_pci_realize 함수 안에서 직접 완료됐었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x05zZ/dJMcaip8n23/vBEQytsXQGjQ8KS0XtFfCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x05zZ/dJMcaip8n23/vBEQytsXQGjQ8KS0XtFfCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x05zZ/dJMcaip8n23/vBEQytsXQGjQ8KS0XtFfCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx05zZ%2FdJMcaip8n23%2FvBEQytsXQGjQ8KS0XtFfCK%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;407&quot; height=&quot;266&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 시점에서 디바이스들의 관계는 아래와 같이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed2TOj/dJMcaicE8N7/bzs7kPSw7A5tKQoJiuA5D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed2TOj/dJMcaicE8N7/bzs7kPSw7A5tKQoJiuA5D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed2TOj/dJMcaicE8N7/bzs7kPSw7A5tKQoJiuA5D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fed2TOj%2FdJMcaicE8N7%2Fbzs7kPSw7A5tKQoJiuA5D1%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;491&quot; height=&quot;421&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;여기까지는 이 장치를 게스트에게 PCI 장치로 어떻게 노출할까를 담당하는 함수들 &amp;mdash; virtio_pci_dc_realize &amp;rarr; pci_qdev_realize &amp;rarr; virtio_pci_realize &amp;rarr; virtio_balloon_pci_realize &amp;rarr; qdev_realize &amp;mdash; 즉 transport/PCI 층의 realize 체인을 살펴봤다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 호출되는 virtio_device_realize부터는 virtio 장치 본연의 기능(virtqueue 설정, feature negotiation, balloon 동작 등)을 어떻게 초기화하는가 &amp;mdash; 즉 virtio core 층을 확인할 것이다.&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;그에 앞서, 지금까지 나온 핵심 개념들(realize 체인이 부모-자식으로 연결되는 방식, device_set_realized 진입점, VirtioPCIClass의 realize 등)을 한 번 더 정리하고 넘어가자.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2215&quot; data-origin-height=&quot;1468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vNEAL/dJMcahSf2Cl/bg7iP0vVBhMVG4DD8VxK3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vNEAL/dJMcahSf2Cl/bg7iP0vVBhMVG4DD8VxK3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vNEAL/dJMcahSf2Cl/bg7iP0vVBhMVG4DD8VxK3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvNEAL%2FdJMcahSf2Cl%2Fbg7iP0vVBhMVG4DD8VxK3k%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;2215&quot; height=&quot;1468&quot; data-origin-width=&quot;2215&quot; data-origin-height=&quot;1468&quot;/&gt;&lt;/span&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1913&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9EorN/dJMcaaMo2YB/6CclwsCZI0YeMQr0ZiaWzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9EorN/dJMcaaMo2YB/6CclwsCZI0YeMQr0ZiaWzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9EorN/dJMcaaMo2YB/6CclwsCZI0YeMQr0ZiaWzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9EorN%2FdJMcaaMo2YB%2F6CclwsCZI0YeMQr0ZiaWzk%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;1913&quot; height=&quot;190&quot; data-origin-width=&quot;1913&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio 디바이스 realize 과정 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_balloon_pci_instance_init() -&amp;gt; virtio_instance_init_common() -&amp;gt; object_initialize_child_with_props() -&amp;gt; object_initialize_child_with_propsv() -&amp;gt; object_initialize()의 과정을 확인했다. 그리고 이 과정으로부터 알 수 있듯이, virtio 디바이스가 생성 과정에서 별로 많은 일을 하지 않으며, 대부분의 일은 디바이스 realize 과정에서 한다는걸 알 수 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio balloon 디바이스 realize를 설명하기 전에, 먼저 디바이스 realize에서 호출되는 함수를 다시보자.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QEMU는 main 함수에서 모든 &quot;-device&quot; 파라미터를 realize한다 (실제로는 main 함수의 qemu_init 함수에서 진행된다).&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;디바이스의 realize 함수는 모두 device_set_realized 함수를 호출한다. 이 함수에서는 디바이스 클래스의 realize 함수를 호출할 것이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;-device 옵션과 서로 대응하는 코드는 softmmu/qdev-monitor.c 안에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781403856523&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;QemuOptsList qemu_device_opts = {
    .name = &quot;device&quot;,
    .implied_opt_name = &quot;driver&quot;,
    .head = QTAILQ_HEAD_INITIALIZER(qemu_device_opts.head),
    .desc = {
        /*
         * no elements =&amp;gt; accept any
         * sanity checking will happen later
         * when setting device properties
         */
        { /* end of list */ }
    },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781403868231&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void device_set_realized(Object *obj, bool value, Error **errp)
{
    DeviceState *dev = DEVICE(obj);
    DeviceClass *dc = DEVICE_GET_CLASS(dev);
    HotplugHandler *hotplug_ctrl;
    BusState *bus;
    NamedClockList *ncl;
    Error *local_err = NULL;
    bool unattached_parent = false;
    static int unattached_count;

    if (dev-&amp;gt;hotplugged &amp;amp;&amp;amp; !dc-&amp;gt;hotpluggable) {
        error_setg(errp, QERR_DEVICE_NO_HOTPLUG, object_get_typename(obj));
        return;
    }

    if (value &amp;amp;&amp;amp; !dev-&amp;gt;realized) {
        if (!check_only_migratable(obj, errp)) {
            goto fail;
        }

        if (!obj-&amp;gt;parent) {
            gchar *name = g_strdup_printf(&quot;device[%d]&quot;, unattached_count++);

            object_property_add_child(container_get(qdev_get_machine(),
                                                   &quot;/unattached&quot;),
                                      name, obj);
            unattached_parent = true;
            g_free(name);
        }

        hotplug_ctrl = qdev_get_hotplug_handler(dev);
        if (hotplug_ctrl) {
            hotplug_handler_pre_plug(hotplug_ctrl, dev, &amp;amp;local_err);
            if (local_err != NULL) {
                goto fail;
            }
        }

        if (dc-&amp;gt;realize) {
            dc-&amp;gt;realize(dev, &amp;amp;local_err);
            if (local_err != NULL) {
                goto fail;
            }
        }

        DEVICE_LISTENER_CALL(realize, Forward, dev);

        /*
         * always free/re-initialize here since the value cannot be cleaned up
         * in device_unrealize due to its usage later on in the unplug path
         */
        g_free(dev-&amp;gt;canonical_path);
        dev-&amp;gt;canonical_path = object_get_canonical_path(OBJECT(dev));
        QLIST_FOREACH(ncl, &amp;amp;dev-&amp;gt;clocks, node) {
            if (ncl-&amp;gt;alias) {
                continue;
            } else {
                clock_setup_canonical_path(ncl-&amp;gt;clock);
            }
        }

        if (qdev_get_vmsd(dev)) {
            if (vmstate_register_with_alias_id(VMSTATE_IF(dev),
                                               VMSTATE_INSTANCE_ID_ANY,
                                               qdev_get_vmsd(dev), dev,
                                               dev-&amp;gt;instance_id_alias,
                                               dev-&amp;gt;alias_required_for_version,
                                               &amp;amp;local_err) &amp;lt; 0) {
                goto post_realize_fail;
            }
        }

        /*
         * Clear the reset state, in case the object was previously unrealized
         * with a dirty state.
         */
        resettable_state_clear(&amp;amp;dev-&amp;gt;reset);

        QLIST_FOREACH(bus, &amp;amp;dev-&amp;gt;child_bus, sibling) {
            if (!qbus_realize(bus, errp)) {
                goto child_realize_fail;
            }
        }
        if (dev-&amp;gt;hotplugged) {
            /*
             * Reset the device, as well as its subtree which, at this point,
             * should be realized too.
             */
            resettable_assert_reset(OBJECT(dev), RESET_TYPE_COLD);
            resettable_change_parent(OBJECT(dev), OBJECT(dev-&amp;gt;parent_bus),
                                     NULL);
            resettable_release_reset(OBJECT(dev), RESET_TYPE_COLD);
        }
        dev-&amp;gt;pending_deleted_event = false;

        if (hotplug_ctrl) {
            hotplug_handler_plug(hotplug_ctrl, dev, &amp;amp;local_err);
            if (local_err != NULL) {
                goto child_realize_fail;
            }
        }

        qatomic_store_release(&amp;amp;dev-&amp;gt;realized, value);

    } else if (!value &amp;amp;&amp;amp; dev-&amp;gt;realized) {

        /*
         * Change the value so that any concurrent users are aware
         * that the device is going to be unrealized
         *
         * TODO: change .realized property to enum that states
         * each phase of the device realization/unrealization
         */
        qatomic_set(&amp;amp;dev-&amp;gt;realized, value);
        /*
         * Ensure that concurrent users see this update prior to
         * any other changes done by unrealize.
         */
        smp_wmb();

        QLIST_FOREACH(bus, &amp;amp;dev-&amp;gt;child_bus, sibling) {
            qbus_unrealize(bus);
        }
        if (qdev_get_vmsd(dev)) {
            vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev);
        }
        if (dc-&amp;gt;unrealize) {
            dc-&amp;gt;unrealize(dev);
        }
        dev-&amp;gt;pending_deleted_event = true;
        DEVICE_LISTENER_CALL(unrealize, Reverse, dev);
    }

    assert(local_err == NULL);
    return;

child_realize_fail:
    QLIST_FOREACH(bus, &amp;amp;dev-&amp;gt;child_bus, sibling) {
        qbus_unrealize(bus);
    }

    if (qdev_get_vmsd(dev)) {
        vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev);
    }

post_realize_fail:
    g_free(dev-&amp;gt;canonical_path);
    dev-&amp;gt;canonical_path = NULL;
    if (dc-&amp;gt;unrealize) {
        dc-&amp;gt;unrealize(dev);
    }

fail:
    error_propagate(errp, local_err);
    if (unattached_parent) {
        /*
         * Beware, this doesn't just revert
         * object_property_add_child(), it also runs bus_remove()!
         */
        object_unparent(OBJECT(dev));
        unattached_count--;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781403886554&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (dc-&amp;gt;realize) {
            dc-&amp;gt;realize(dev, &amp;amp;local_err);
            if (local_err != NULL) {
                goto fail;
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;디바이스 클래스의 realize 함수에서 가장 처음 호출되는 것은 DeviceClass의 realize 함수이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 회수 호출의 기본 함수는 xxx_device_realize()이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;당연히, 만약 Device를 상속한 클래스라면, 이 함수를 다시 쓸 수도 있다. 예를 들면 PCIDeviceClass류는 그 클래스 초기화 함수 pci_device_class_init()에서 DeviceClass-&amp;gt;realize를 pci_qdev_realize로 다시 썼다.&lt;/p&gt;
&lt;pre id=&quot;code_1781403914514&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void pci_device_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *k = DEVICE_CLASS(klass);

    k-&amp;gt;realize = pci_qdev_realize;
    k-&amp;gt;unrealize = pci_qdev_unrealize;
    k-&amp;gt;bus_type = TYPE_PCI_BUS;
    device_class_set_props(k, pci_props);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;PCIDeviceClass 자체로 말하자면, 그 PCIDeviceClass-&amp;gt;realize는 pci_default_realize 함수로 할당될 수 있다 (이곳은 단지 문제를 더 잘 설명하기 위해서다. 새 QEMU에는 이 함수가 이미 없다).&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;뒤에 PCIDeviceClass를 상속한 클래스는 자신의 클래스 초기화 함수에서 자기의 realize 함수를 할당할 수 있다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio 디바이스 클래스의 상속 체인 관계는 &quot;DeviceClass -&amp;gt; PCIDeviceClass -&amp;gt; VirtioPCIClass&quot;이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8VM4x/dJMcahLxgPN/9IosGvjb5Sx6USivrbxV5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8VM4x/dJMcahLxgPN/9IosGvjb5Sx6USivrbxV5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8VM4x/dJMcahLxgPN/9IosGvjb5Sx6USivrbxV5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8VM4x%2FdJMcahLxgPN%2F9IosGvjb5Sx6USivrbxV5K%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;964&quot; height=&quot;94&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;VirtioPCIClass의 realize 함수 분석&lt;/h4&gt;
&lt;table style=&quot;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;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;realize 함수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeviceClass&lt;/td&gt;
&lt;td&gt;pci_qdev_realize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCIDeviceClass&lt;/td&gt;
&lt;td&gt;pci_default_realize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtioPCIClass의 관련 정의는 모든 virtio PCI proxy 디바이스의 부 디바이스 TYPE_VIRTIO_PCI에서 진행한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그중 클래스 초기화 함수는 virtio_pci_class_init이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404031126&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
    VirtioPCIClass *vpciklass = VIRTIO_PCI_CLASS(klass);
    ResettableClass *rc = RESETTABLE_CLASS(klass);

    device_class_set_props(dc, virtio_pci_properties);
    k-&amp;gt;realize = virtio_pci_realize;
    k-&amp;gt;exit = virtio_pci_exit;
    k-&amp;gt;vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
    k-&amp;gt;revision = VIRTIO_PCI_ABI_VERSION;
    k-&amp;gt;class_id = PCI_CLASS_OTHERS;
    device_class_set_parent_realize(dc, virtio_pci_dc_realize,
                                    &amp;amp;vpciklass-&amp;gt;parent_dc_realize);
    rc-&amp;gt;phases.hold = virtio_pci_bus_reset_hold;
}

static const TypeInfo virtio_pci_info = {
    .name          = TYPE_VIRTIO_PCI,
    .parent        = TYPE_PCI_DEVICE,
    .instance_size = sizeof(VirtIOPCIProxy),
    .class_init    = virtio_pci_class_init,
    .class_size    = sizeof(VirtioPCIClass),
    .abstract      = true,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;앞 회차에서 말한 바와 같이, virtio_pci_class_init 함수는 가장 먼저 PCIDeviceClass-&amp;gt;realize 함수 포인터를 자기의 virtio_pci_realize 함수로 교체한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404049480&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;k-&amp;gt;realize = virtio_pci_realize;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781404058614&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;device_class_set_parent_realize(dc, virtio_pci_dc_realize,
                                &amp;amp;vpciklass-&amp;gt;parent_dc_realize);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781404068275&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void device_class_set_parent_realize(DeviceClass *dc,
                                     DeviceRealize dev_realize,
                                     DeviceRealize *parent_realize)
{
    *parent_realize = dc-&amp;gt;realize;
    dc-&amp;gt;realize = dev_realize;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;device_class_set_parent 함수는 위아래 글에서 먼저 vpciklass-&amp;gt;parent_dc_realize를 dc-&amp;gt;realize로 할당한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 값이 바로 pci_qdev_realize이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;그러고 나서 dc-&amp;gt;realize를 virtio_pci_dc_realize (중간 변수 dev_realize를 통해서)로 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통상적으로 말하면, 부모 클래스의 realize 함수는 자식 클래스의 realize 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 DeviceClass-&amp;gt;realize (pci_qdev_realize)가 PCIDeviceClass-&amp;gt;realize 함수를 호출할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 PCIDeviceClass-&amp;gt;realize 호출은 자식 클래스의 realize 함수를 호출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 두 줄 코드는 이 순서를 바꿨다. &lt;br /&gt;여기에서, dc-&amp;gt;realize는 virtio_pci_dc_realize로 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeviceClass-&amp;gt;realize 즉 dc-&amp;gt;realize가 가리키는 함수가 먼저 실행될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이유로 virtio_pci_dc_realize 함수가 가장 먼저 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 나서 원래의 dc-&amp;gt;realize 즉 pci_qdev_realize 함수를 VirtioPCIClass-&amp;gt;parent_dc_realize 함수에 보존한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7lEK8/dJMcaiRcgbK/jjO7BQFr9bzCEsSsl5qpck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7lEK8/dJMcaiRcgbK/jjO7BQFr9bzCEsSsl5qpck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7lEK8/dJMcaiRcgbK/jjO7BQFr9bzCEsSsl5qpck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7lEK8%2FdJMcaiRcgbK%2FjjO7BQFr9bzCEsSsl5qpck%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;486&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;통상 디바이스 realize 과정에서, 자식 클래스형의 realize 함수가 먼저 어떤 일을 해야 할 때 이런 방법을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio balloon PCI proxy 디바이스 유형의 초기화 함수 virtio_balloon_pci_class_init()로 돌아가 본다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404230134&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_balloon_pci_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
    PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);

    k-&amp;gt;realize = virtio_balloon_pci_realize;
    set_bit(DEVICE_CATEGORY_MISC, dc-&amp;gt;categories);
    pcidev_k-&amp;gt;vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET;
    pcidev_k-&amp;gt;device_id = PCI_DEVICE_ID_VIRTIO_BALLOON;
    pcidev_k-&amp;gt;revision = VIRTIO_PCI_ABI_VERSION;
    pcidev_k-&amp;gt;class_id = PCI_CLASS_OTHERS;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;볼 수 있듯이, 그중 VirtioPCIClass-&amp;gt;realize 함수 포인터이 virtio_balloon_pci_realize()를 가리키도록 할당되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종합해서 말하면, virtio balloon 관련 류가 미치는 realize 함수는 아래 표와 같이 보인다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;1529:1-1533:67;48993-49197&quot;&gt;
&lt;table style=&quot;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;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; realize 함수 &lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; parent_dc_realize 함수 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeviceClass&lt;/td&gt;
&lt;td&gt;virtio_pci_dc_realize&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCIDeviceClass&lt;/td&gt;
&lt;td&gt;virtio_pci_realize&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VirtioPCIClass&lt;/td&gt;
&lt;td&gt;virtio_balloon_pci_realize&lt;/td&gt;
&lt;td&gt;pci_qdev_realize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;1539:1-1539:14;49241-49254&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;device_set_realized 함수의 호출 흐름 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio PCI proxy 디바이스의 realize 속성을 할당할 때, device_set_realized 함수에서 가장 먼저 호출되는 것은 DeviceClass-&amp;gt;realize 함수 포인터이 가리키는 함수이다. 즉 virtio_pci_dc_realize 함수이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404325475&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (dc-&amp;gt;realize) {
            dc-&amp;gt;realize(dev, &amp;amp;local_err);
            if (local_err != NULL) {
                goto fail;
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_dc_realize 함수에서는 VirtioPCIClass-&amp;gt;parent_dc_realize를 호출한다. 즉 pci_qdev_realize 함수이다&lt;/p&gt;
&lt;pre id=&quot;code_1781404343528&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vpciklass-&amp;gt;parent_dc_realize(qdev, errp);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781404350946&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void pci_qdev_realize(DeviceState *qdev, Error **errp)
{
    PCIDevice *pci_dev = (PCIDevice *)qdev;
    PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
    ObjectClass *klass = OBJECT_CLASS(pc);
    Error *local_err = NULL;
    bool is_default_rom;
    uint16_t class_id;

    /*
     * capped by systemd (see: udev-builtin-net_id.c)
     * as it's the only known user honor it to avoid users
     * misconfigure QEMU and then wonder why acpi-index doesn't work
     */
    if (pci_dev-&amp;gt;acpi_index &amp;gt; ONBOARD_INDEX_MAX) {
        error_setg(errp, &quot;acpi-index should be less or equal to %u&quot;,
                    ONBOARD_INDEX_MAX);
        return;
    }

    /*
     * make sure that acpi-index is unique across all present PCI devices
     */
    if (pci_dev-&amp;gt;acpi_index) {
        GSequence *used_indexes = pci_acpi_index_list();

        if (g_sequence_lookup(used_indexes,
                              GINT_TO_POINTER(pci_dev-&amp;gt;acpi_index),
                              g_cmp_uint32, NULL)) {
            error_setg(errp, &quot;a PCI device with acpi-index = %&quot; PRIu32
                       &quot; already exist&quot;, pci_dev-&amp;gt;acpi_index);
            return;
        }

        g_sequence_insert_sorted(used_indexes,
                                 GINT_TO_POINTER(pci_dev-&amp;gt;acpi_index),
                                 g_cmp_uint32, NULL);
    }

    if (pci_dev-&amp;gt;romsize != -1 &amp;amp;&amp;amp; !is_power_of_2(pci_dev-&amp;gt;romsize)) {
        error_setg(errp, &quot;ROM size %u is not a power of two&quot;, pci_dev-&amp;gt;romsize);
        return;
    }

    /* initialize cap_present for pci_is_express() and pci_config_size(),
     * Note that hybrid PCIs are not set automatically and need to manage
     * QEMU_PCI_CAP_EXPRESS */
    if (object_class_dynamic_cast(klass, INTERFACE_PCIE_DEVICE) &amp;amp;&amp;amp;
       !object_class_dynamic_cast(klass, INTERFACE_CONVENTIONAL_PCI_DEVICE)) {
        pci_dev-&amp;gt;cap_present |= QEMU_PCI_CAP_EXPRESS;
    }

    if (object_class_dynamic_cast(klass, INTERFACE_CXL_DEVICE)) {
        pci_dev-&amp;gt;cap_present |= QEMU_PCIE_CAP_CXL;
    }

    pci_dev = do_pci_register_device(pci_dev,
                                     object_get_typename(OBJECT(qdev)),
                                     pci_dev-&amp;gt;devfn, errp);
    if (pci_dev == NULL)
        return;

    if (pc-&amp;gt;realize) {
        pc-&amp;gt;realize(pci_dev, &amp;amp;local_err);
        if (local_err) {
            error_propagate(errp, local_err);
            do_pci_unregister_device(pci_dev);
            return;
        }
    }

    /*
     * A PCIe Downstream Port that do not have ARI Forwarding enabled must
     * associate only Device 0 with the device attached to the bus
     * representing the Link from the Port (PCIe base spec rev 4.0 ver 0.3,
     * sec 7.3.1).
     * With ARI, PCI_SLOT() can return non-zero value as the traditional
     * 5-bit Device Number and 3-bit Function Number fields in its associated
     * Routing IDs, Requester IDs and Completer IDs are interpreted as a
     * single 8-bit Function Number. Hence, ignore ARI capable devices.
     */
    if (pci_is_express(pci_dev) &amp;amp;&amp;amp;
        !pcie_find_capability(pci_dev, PCI_EXT_CAP_ID_ARI) &amp;amp;&amp;amp;
        pcie_has_upstream_port(pci_dev) &amp;amp;&amp;amp;
        PCI_SLOT(pci_dev-&amp;gt;devfn)) {
        warn_report(&quot;PCI: slot %d is not valid for %s,&quot;
                    &quot; parent device only allows plugging into slot 0.&quot;,
                    PCI_SLOT(pci_dev-&amp;gt;devfn), pci_dev-&amp;gt;name);
    }

    if (pci_dev-&amp;gt;failover_pair_id) {
        if (!pci_bus_is_express(pci_get_bus(pci_dev))) {
            error_setg(errp, &quot;failover primary device must be on &quot;
                             &quot;PCIExpress bus&quot;);
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        class_id = pci_get_word(pci_dev-&amp;gt;config + PCI_CLASS_DEVICE);
        if (class_id != PCI_CLASS_NETWORK_ETHERNET) {
            error_setg(errp, &quot;failover primary device is not an &quot;
                             &quot;Ethernet device&quot;);
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        if ((pci_dev-&amp;gt;cap_present &amp;amp; QEMU_PCI_CAP_MULTIFUNCTION)
            || (PCI_FUNC(pci_dev-&amp;gt;devfn) != 0)) {
            error_setg(errp, &quot;failover: primary device must be in its own &quot;
                              &quot;PCI slot&quot;);
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        qdev-&amp;gt;allow_unplug_during_migration = true;
    }

    /* rom loading */
    is_default_rom = false;
    if (pci_dev-&amp;gt;romfile == NULL &amp;amp;&amp;amp; pc-&amp;gt;romfile != NULL) {
        pci_dev-&amp;gt;romfile = g_strdup(pc-&amp;gt;romfile);
        is_default_rom = true;
    }

    pci_add_option_rom(pci_dev, is_default_rom, &amp;amp;local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        pci_qdev_unrealize(DEVICE(pci_dev));
        return;
    }

    pci_set_power(pci_dev, true);

    pci_dev-&amp;gt;msi_trigger = pci_msi_trigger;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;pci_qdev_realize 함수에서는 PCIDeviceClass의 realize 함수 포인터이 가리키는 함수를 호출한다. 즉 virtio_pci_realize 함수이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404373626&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
    &amp;hellip;&amp;hellip;
    if (pc-&amp;gt;realize) {
        pc-&amp;gt;realize(pci_dev, &amp;amp;local_err);
        if (local_err) {
            error_propagate(errp, local_err);
            do_pci_unregister_device(pci_dev);
            return;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781404383361&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
    VirtioPCIClass *k = VIRTIO_PCI_GET_CLASS(pci_dev);
    bool pcie_port = pci_bus_is_express(pci_get_bus(pci_dev)) &amp;amp;&amp;amp;
                     !pci_bus_is_root(pci_get_bus(pci_dev));

    if (kvm_enabled() &amp;amp;&amp;amp; !kvm_has_many_ioeventfds()) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /* fd-based ioevents can't be synchronized in record/replay */
    if (replay_mode != REPLAY_MODE_NONE) {
        proxy-&amp;gt;flags &amp;amp;= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

    /*
     * virtio pci bar layout used by default.
     * subclasses can re-arrange things if needed.
     *
     *   region 0   --  virtio legacy io bar
     *   region 1   --  msi-x bar
     *   region 2   --  virtio modern io bar (off by default)
     *   region 4+5 --  virtio modern memory (64bit) bar
     *
     */
    proxy-&amp;gt;legacy_io_bar_idx  = 0;
    proxy-&amp;gt;msix_bar_idx       = 1;
    proxy-&amp;gt;modern_io_bar_idx  = 2;
    proxy-&amp;gt;modern_mem_bar_idx = 4;

    proxy-&amp;gt;common.offset = 0x0;
    proxy-&amp;gt;common.size = 0x1000;
    proxy-&amp;gt;common.type = VIRTIO_PCI_CAP_COMMON_CFG;

    proxy-&amp;gt;isr.offset = 0x1000;
    proxy-&amp;gt;isr.size = 0x1000;
    proxy-&amp;gt;isr.type = VIRTIO_PCI_CAP_ISR_CFG;

    proxy-&amp;gt;device.offset = 0x2000;
    proxy-&amp;gt;device.size = 0x1000;
    proxy-&amp;gt;device.type = VIRTIO_PCI_CAP_DEVICE_CFG;

    proxy-&amp;gt;notify.offset = 0x3000;
    proxy-&amp;gt;notify.size = virtio_pci_queue_mem_mult(proxy) * VIRTIO_QUEUE_MAX;
    proxy-&amp;gt;notify.type = VIRTIO_PCI_CAP_NOTIFY_CFG;

    proxy-&amp;gt;notify_pio.offset = 0x0;
    proxy-&amp;gt;notify_pio.size = 0x4;
    proxy-&amp;gt;notify_pio.type = VIRTIO_PCI_CAP_NOTIFY_CFG;

    /* subclasses can enforce modern, so do this unconditionally */
    memory_region_init(&amp;amp;proxy-&amp;gt;modern_bar, OBJECT(proxy), &quot;virtio-pci&quot;,
                       /* PCI BAR regions must be powers of 2 */
                       pow2ceil(proxy-&amp;gt;notify.offset + proxy-&amp;gt;notify.size));

    if (proxy-&amp;gt;disable_legacy == ON_OFF_AUTO_AUTO) {
        proxy-&amp;gt;disable_legacy = pcie_port ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
    }

    if (!virtio_pci_modern(proxy) &amp;amp;&amp;amp; !virtio_pci_legacy(proxy)) {
        error_setg(errp, &quot;device cannot work as neither modern nor legacy mode&quot;
                   &quot; is enabled&quot;);
        error_append_hint(errp, &quot;Set either disable-modern or disable-legacy&quot;
                          &quot; to off\n&quot;);
        return;
    }

    if (pcie_port &amp;amp;&amp;amp; pci_is_express(pci_dev)) {
        int pos;
        uint16_t last_pcie_cap_offset = PCI_CONFIG_SPACE_SIZE;

        pos = pcie_endpoint_cap_init(pci_dev, 0);
        assert(pos &amp;gt; 0);

        pos = pci_add_capability(pci_dev, PCI_CAP_ID_PM, 0,
                                 PCI_PM_SIZEOF, errp);
        if (pos &amp;lt; 0) {
            return;
        }

        pci_dev-&amp;gt;exp.pm_cap = pos;

        /*
         * Indicates that this function complies with revision 1.2 of the
         * PCI Power Management Interface Specification.
         */
        pci_set_word(pci_dev-&amp;gt;config + pos + PCI_PM_PMC, 0x3);

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_AER) {
            pcie_aer_init(pci_dev, PCI_ERR_VER, last_pcie_cap_offset,
                          PCI_ERR_SIZEOF, NULL);
            last_pcie_cap_offset += PCI_ERR_SIZEOF;
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_DEVERR) {
            /* Init error enabling flags */
            pcie_cap_deverr_init(pci_dev);
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_LNKCTL) {
            /* Init Link Control Register */
            pcie_cap_lnkctl_init(pci_dev);
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_PM) {
            /* Init Power Management Control Register */
            pci_set_word(pci_dev-&amp;gt;config + pos + PCI_PM_CTRL,
                         PCI_PM_CTRL_STATE_MASK);
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_ATS) {
            pcie_ats_init(pci_dev, last_pcie_cap_offset,
                          proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_ATS_PAGE_ALIGNED);
            last_pcie_cap_offset += PCI_EXT_CAP_ATS_SIZEOF;
        }

        if (proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_INIT_FLR) {
            /* Set Function Level Reset capability bit */
            pcie_cap_flr_init(pci_dev);
        }
    } else {
        /*
         * make future invocations of pci_is_express() return false
         * and pci_config_size() return PCI_CONFIG_SPACE_SIZE.
         */
        pci_dev-&amp;gt;cap_present &amp;amp;= ~QEMU_PCI_CAP_EXPRESS;
    }

    virtio_pci_bus_new(&amp;amp;proxy-&amp;gt;bus, sizeof(proxy-&amp;gt;bus), proxy);
    if (k-&amp;gt;realize) {
        k-&amp;gt;realize(proxy, errp);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 함수의 가장 마지막에 VirtioPCIClass의 realize 함수 포인터이 가리키는 함수를 호출할 것이다. 즉 virtio_balloon_pci_realize 함수이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404404557&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_realize(PCIDevice *pci_dev, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(pci_dev);
    VirtioPCIClass *k = VIRTIO_PCI_GET_CLASS(pci_dev);
    &amp;hellip;&amp;hellip;
    if (k-&amp;gt;realize) {
        k-&amp;gt;realize(proxy, errp);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;전체 realize 호출 흐름 다이어그램&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8xX9I/dJMcaijnaXh/sAr30gC8Hkq3pnIfXNLXBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8xX9I/dJMcaijnaXh/sAr30gC8Hkq3pnIfXNLXBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8xX9I/dJMcaijnaXh/sAr30gC8Hkq3pnIfXNLXBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8xX9I%2FdJMcaijnaXh%2FsAr30gC8Hkq3pnIfXNLXBK%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;1606&quot; height=&quot;754&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;핵심 호출 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;1006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blTcc6/dJMcaf1dvR0/9V8jPuTOsbb9XIgbTozUlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blTcc6/dJMcaf1dvR0/9V8jPuTOsbb9XIgbTozUlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blTcc6/dJMcaf1dvR0/9V8jPuTOsbb9XIgbTozUlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblTcc6%2FdJMcaf1dvR0%2F9V8jPuTOsbb9XIgbTozUlK%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;204&quot; height=&quot;744&quot; data-origin-width=&quot;276&quot; data-origin-height=&quot;1006&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;instance_init 단계는 별일 안 한다. 그저 object_initialize까지 내려가서 메모리 초기화하고 자식 속성으로 부모에 붙이는 정도&lt;/li&gt;
&lt;li&gt;realize 단계가 진짜 일을 한다. device_set_realized에서 시작해서 dc-&amp;gt;realize -&amp;gt; parent_dc_realize -&amp;gt; pc-&amp;gt;realize -&amp;gt; k-&amp;gt;realize 순서로 위에서 아래로 차례로 호출된다.&lt;/li&gt;
&lt;li&gt;device_class_set_parent_realize의 트릭이 핵심이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통은 부모 클래스 &amp;rarr; 자식 클래스 순으로 realize가 흘러가지만, virtio 같이 자식 클래스의 realize가 먼저 일을 좀 해야 하는 경우에는 이 함수로 순서를 뒤집어서 virtio_pci_dc_realize가 먼저 실행되고 그 안에서 원래의 pci_qdev_realize를 호출하도록 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size23&quot;&gt;2) virtio_device_realize 와 virtio_balloon_device_realize&lt;/h3&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_device_realize 함수 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 분석에서 virtio_balloon_pci_realize 함수의 마지막 단계에서 object_property_set 함수를 호출했고, 그로 인해 virtio_device_realize 함수가 실행됐다는 걸 다뤘다. 이번에는 virtio_device_realize 함수에 대해 분석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_device_realize 함수도 사실 범용 함수다. TYPE_VIRTIO_DEVICE 추상 디바이스의 realize 함수다. 모든 virtio 디바이스가 초기화될 때 이 함수를 호출한다.&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;virtio_device_realize의 5단계 구조&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbi67G/dJMb997NlYd/9dGZPM63X1YXPJp0QWi6C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbi67G/dJMb997NlYd/9dGZPM63X1YXPJp0QWi6C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbi67G/dJMb997NlYd/9dGZPM63X1YXPJp0QWi6C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbi67G%2FdJMb997NlYd%2F9dGZPM63X1YXPJp0QWi6C1%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;208&quot; height=&quot;681&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) virtio_device_realize 함수는 먼저 virtio 디바이스가 속한 클래스를 얻는다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781404559337&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 그러고 나서 구체적인 클래스의 realize 함수를 호출한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio balloon 디바이스 입장에서는 virtio_balloon_device_realize 함수다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404605260&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (vdc-&amp;gt;realize != NULL) {
    vdc-&amp;gt;realize(dev, &amp;amp;err);
    if (err != NULL) {
        error_propagate(errp, err);
        return;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(3) 다음으로 virtio_bus_device_plugged 함수를 호출해서 virtio 디바이스를 virtio 버스에 연결한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781404629756&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_bus_device_plugged(vdev, &amp;amp;err);
if (err != NULL) {
    error_propagate(errp, err);
    vdc-&amp;gt;unrealize(dev);
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(4) 그 후, memory_listener_register 함수를 호출해서 메모리 리스너를 등록한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781404674876&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;memory_listener_register(&amp;amp;vdev-&amp;gt;listener, vdev-&amp;gt;dma_as);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781404706737&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * memory_listener_register: register callbacks to be called when memory
 *                           sections are mapped or unmapped into an address
 *                           space
 *
 * @listener: an object containing the callbacks to be called
 * @filter: if non-%NULL, only regions in this address space will be observed
 */
void memory_listener_register(MemoryListener *listener, AddressSpace *filter);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 함수는 lister(여기서는 vdev-&amp;gt;listener)를 filter 주소 공간(여기서는 vdev-&amp;gt;dma_as)에 등록한다. filter 주소 공간의 토폴로지 구조에 변화가 생기면, 그 연결 리스트 위의 모든 listener를 호출해서 관련 콜백 함수를 호출한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; 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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNwyu2/dJMcacwH1kg/BdzXS1kxE0nexHRkthIEv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNwyu2/dJMcacwH1kg/BdzXS1kxE0nexHRkthIEv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNwyu2/dJMcacwH1kg/BdzXS1kxE0nexHRkthIEv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNwyu2%2FdJMcacwH1kg%2FBdzXS1kxE0nexHRkthIEv1%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;1127&quot; height=&quot;184&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(5) 마지막으로 QTAILQ_INSERT_TAIL()을 호출해서 vdev를 virtio_list 끝에 삽입한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781404751760&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;QTAILQ_INSERT_TAIL(&amp;amp;virtio_list, vdev, next);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;QTAILQ_INSERT_TAIL은 매크로 하나인데, 원소를 큐 끝에 삽입하는 데 쓴다. QTAIL_INSERT_TAIL 정의는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781404771756&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define QTAILQ_INSERT_TAIL(head, elm, field) do {                       \
        (elm)-&amp;gt;field.tqe_next = NULL;                                   \
        (elm)-&amp;gt;field.tqe_circ.tql_prev = (head)-&amp;gt;tqh_circ.tql_prev;     \
        (head)-&amp;gt;tqh_circ.tql_prev-&amp;gt;tql_next = (elm);                    \
        (head)-&amp;gt;tqh_circ.tql_prev = &amp;amp;(elm)-&amp;gt;field.tqe_circ;             \
} while (/*CONSTCOND*/0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781404786808&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;do {                                                            \
    (vdev)-&amp;gt;next.tqe_next = NULL;                               \
    (vdev)-&amp;gt;next.tqe_circ.tql_prev = (&amp;amp;virtio_list)-&amp;gt;tqh_circ.tql_prev;     \
    (&amp;amp;virtio_list)-&amp;gt;tqh_circ.tql_prev-&amp;gt;tql_next = (vdev);       \
    (&amp;amp;virtio_list)-&amp;gt;tqh_circ.tql_prev = &amp;amp;(vdev)-&amp;gt;next.tqe_circ; \
} while (/*CONSTCOND*/0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_list에 vdev가 삽입되는 모습&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l6j8P/dJMcabLoiVY/KHC7E9iGKyDKekO7cQb1YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l6j8P/dJMcabLoiVY/KHC7E9iGKyDKekO7cQb1YK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l6j8P/dJMcabLoiVY/KHC7E9iGKyDKekO7cQb1YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl6j8P%2FdJMcabLoiVY%2FKHC7E9iGKyDKekO7cQb1YK%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;662&quot; height=&quot;67&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_balloon_device_realize 함수 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_device_realize 함수가 구체적인 클래스의 realize 함수를 호출한다고 했는데, virtio balloon 디바이스 입장에서는 그게 virtio_balloon_device_realize 함수다&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;구체적인 클래스의 realize 함수를 호출하는 부분은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405061312&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (vdc-&amp;gt;realize != NULL) {
    vdc-&amp;gt;realize(dev, &amp;amp;err);
    if (err != NULL) {
        error_propagate(errp, err);
        return;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781405070898&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_balloon_device_realize(DeviceState *dev, Error **errp)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
    VirtIOBalloon *s = VIRTIO_BALLOON(dev);
    int ret;

    virtio_init(vdev, VIRTIO_ID_BALLOON, virtio_balloon_config_size(s));

    ret = qemu_add_balloon_handler(virtio_balloon_to_target,
                                    virtio_balloon_stat, s);

    if (ret &amp;lt; 0) {
        error_setg(errp, &quot;Only one balloon device is supported&quot;);
        virtio_cleanup(vdev);
        return;
    }

    if (virtio_has_feature(s-&amp;gt;host_features, VIRTIO_BALLOON_F_FREE_PAGE_HINT) &amp;amp;&amp;amp;
        !s-&amp;gt;iothread) {
        error_setg(errp, &quot;'free-page-hint' requires 'iothread' to be set&quot;);
        virtio_cleanup(vdev);
        return;
    }

    s-&amp;gt;ivq = virtio_add_queue(vdev, 128, virtio_balloon_handle_output);
    s-&amp;gt;dvq = virtio_add_queue(vdev, 128, virtio_balloon_handle_output);
    s-&amp;gt;svq = virtio_add_queue(vdev, 128, virtio_balloon_receive_stats);

    if (virtio_has_feature(s-&amp;gt;host_features, VIRTIO_BALLOON_F_FREE_PAGE_HINT)) {
        s-&amp;gt;free_page_vq = virtio_add_queue(vdev, VIRTQUEUE_MAX_SIZE,
                                            virtio_balloon_handle_free_page_vq);
        precopy_add_notifier(&amp;amp;s-&amp;gt;free_page_hint_notify);

        object_ref(OBJECT(s-&amp;gt;iothread));
        s-&amp;gt;free_page_bh = aio_bh_new_guarded(iothread_get_aio_context(s-&amp;gt;iothread),
                                              virtio_balloon_get_free_page_hints, s,
                                              &amp;amp;dev-&amp;gt;mem_reentrancy_guard);
    }

    if (virtio_has_feature(s-&amp;gt;host_features, VIRTIO_BALLOON_F_REPORTING)) {
        s-&amp;gt;reporting_vq = virtio_add_queue(vdev, 32,
                                            virtio_balloon_handle_report);
    }

    reset_stats(s);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;같은 파일(hw/virtio/virtio-balloon.c)의 virtio_balloon_class_init 함수 안에서, virtio_balloon_device_realize 함수(주소)를 vdc-&amp;gt;realize에 할당해놨다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405093541&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_balloon_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);

    device_class_set_props(dc, virtio_balloon_properties);
    dc-&amp;gt;vmsd = &amp;amp;vmstate_virtio_balloon;
    set_bit(DEVICE_CATEGORY_MISC, dc-&amp;gt;categories);
    vdc-&amp;gt;realize = virtio_balloon_device_realize;
    vdc-&amp;gt;unrealize = virtio_balloon_device_unrealize;
    vdc-&amp;gt;reset = virtio_balloon_device_reset;
    vdc-&amp;gt;get_config = virtio_balloon_get_config;
    vdc-&amp;gt;set_config = virtio_balloon_set_config;
    vdc-&amp;gt;get_features = virtio_balloon_get_features;
    vdc-&amp;gt;set_status = virtio_balloon_set_status;
    vdc-&amp;gt;vmsd = &amp;amp;vmstate_virtio_device;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_balloon_class_init에서 콜백이 할당되는 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCk7k/dJMcabLoiV4/XwwNBVIRmqXH4bnSPfEBvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCk7k/dJMcabLoiV4/XwwNBVIRmqXH4bnSPfEBvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCk7k/dJMcabLoiV4/XwwNBVIRmqXH4bnSPfEBvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCk7k%2FdJMcabLoiV4%2FXwwNBVIRmqXH4bnSPfEBvk%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;1088&quot; height=&quot;694&quot; data-origin-width=&quot;1088&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_balloon_device_realize()는 virtio balloon 디바이스의 realize 함수다. TYPE_VIRTIO_BALLOON_DEVICE의 realize를 구현하는 데 쓴다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) virtio_balloon_device_realize 함수는 먼저 virtio_init 함수를 호출해서 virtio 디바이스의 공통 부분을 초기화한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781405149154&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_init(vdev, VIRTIO_ID_BALLOON, virtio_balloon_config_size(s));&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781405158266&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void virtio_init(VirtIODevice *vdev, uint16_t device_id, size_t config_size)
{
    BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
    VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
    int i;
    int nvectors = k-&amp;gt;query_nvectors ? k-&amp;gt;query_nvectors(qbus-&amp;gt;parent) : 0;

    if (nvectors) {
        vdev-&amp;gt;vector_queues =
            g_malloc0(sizeof(*vdev-&amp;gt;vector_queues) * nvectors);
    }

    vdev-&amp;gt;start_on_kick = false;
    vdev-&amp;gt;started = false;
    vdev-&amp;gt;vhost_started = false;
    vdev-&amp;gt;device_id = device_id;
    vdev-&amp;gt;status = 0;
    qatomic_set(&amp;amp;vdev-&amp;gt;isr, 0);
    vdev-&amp;gt;queue_sel = 0;
    vdev-&amp;gt;config_vector = VIRTIO_NO_VECTOR;
    vdev-&amp;gt;vq = g_new0(VirtQueue, VIRTIO_QUEUE_MAX);
    vdev-&amp;gt;vm_running = runstate_is_running();
    vdev-&amp;gt;broken = false;
    for (i = 0; i &amp;lt; VIRTIO_QUEUE_MAX; i++) {
        vdev-&amp;gt;vq[i].vector = VIRTIO_NO_VECTOR;
        vdev-&amp;gt;vq[i].vdev = vdev;
        vdev-&amp;gt;vq[i].queue_index = i;
        vdev-&amp;gt;vq[i].host_notifier_enabled = false;
    }

    vdev-&amp;gt;name = virtio_id_to_name(device_id);
    vdev-&amp;gt;config_len = config_size;
    if (vdev-&amp;gt;config_len) {
        vdev-&amp;gt;config = g_malloc0(config_size);
    } else {
        vdev-&amp;gt;config = NULL;
    }
    vdev-&amp;gt;vmstate = qdev_add_vm_change_state_handler(DEVICE(vdev),
                                                      virtio_vmstate_change, vdev);
    vdev-&amp;gt;device_endian = virtio_default_endian();
    vdev-&amp;gt;use_guest_notifier_mask = true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_init 함수가 하는 일은 모든 virtio 디바이스의 기반 클래스 TYPE_VIRTIO_DEVICE의 인스턴스 VirtIODevice 구조체를 초기화하는 거다. VirtIODevice의 멤버들에 대해 초기화한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VirtIODevice의 vector_queues 멤버와 config_vector 멤버는 MSI 인터럽트와 관련이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device_id, status, name 멤버는 각각 디바이스의 id, 상태, 이름을 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isr 멤버는 인터럽트 요청을 표시하는 데 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;queue_sel 멤버는 디바이스 큐 열의 선택을 진행하는 데 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vq 멤버는 이 디바이스의 virtio queue를 표시하는데, 여기서는 VIRTIO_QUEUE_MAX개의 queue를 할당했고, 또한 초기화를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config_len과 config 멤버는 각각 이 virtio 디바이스 설정 공간의 길이와 데이터 저장 영역을 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;use_guest_notifier_mask 멤버는 irqfd와 관련이 있다.&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;VirtIODevice 구조체 멤버&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1397&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O3qd5/dJMcahSf2Ep/eKfgBmKe8bvhFXtXWaS9b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O3qd5/dJMcahSf2Ep/eKfgBmKe8bvhFXtXWaS9b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O3qd5/dJMcahSf2Ep/eKfgBmKe8bvhFXtXWaS9b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO3qd5%2FdJMcahSf2Ep%2FeKfgBmKe8bvhFXtXWaS9b0%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;1397&quot; height=&quot;246&quot; data-origin-width=&quot;1397&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) virtio_init 함수가 VirtIODevice를 초기화한 후에, virtio_add_queue 함수를 호출해서 3개의 virtqueue를 만든다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781405227636&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;s-&amp;gt;ivq = virtio_add_queue(vdev, 128, virtio_balloon_handle_output);
s-&amp;gt;dvq = virtio_add_queue(vdev, 128, virtio_balloon_handle_output);
s-&amp;gt;svq = virtio_add_queue(vdev, 128, virtio_balloon_receive_stats);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio balloon의 3개 virtqueue&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UmL6b/dJMb99Uf2C3/suISYBEeon6aT4cjTk6cPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UmL6b/dJMb99Uf2C3/suISYBEeon6aT4cjTk6cPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UmL6b/dJMb99Uf2C3/suISYBEeon6aT4cjTk6cPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUmL6b%2FdJMb99Uf2C3%2FsuISYBEeon6aT4cjTk6cPk%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;668&quot; height=&quot;335&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtqueue는 virtio 디바이스의 중요한 구성 부분이고, 가상 머신 안의 운영 체제와 데이터 전송을 하는 데 쓴다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405254088&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
                             VirtIOHandleOutput handle_output)
{
    int i;

    for (i = 0; i &amp;lt; VIRTIO_QUEUE_MAX; i++) {
        if (vdev-&amp;gt;vq[i].vring.num == 0)
            break;
    }

    if (i == VIRTIO_QUEUE_MAX || queue_size &amp;gt; VIRTQUEUE_MAX_SIZE)
        abort();

    vdev-&amp;gt;vq[i].vring.num = queue_size;
    vdev-&amp;gt;vq[i].vring.num_default = queue_size;
    vdev-&amp;gt;vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN;
    vdev-&amp;gt;vq[i].handle_output = handle_output;
    vdev-&amp;gt;vq[i].used_elems = g_new0(VirtQueueElement, queue_size);

    return &amp;amp;vdev-&amp;gt;vq[i];
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_add_queue 함수는 virtio 프레임워크에서 virtqueue를 추가하는 데 쓰는 인터페이스다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;3개의 파라미터는 각각&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;추가할 디바이스(VirtIODevice *vdev)&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;virtqueue의 크기(int queue_size)&lt;/li&gt;
&lt;li data-pm-slice=&quot;0 0 []&quot;&gt;처리 함수(VirtIOHandleOutput handle_output)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_add_queue 함수는 VirtIODevice의 vq 배열 멤버에서 아직 사용되지 않은 queue 하나를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 virtqueue는 VirtQueue 구조체로 표시되는데, 이 VirtQueue의 멤버들에 대해 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 queue의 크기와 정렬 정보 설정도 포함된다. 마지막은 VirtQueue의 handle_output 멤버를 설정하는 건데, 그건 함수 포인터 하나로, 가상 머신이 보낸 IO 요청을 받았을 때 handle_output에 저장된 콜백 함수가 호출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405308982&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct VirtQueue
{
    VRing vring;
    VirtQueueElement *used_elems;

    /* Next head to pop */
    uint16_t last_avail_idx;
    bool last_avail_wrap_counter;

    /* Last avail_idx read from VQ. */
    uint16_t shadow_avail_idx;
    bool shadow_avail_wrap_counter;

    uint16_t used_idx;
    bool used_wrap_counter;

    /* Last used index value we have signalled on */
    uint16_t signalled_used;

    /* Last used index value we have signalled on */
    bool signalled_used_valid;

    /* Notification enabled? */
    bool notification;

    uint16_t queue_index;

    unsigned int inuse;

    uint16_t vector;
    VirtIOHandleOutput handle_output;
    VirtIODevice *vdev;
    EventNotifier guest_notifier;
    EventNotifier host_notifier;
    bool host_notifier_enabled;
    QLIST_ENTRY(VirtQueue) node;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) virtio_bus_device_plugged 와 virtio_pci_device_plugged&lt;/h3&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_bus_device_plugged 함수 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio PCI proxy 디바이스 realize부터 virtio 디바이스 realize까지의 과정을 확인해보았다. 다만 앞서 분석한 내용 중 한 부분이 빠져 있는데, 바로 virtio 디바이스가 virtio 버스에 연결되는 동작이다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 과정은 virtio_device_realize 함수 안에서 virtio_bus_device_plugged 함수를 호출해서 완성된다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_bus_device_plugged 함수의 역할은 바로 virtio 디바이스를 virtio 버스에 삽입하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405415183&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* A VirtIODevice is being plugged */
void virtio_bus_device_plugged(VirtIODevice *vdev, Error **errp)
{
    DeviceState *qdev = DEVICE(vdev);
    BusState *qbus = BUS(qdev_get_parent_bus(qdev));
    VirtioBusState *bus = VIRTIO_BUS(qbus);
    VirtioBusClass *klass = VIRTIO_BUS_GET_CLASS(bus);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
    bool has_iommu = virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM);
    bool vdev_has_iommu;
    Error *local_err = NULL;

    DPRINTF(&quot;%s: plug device.\n&quot;, qbus-&amp;gt;name);

    if (klass-&amp;gt;pre_plugged != NULL) {
        klass-&amp;gt;pre_plugged(qbus-&amp;gt;parent, &amp;amp;local_err);
        if (local_err) {
            error_propagate(errp, local_err);
            return;
        }
    }

    /* Get the features of the plugged device. */
    assert(vdc-&amp;gt;get_features != NULL);
    vdev-&amp;gt;host_features = vdc-&amp;gt;get_features(vdev, vdev-&amp;gt;host_features,
                                              &amp;amp;local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    if (klass-&amp;gt;device_plugged != NULL) {
        klass-&amp;gt;device_plugged(qbus-&amp;gt;parent, &amp;amp;local_err);
    }
    if (local_err) {
        error_propagate(errp, local_err);
        return;
    }

    vdev-&amp;gt;dma_as = &amp;amp;address_space_memory;
    if (has_iommu) {
        vdev_has_iommu = virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM);
        /*
         * Present IOMMU_PLATFORM to the driver iff iommu_plattform=on and
         * device operational. If the driver does not accept IOMMU_PLATFORM
         * we fail the device.
         */
        virtio_add_feature(&amp;amp;vdev-&amp;gt;host_features, VIRTIO_F_IOMMU_PLATFORM);
        if (klass-&amp;gt;get_dma_as) {
            vdev-&amp;gt;dma_as = klass-&amp;gt;get_dma_as(qbus-&amp;gt;parent);
            if (!vdev_has_iommu &amp;amp;&amp;amp; vdev-&amp;gt;dma_as != &amp;amp;address_space_memory) {
                error_setg(errp,
                            &quot;iommu_platform=true is not supported by the device&quot;);
                return;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_bus_device_plugged 내부 흐름&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMN3Yo/dJMcahdIEWH/jDiNN9kzjwY82ms7r4GFHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMN3Yo/dJMcahdIEWH/jDiNN9kzjwY82ms7r4GFHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMN3Yo/dJMcahdIEWH/jDiNN9kzjwY82ms7r4GFHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMN3Yo%2FdJMcahdIEWH%2FjDiNN9kzjwY82ms7r4GFHk%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;189&quot; height=&quot;508&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_bus_device_plugged 함수는 주로 VirtioBusClass 클래스 타입의 device_plugged 콜백 함수를 호출했다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405446511&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VirtioBusClass *klass = VIRTIO_BUS_GET_CLASS(bus);
&amp;hellip;&amp;hellip;
if (klass-&amp;gt;device_plugged != NULL) {
    klass-&amp;gt;device_plugged(qbus-&amp;gt;parent, &amp;amp;local_err);
}
if (local_err) {
    error_propagate(errp, local_err);
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;device_plugged 콜백 함수, 즉 klass-&amp;gt;device_plugged 함수 포인터가 가리키는 함수는 virtio_pci_bus_class_init 함수 안에서 virtio_pci_device_plugged 함수로 초기화됐다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405463941&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_bus_class_init(ObjectClass *klass, void *data)
{
    BusClass *bus_class = BUS_CLASS(klass);
    VirtioBusClass *k = VIRTIO_BUS_CLASS(klass);
    bus_class-&amp;gt;max_dev = 1;
    k-&amp;gt;notify = virtio_pci_notify;
    k-&amp;gt;save_config = virtio_pci_save_config;
    k-&amp;gt;load_config = virtio_pci_load_config;
    k-&amp;gt;save_queue = virtio_pci_save_queue;
    k-&amp;gt;load_queue = virtio_pci_load_queue;
    k-&amp;gt;save_extra_state = virtio_pci_save_extra_state;
    k-&amp;gt;load_extra_state = virtio_pci_load_extra_state;
    k-&amp;gt;has_extra_state = virtio_pci_has_extra_state;
    k-&amp;gt;query_guest_notifiers = virtio_pci_query_guest_notifiers;
    k-&amp;gt;set_guest_notifiers = virtio_pci_set_guest_notifiers;
    k-&amp;gt;set_host_notifier_mr = virtio_pci_set_host_notifier_mr;
    k-&amp;gt;vmstate_change = virtio_pci_vmstate_change;
    k-&amp;gt;pre_plugged = virtio_pci_pre_plugged;
    k-&amp;gt;device_plugged = virtio_pci_device_plugged;
    k-&amp;gt;device_unplugged = virtio_pci_device_unplugged;
    k-&amp;gt;query_nvectors = virtio_pci_query_nvectors;
    k-&amp;gt;ioeventfd_enabled = virtio_pci_ioeventfd_enabled;
    k-&amp;gt;ioeventfd_assign = virtio_pci_ioeventfd_assign;
    k-&amp;gt;get_dma_as = virtio_pci_get_dma_as;
    k-&amp;gt;iommu_enabled = virtio_pci_iommu_enabled;
    k-&amp;gt;queue_enabled = virtio_pci_queue_enabled;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtioBusClass의 device_plugged 콜백 연결&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNCxfF/dJMcabYTo1x/cFdXMURUEVnFhytgmjU6tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNCxfF/dJMcabYTo1x/cFdXMURUEVnFhytgmjU6tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNCxfF/dJMcabYTo1x/cFdXMURUEVnFhytgmjU6tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNCxfF%2FdJMcabYTo1x%2FcFdXMURUEVnFhytgmjU6tk%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;1416&quot; height=&quot;198&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781405486921&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* This is called by virtio-bus just after the device is plugged. */
static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
    VirtioBusState *bus = &amp;amp;proxy-&amp;gt;bus;
    bool legacy = virtio_pci_legacy(proxy);
    bool modern;
    bool modern_pio = proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_MODERN_PIO_NOTIFY;
    uint8_t *config;
    uint32_t size;
    VirtIODevice *vdev = virtio_bus_get_device(&amp;amp;proxy-&amp;gt;bus);

    /*
     * Virtio capabilities present without
     * VIRTIO_F_VERSION_1 confuses guests
     */
    if (!proxy-&amp;gt;ignore_backend_features &amp;amp;&amp;amp;
        !virtio_has_feature(vdev-&amp;gt;host_features, VIRTIO_F_VERSION_1)) {
        virtio_pci_disable_modern(proxy);

        if (!legacy) {
            error_setg(errp, &quot;Device doesn't support modern mode, and legacy&quot;
                              &quot; mode is disabled&quot;);
            error_append_hint(errp, &quot;Set disable-legacy to off\n&quot;);

            return;
        }
    }

    modern = virtio_pci_modern(proxy);

    config = proxy-&amp;gt;pci_dev.config;
    if (proxy-&amp;gt;class_code) {
        pci_config_set_class(config, proxy-&amp;gt;class_code);
    }

    if (legacy) {
        if (!virtio_legacy_allowed(vdev)) {
            /*
             * To avoid migration issues, we allow legacy mode when legacy
             * check is disabled in the old machine types (&amp;lt; 5.1).
             */
            if (virtio_legacy_check_disabled(vdev)) {
                warn_report(&quot;device is modern-only, but for backward &quot;
                              &quot;compatibility legacy is allowed&quot;);
            } else {
                error_setg(errp,
                            &quot;device is modern-only, use disable-legacy=on&quot;);
                return;
            }
        }
        /*
         * Legacy and transitional devices use specific subsystem IDs.
         * Note that the subsystem vendor ID (config + PCI_SUBSYSTEM_VENDOR_ID)
         * is set to PCI_SUBVENDOR_ID_REDHAT_QUMRANET by default.
         */
        pci_set_word(config + PCI_SUBSYSTEM_ID, virtio_bus_get_vdev_id(bus));
        if (proxy-&amp;gt;trans_devid) {
            pci_config_set_device_id(config, proxy-&amp;gt;trans_devid);
        }
    } else {
        /* pure virtio-1.0 */
        pci_set_word(config + PCI_VENDOR_ID,
                      PCI_VENDOR_ID_REDHAT_QUMRANET);
        pci_set_word(config + PCI_DEVICE_ID,
                      PCI_DEVICE_ID_VIRTIO_10_BASE + virtio_bus_get_vdev_id(bus));
        pci_config_set_revision(config, 1);
    }
    config[PCI_INTERRUPT_PIN] = 1;

    if (modern) {
        struct virtio_pci_cap cap = {
            .cap_len = sizeof cap,
        };
        struct virtio_pci_notify_cap notify = {
            .cap.cap_len = sizeof notify,
            .notify_off_multiplier =
                cpu_to_le32(virtio_pci_queue_mem_mult(proxy)),
        };
        struct virtio_pci_cfg_cap cfg = {
            .cap.cap_len = sizeof cfg,
            .cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,
        };
        struct virtio_pci_notify_cap notify_pio = {
            .cap.cap_len = sizeof notify,
            .notify_off_multiplier = cpu_to_le32(0x0),
        };

        struct virtio_pci_cfg_cap *cfg_mask;

        virtio_pci_modern_regions_init(proxy, vdev-&amp;gt;name);

        virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;common, &amp;amp;cap);
        virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;isr, &amp;amp;cap);
        virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;device, &amp;amp;cap);
        virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;notify, &amp;amp;notify.cap);

        if (modern_pio) {
            memory_region_init(&amp;amp;proxy-&amp;gt;io_bar, OBJECT(proxy),
                                &quot;virtio-pci-io&quot;, 0x4);

            pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_io_bar_idx,
                              PCI_BASE_ADDRESS_SPACE_IO, &amp;amp;proxy-&amp;gt;io_bar);

            virtio_pci_modern_io_region_map(proxy, &amp;amp;proxy-&amp;gt;notify_pio,
                                              &amp;amp;notify_pio.cap);
        }

        pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_mem_bar_idx,
                          PCI_BASE_ADDRESS_SPACE_MEMORY |
                          PCI_BASE_ADDRESS_MEM_PREFETCH |
                          PCI_BASE_ADDRESS_MEM_TYPE_64,
                          &amp;amp;proxy-&amp;gt;modern_bar);

        proxy-&amp;gt;config_cap = virtio_pci_add_mem_cap(proxy, &amp;amp;cfg.cap);
        cfg_mask = (void *)(proxy-&amp;gt;pci_dev.wmask + proxy-&amp;gt;config_cap);
        pci_set_byte(&amp;amp;cfg_mask-&amp;gt;cap.bar, ~0x0);
        pci_set_long((uint8_t *)&amp;amp;cfg_mask-&amp;gt;cap.offset, ~0x0);
        pci_set_long((uint8_t *)&amp;amp;cfg_mask-&amp;gt;cap.length, ~0x0);
        pci_set_long(cfg_mask-&amp;gt;pci_cfg_data, ~0x0);
    }

    if (proxy-&amp;gt;nvectors) {
        int err = msix_init_exclusive_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;nvectors,
                                            proxy-&amp;gt;msix_bar_idx, NULL);
        if (err) {
            /* Notice when a system that supports MSIx can't initialize it */
            if (err != -ENOTSUP) {
                warn_report(&quot;unable to init msix vectors to %&quot; PRIu32,
                              proxy-&amp;gt;nvectors);
            }
            proxy-&amp;gt;nvectors = 0;
        }
    }

    proxy-&amp;gt;pci_dev.config_write = virtio_write_config;
    proxy-&amp;gt;pci_dev.config_read = virtio_read_config;

    if (legacy) {
        size = VIRTIO_PCI_REGION_SIZE(&amp;amp;proxy-&amp;gt;pci_dev)
            + virtio_bus_get_vdev_config_len(bus);
        size = pow2ceil(size);

        memory_region_init_io(&amp;amp;proxy-&amp;gt;bar, OBJECT(proxy),
                                &amp;amp;virtio_pci_config_ops,
                                proxy, &quot;virtio-pci&quot;, size);

        pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;legacy_io_bar_idx,
                          PCI_BASE_ADDRESS_SPACE_IO, &amp;amp;proxy-&amp;gt;bar);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_pci_device_plugged 함수 자세한 분석&lt;/h4&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) virtio_pci_device_plugged 함수는 virtio PCI proxy 디바이스의 설정 공간 안에 vendor id와 device id를 세팅한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781405536468&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (legacy) {
    &amp;hellip;&amp;hellip;
} else {
    /* pure virtio-1.0 */
    pci_set_word(config + PCI_VENDOR_ID,
                  PCI_VENDOR_ID_REDHAT_QUMRANET);
    pci_set_word(config + PCI_DEVICE_ID,
                  PCI_DEVICE_ID_VIRTIO_10_BASE + virtio_bus_get_vdev_id(bus));
    pci_config_set_revision(config, 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vendor id를 PCI_VENDOR_ID_REDHAT_QUMRANE로 세팅한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405555098&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Red Hat / Qumranet (for QEMU) -- see pci-ids.txt */
#define PCI_VENDOR_ID_REDHAT_QUMRANET    0x1af4
#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4
#define PCI_SUBDEVICE_ID_QEMU            0x1100&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;device id를 PCI_DEVICE_ID_VIRTIO_10_BASE에 virtio 디바이스 클래스 형태의 id를 더한 걸로 세팅한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405574042&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * modern virtio-pci devices get their id assigned automatically,
 * there is no need to add #defines here.  It gets calculated as
 *
 * PCI_DEVICE_ID = PCI_DEVICE_ID_VIRTIO_10_BASE +
 *                 virtio_bus_get_vdev_id(bus)
 */
#define PCI_DEVICE_ID_VIRTIO_10_BASE     0x1040&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtual balloon 디바이스 입장에서는 VIRTIO_ID_BALLOON이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405590161&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define VIRTIO_ID_BALLOON        5 /* virtio balloon */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;vendor id / device id 계산 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDrx6e/dJMcadWEwRg/KcKjxdfTr5rjweRJ4ZEL71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDrx6e/dJMcadWEwRg/KcKjxdfTr5rjweRJ4ZEL71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDrx6e/dJMcadWEwRg/KcKjxdfTr5rjweRJ4ZEL71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDrx6e%2FdJMcadWEwRg%2FKcKjxdfTr5rjweRJ4ZEL71%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;453&quot; height=&quot;255&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) 다음으로 virtio 디바이스의 레지스터 설정 정보를 PCI capability로서 설정 공간에 써넣는다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781405618166&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (modern) {
    struct virtio_pci_cap cap = {
        .cap_len = sizeof cap,
    };
    struct virtio_pci_notify_cap notify = {
        .cap.cap_len = sizeof notify,
        .notify_off_multiplier =
            cpu_to_le32(virtio_pci_queue_mem_mult(proxy)),
    };
    struct virtio_pci_cfg_cap cfg = {
        .cap.cap_len = sizeof cfg,
        .cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,
    };
    struct virtio_pci_notify_cap notify_pio = {
        .cap.cap_len = sizeof notify,
        .notify_off_multiplier = cpu_to_le32(0x0),
    };

    struct virtio_pci_cfg_cap *cfg_mask;

    virtio_pci_modern_regions_init(proxy, vdev-&amp;gt;name);

    virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;common, &amp;amp;cap);
    virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;isr, &amp;amp;cap);
    virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;device, &amp;amp;cap);
    virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;notify, &amp;amp;notify.cap);

    if (modern_pio) {
        memory_region_init(&amp;amp;proxy-&amp;gt;io_bar, OBJECT(proxy),
                            &quot;virtio-pci-io&quot;, 0x4);

        pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_io_bar_idx,
                          PCI_BASE_ADDRESS_SPACE_IO, &amp;amp;proxy-&amp;gt;io_bar);

        virtio_pci_modern_io_region_map(proxy, &amp;amp;proxy-&amp;gt;notify_pio,
                                          &amp;amp;notify_pio.cap);
    }

    pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_mem_bar_idx,
                      PCI_BASE_ADDRESS_SPACE_MEMORY |
                      PCI_BASE_ADDRESS_MEM_PREFETCH |
                      PCI_BASE_ADDRESS_MEM_TYPE_64,
                      &amp;amp;proxy-&amp;gt;modern_bar);

    proxy-&amp;gt;config_cap = virtio_pci_add_mem_cap(proxy, &amp;amp;cfg.cap);
    cfg_mask = (void *)(proxy-&amp;gt;pci_dev.wmask + proxy-&amp;gt;config_cap);
    pci_set_byte(&amp;amp;cfg_mask-&amp;gt;cap.bar, ~0x0);
    pci_set_long((uint8_t *)&amp;amp;cfg_mask-&amp;gt;cap.offset, ~0x0);
    pci_set_long((uint8_t *)&amp;amp;cfg_mask-&amp;gt;cap.length, ~0x0);
    pci_set_long(cfg_mask-&amp;gt;pci_cfg_data, ~0x0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 코드 조각을 이해하려면 먼저 pci capability에 대해 알아둬야 한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;pci capability는 디바이스의 기능을 명시하는 데 쓴다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio는 여러 개의 MemoryRegion을 VirtIOPCIProxy 디바이스 MMIO에 대응하는 MemoryRegion의 서브 MemoryRegion(비교적 어색하다)으로 삼는데,&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 몇 개의 MemoryRegion의 정보를 capability로 삼아 virtioPCI proxy 디바이스의 설정 공간에 써넣는다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;이 capability들의 헤더 구조는 struct virtio_pci_cap 구조체를 쓴다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405651196&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* This is the PCI capability header: */
struct virtio_pci_cap {
    uint8_t cap_vndr;        /* Generic PCI field: PCI_CAP_ID_VNDR */
    uint8_t cap_next;        /* Generic PCI field: next ptr. */
    uint8_t cap_len;         /* Generic PCI field: capability length */
    uint8_t cfg_type;        /* Identifies the structure. */
    uint8_t bar;             /* Where to find it. */
    uint8_t id;              /* Multiple capabilities of the same type */
    uint8_t padding[2];      /* Pad to full dword. */
    uint32_t offset;         /* Offset within structure, in bytes. */
    uint32_t length;         /* Length of the structure, in bytes. */
};&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;0 0 []&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cap_vndr는 capability의 id를 나타내는 데 쓴다. 일부 표준 capability를 제외하고, 만약 디바이스가 직접 정의한 것(여기 virtio 디바이스처럼)이면, PCI_CAP_ID_VNDR로 세팅된다.&lt;/li&gt;
&lt;li&gt;cap_next는 PCI 설정 공간에서 다음 capability의 오프셋을 가리킨다.&lt;/li&gt;
&lt;li&gt;bar는 이 capability가 어느 bar를 사용하는지 나타낸다.&lt;/li&gt;
&lt;li&gt;offset은 이 capability가 대표하는 MemoryRegion이 virtioPCI proxy 디바이스의 bar 안에서 어디부터 시작하는지 나타낸다.&lt;/li&gt;
&lt;li&gt;length는 이 capability의 길이를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_cap 구조체 필드&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efJkBd/dJMcafGZper/fPPGjh9Cv2Nh2g1m090Ol0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efJkBd/dJMcafGZper/fPPGjh9Cv2Nh2g1m090Ol0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efJkBd/dJMcafGZper/fPPGjh9Cv2Nh2g1m090Ol0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefJkBd%2FdJMcafGZper%2FfPPGjh9Cv2Nh2g1m090Ol0%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;2038&quot; height=&quot;222&quot; data-origin-width=&quot;2038&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;PCI 설정 공간과 cap, MMIO의 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oUrDD/dJMcafGZpex/nvRmiQwl22m4i4f0yNmDw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oUrDD/dJMcafGZpex/nvRmiQwl22m4i4f0yNmDw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oUrDD/dJMcafGZpex/nvRmiQwl22m4i4f0yNmDw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoUrDD%2FdJMcafGZpex%2FnvRmiQwl22m4i4f0yNmDw1%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;1906&quot; height=&quot;785&quot; data-origin-width=&quot;1906&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;전체 데이터 구조 관계&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BwsUg/dJMcag6UHTQ/xWlHKMfViVTpWhW1foGqLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BwsUg/dJMcag6UHTQ/xWlHKMfViVTpWhW1foGqLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BwsUg/dJMcag6UHTQ/xWlHKMfViVTpWhW1foGqLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBwsUg%2FdJMcag6UHTQ%2FxWlHKMfViVTpWhW1foGqLK%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;1736&quot; height=&quot;526&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size23&quot;&gt;(심화) virtio_pci_device_plugged 상세 (modern regions / BAR / msix)&lt;/h3&gt;
&lt;h4 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size20&quot;&gt;virtio_pci_device_plugged 함수 분석 - 앞부분&lt;/h4&gt;
&lt;pre id=&quot;code_1781405769548&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* This is called by virtio-bus just after the device is plugged. */
static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
{
    VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
    VirtioBusState *bus = &amp;amp;proxy-&amp;gt;bus;
    bool legacy = virtio_pci_legacy(proxy);
    bool modern;
    bool modern_pio = proxy-&amp;gt;flags &amp;amp; VIRTIO_PCI_FLAG_MODERN_PIO_NOTIFY;
    uint8_t *config;
    uint32_t size;
    VirtIODevice *vdev = virtio_bus_get_device(&amp;amp;proxy-&amp;gt;bus);

    /*
     * Virtio capabilities present without
     * VIRTIO_F_VERSION_1 confuses guests
     */
    if (!proxy-&amp;gt;ignore_backend_features &amp;amp;&amp;amp;
            !virtio_has_feature(vdev-&amp;gt;host_features, VIRTIO_F_VERSION_1)) {
        virtio_pci_disable_modern(proxy);

        if (!legacy) {
            error_setg(errp, &quot;Device doesn't support modern mode, and legacy&quot;
                             &quot; mode is disabled&quot;);
            error_append_hint(errp, &quot;Set disable-legacy to off\n&quot;);

            return;
        }
    }

    modern = virtio_pci_modern(proxy);

    config = proxy-&amp;gt;pci_dev.config;
    if (proxy-&amp;gt;class_code) {
        pci_config_set_class(config, proxy-&amp;gt;class_code);
    }

    if (legacy) {
        if (!virtio_legacy_allowed(vdev)) {
            /*
             * To avoid migration issues, we allow legacy mode when legacy
             * check is disabled in the old machine types (&amp;lt; 5.1).
             */
            if (virtio_legacy_check_disabled(vdev)) {
                warn_report(&quot;device is modern-only, but for backward &quot;
                            &quot;compatibility legacy is allowed&quot;);
            } else {
                error_setg(errp,
                           &quot;device is modern-only, use disable-legacy=on&quot;);
                return;
            }
        }
        if (virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM)) {
            error_setg(errp, &quot;VIRTIO_F_IOMMU_PLATFORM was supported by&quot;
                       &quot; neither legacy nor transitional device&quot;);
            return;
        }
        /*
         * Legacy and transitional devices use specific subsystem IDs.
         * Note that the subsystem vendor ID (config + PCI_SUBSYSTEM_VENDOR_ID)
         * is set to PCI_SUBVENDOR_ID_REDHAT_QUMRANET by default.
         */
        pci_set_word(config + PCI_SUBSYSTEM_ID, virtio_bus_get_vdev_id(bus));
        if (proxy-&amp;gt;trans_devid) {
            pci_config_set_device_id(config, proxy-&amp;gt;trans_devid);
        } else {
            /* pure virtio-1.0 */
            pci_set_word(config + PCI_VENDOR_ID,
                         PCI_VENDOR_ID_REDHAT_QUMRANET);
            pci_set_word(config + PCI_DEVICE_ID,
                         PCI_DEVICE_ID_VIRTIO_10_BASE + virtio_bus_get_vdev_id(bus));
            pci_config_set_revision(config, 1);
        }
        config[PCI_INTERRUPT_PIN] = 1;

        if (modern) {
            struct virtio_pci_cap cap = {
                .cap_len = sizeof cap,
            };
            struct virtio_pci_notify_cap notify = {
                .cap.cap_len = sizeof notify,
                .notify_off_multiplier =
                    cpu_to_le32(virtio_pci_queue_mem_mult(proxy)),
            };
            struct virtio_pci_cfg_cap cfg = {
                .cap.cap_len = sizeof cfg,
                .cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,
            };
            struct virtio_pci_notify_cap notify_pio = {
                .cap.cap_len = sizeof notify,
                .notify_off_multiplier = cpu_to_le32(0x0),
            };

            struct virtio_pci_cfg_cap *cfg_mask;

            virtio_pci_modern_regions_init(proxy, vdev-&amp;gt;name);

            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;common, &amp;amp;cap);
            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;isr, &amp;amp;cap);
            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;device, &amp;amp;cap);
            virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;notify, &amp;amp;notify.cap);

            if (modern_pio) {
                memory_region_init(&amp;amp;proxy-&amp;gt;io_bar,
                    OBJECT(proxy),
                    &quot;virtio-pci-io&quot;, 0x4);

                pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_io_bar_idx,
                                 PCI_BASE_ADDRESS_SPACE_IO, &amp;amp;proxy-&amp;gt;io_bar);

                virtio_pci_modern_io_region_map(proxy, &amp;amp;proxy-&amp;gt;notify_pio,
                                                &amp;amp;notify_pio.cap);
            }

            pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_mem_bar_idx,
                             PCI_BASE_ADDRESS_SPACE_MEMORY |
                             PCI_BASE_ADDRESS_MEM_PREFETCH |
                             PCI_BASE_ADDRESS_MEM_TYPE_64,
                             &amp;amp;proxy-&amp;gt;modern_bar);

            proxy-&amp;gt;config_cap = virtio_pci_add_mem_cap(proxy, &amp;amp;cfg.cap);
            cfg_mask = (void *)(proxy-&amp;gt;pci_dev.wmask + proxy-&amp;gt;config_cap);
            pci_set_byte(&amp;amp;cfg_mask-&amp;gt;cap.bar, ~0x0);
            pci_set_long((uint8_t *)&amp;amp;cfg_mask-&amp;gt;cap.offset, ~0x0);
            pci_set_long((uint8_t *)&amp;amp;cfg_mask-&amp;gt;cap.length, ~0x0);
            pci_set_long(cfg_mask-&amp;gt;pci_cfg_data, ~0x0);
        }

        if (proxy-&amp;gt;nvectors) {
            int err = msix_init_exclusive_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;nvectors,
                                              proxy-&amp;gt;msix_bar_idx, NULL);

            if (err) {
                /* Notice when a system that supports MSIx can't initialize it */
                if (err != -ENOTSUP) {
                    warn_report(&quot;unable to init msix vectors to %&quot; PRIu32,
                                proxy-&amp;gt;nvectors);
                }
                proxy-&amp;gt;nvectors = 0;
            }
        }

        proxy-&amp;gt;pci_dev.config_write = virtio_write_config;
        proxy-&amp;gt;pci_dev.config_read = virtio_read_config;

        if (legacy) {
            size = VIRTIO_PCI_REGION_SIZE(&amp;amp;proxy-&amp;gt;pci_dev)
                + virtio_bus_get_vdev_config_len(bus);
            size = pow2ceil(size);

            memory_region_init_io(&amp;amp;proxy-&amp;gt;bar, OBJECT(proxy),
                                  &amp;amp;virtio_pci_config_ops,
                                  proxy, &quot;virtio-pci&quot;, size);

            pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;legacy_io_bar_idx,
                             PCI_BASE_ADDRESS_SPACE_IO, &amp;amp;proxy-&amp;gt;bar);
        }
}&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;1110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SJYJ2/dJMcaf1dvTU/8o8sLfWqLviF11jjKTb8Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SJYJ2/dJMcaf1dvTU/8o8sLfWqLviF11jjKTb8Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SJYJ2/dJMcaf1dvTU/8o8sLfWqLviF11jjKTb8Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSJYJ2%2FdJMcaf1dvTU%2F8o8sLfWqLviF11jjKTb8Bk%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;241&quot; height=&quot;739&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;1110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.1) virtio_pci_modern_regions_init 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;초기화는 5개의 MemoryRegion을 만든다. 각각 virtio-pci-common, virtio-pci-isr, virtio-pci-device, virtio-pci-notify, virtio-pci-notify-pio다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405835543&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_pci_modern_regions_init(proxy, vdev-&amp;gt;name);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781405844002&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_modern_regions_init(VirtIOPCIProxy *proxy,
                                           const char *vdev_name)
{
    static const MemoryRegionOps common_ops = {
        .read = virtio_pci_common_read,
        .write = virtio_pci_common_write,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 4,
        },
        .endianness = DEVICE_LITTLE_ENDIAN,
    };
    static const MemoryRegionOps isr_ops = {
        .read = virtio_pci_isr_read,
        .write = virtio_pci_isr_write,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 4,
        },
        .endianness = DEVICE_LITTLE_ENDIAN,
    };
    static const MemoryRegionOps device_ops = {
        .read = virtio_pci_device_read,
        .write = virtio_pci_device_write,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 4,
        },
        .endianness = DEVICE_LITTLE_ENDIAN,
    };
    static const MemoryRegionOps notify_ops = {
        .read = virtio_pci_notify_read,
        .write = virtio_pci_notify_write,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 4,
        },
        .endianness = DEVICE_LITTLE_ENDIAN,
    };
    static const MemoryRegionOps notify_pio_ops = {
        .read = virtio_pci_notify_read,
        .write = virtio_pci_notify_write_pio,
        .impl = {
            .min_access_size = 1,
            .max_access_size = 4,
        },
        .endianness = DEVICE_LITTLE_ENDIAN,
    };
    g_autoptr(GString) name = g_string_new(NULL);

    g_string_printf(name, &quot;virtio-pci-common-%s&quot;, vdev_name);
    memory_region_init_io(&amp;amp;proxy-&amp;gt;common.mr, OBJECT(proxy),
                          &amp;amp;common_ops,
                          proxy,
                          name-&amp;gt;str,
                          proxy-&amp;gt;common.size);

    g_string_printf(name, &quot;virtio-pci-isr-%s&quot;, vdev_name);
    memory_region_init_io(&amp;amp;proxy-&amp;gt;isr.mr, OBJECT(proxy),
                          &amp;amp;isr_ops,
                          proxy,
                          name-&amp;gt;str,
                          proxy-&amp;gt;isr.size);

    g_string_printf(name, &quot;virtio-pci-device-%s&quot;, vdev_name);
    memory_region_init_io(&amp;amp;proxy-&amp;gt;device.mr, OBJECT(proxy),
                          &amp;amp;device_ops,
                          proxy,
                          name-&amp;gt;str,
                          proxy-&amp;gt;device.size);

    g_string_printf(name, &quot;virtio-pci-notify-%s&quot;, vdev_name);
    memory_region_init_io(&amp;amp;proxy-&amp;gt;notify.mr, OBJECT(proxy),
                          &amp;amp;notify_ops,
                          proxy,
                          name-&amp;gt;str,
                          proxy-&amp;gt;notify.size);

    g_string_printf(name, &quot;virtio-pci-notify-pio-%s&quot;, vdev_name);
    memory_region_init_io(&amp;amp;proxy-&amp;gt;notify_pio.mr, OBJECT(proxy),
                          &amp;amp;notify_pio_ops,
                          proxy,
                          name-&amp;gt;str,
                          proxy-&amp;gt;notify_pio.size);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;5개 MemoryRegion 구조&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X60KO/dJMcaf1dvTY/vOMffDeO0xerNQhumM3Ra0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X60KO/dJMcaf1dvTY/vOMffDeO0xerNQhumM3Ra0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X60KO/dJMcaf1dvTY/vOMffDeO0xerNQhumM3Ra0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX60KO%2FdJMcaf1dvTY%2FvOMffDeO0xerNQhumM3Ra0%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;1656&quot; height=&quot;398&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781405869172&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct VirtIOPCIProxy {
    PCIDevice pci_dev;
    MemoryRegion bar;
    union {
        struct {
            VirtIOPCIRegion common;
            VirtIOPCIRegion isr;
            VirtIOPCIRegion device;
            VirtIOPCIRegion notify;
            VirtIOPCIRegion notify_pio;
        };
        VirtIOPCIRegion regs[5];
    };
    MemoryRegion modern_bar;
    MemoryRegion io_bar;
    uint32_t legacy_io_bar_idx;
    uint32_t msix_bar_idx;
    uint32_t modern_io_bar_idx;
    uint32_t modern_mem_bar_idx;
    int config_cap;
    uint32_t flags;
    bool disable_modern;
    bool ignore_backend_features;
    OnOffAuto disable_legacy;
    /* Transitional device id */
    uint16_t trans_devid;
    uint32_t class_code;
    uint32_t nvectors;
    uint32_t dfselect;
    uint32_t gfselect;
    uint32_t guest_features[2];
    VirtIOPCIQueue vqs[VIRTIO_QUEUE_MAX];

    VirtIOIRQFD *vector_irqfd;
    int nvqs_with_notifiers;
    VirtioBusState bus;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio-pci-common, virtio-pci-isr, virtio-pci-device, virtio-pci-notify, virtio-pci-notify-pio 이 5개의 MemoryRegion 관련 정보는 VirtIOPCIProxy 구조체 안의 몇 개의 VirtIOPCIRegion 멤버에 저장한다.&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.2) virtio_pci_device_plugged는 이어서 virtio_pci_modern_mem_region_map 함수를 호출한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781405900545&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;common, &amp;amp;cap);
virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;isr, &amp;amp;cap);
virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;device, &amp;amp;cap);
virtio_pci_modern_mem_region_map(proxy, &amp;amp;proxy-&amp;gt;notify, &amp;amp;notify.cap);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781405909028&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_modern_mem_region_map(VirtIOPCIProxy *proxy,
                                             VirtIOPCIRegion *region,
                                             struct virtio_pci_cap *cap)
{
    virtio_pci_modern_region_map(proxy, region, cap,
                                 &amp;amp;proxy-&amp;gt;modern_bar, proxy-&amp;gt;modern_mem_bar_idx);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_modern_mem_region_map 함수는 virtio_pci_modern_region_map 함수만 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781405927532&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void virtio_pci_modern_region_map(VirtIOPCIProxy *proxy,
                                         VirtIOPCIRegion *region,
                                         struct virtio_pci_cap *cap,
                                         MemoryRegion *mr,
                                         uint8_t bar)
{
    memory_region_add_subregion(mr, region-&amp;gt;offset, &amp;amp;region-&amp;gt;mr);

    cap-&amp;gt;cfg_type = region-&amp;gt;type;
    cap-&amp;gt;bar = bar;
    cap-&amp;gt;offset = cpu_to_le32(region-&amp;gt;offset);
    cap-&amp;gt;length = cpu_to_le32(region-&amp;gt;size);
    virtio_pci_add_mem_cap(proxy, cap);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_modern_region_map 동작&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QeFWu/dJMcah5SYB0/zjkfCuAiaKbPvslBM1EP81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QeFWu/dJMcah5SYB0/zjkfCuAiaKbPvslBM1EP81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QeFWu/dJMcah5SYB0/zjkfCuAiaKbPvslBM1EP81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQeFWu%2FdJMcah5SYB0%2FzjkfCuAiaKbPvslBM1EP81%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;503&quot; height=&quot;265&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio_pci_modern_region_map 함수는 두 가지 기능을 완성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;VirtIOPCIRegion의 mr 멤버 virtio-pci-***를 자식 MemoryRegion으로 만들어서 VirtIOProxy의 modern_bar 멤버에 추가한다. 그래서 가상머신 내부에서 virtio PCI proxy의 MMIO에 쓰기를 할 때 이 몇 개의 virtio 장치의 MemoryRegion의 콜백 함수로 떨어지게 된다.&lt;/li&gt;
&lt;li&gt;virtio_pci_add_mem_cap 함수를 호출해서 이 레지스터 정보들을 virtio PCI proxy 장치의 pci capability에 추가한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781405970844&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static int virtio_pci_add_mem_cap(VirtIOPCIProxy *proxy,
                                   struct virtio_pci_cap *cap)
{
    PCIDevice *dev = &amp;amp;proxy-&amp;gt;pci_dev;
    int offset;

    offset = pci_add_capability(dev, PCI_CAP_ID_VNDR, 0,
                                cap-&amp;gt;cap_len, &amp;amp;error_abort);

    assert(cap-&amp;gt;cap_len &amp;gt;= sizeof *cap);
    memcpy(dev-&amp;gt;config + offset + PCI_CAP_FLAGS, &amp;amp;cap-&amp;gt;cap_len,
           cap-&amp;gt;cap_len - PCI_CAP_FLAGS);

    return offset;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.3) virtio_pci_device_plugged 함수는 이어서 pci_register_bar 함수를 호출한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;VirtIOPCIProxy의 modern_bar라는 MemoryRegion을 시스템에 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781406003792&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pci_register_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;modern_mem_bar_idx,
                 PCI_BASE_ADDRESS_SPACE_MEMORY |
                 PCI_BASE_ADDRESS_MEM_PREFETCH |
                 PCI_BASE_ADDRESS_MEM_TYPE_64,
                 &amp;amp;proxy-&amp;gt;modern_bar);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781406012455&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void pci_register_bar(PCIDevice *pci_dev, int region_num,
                      uint8_t type, MemoryRegion *memory)
{
    PCIIORegion *r;
    uint32_t addr; /* offset in pci config space */
    uint64_t wmask;
    pcibus_t size = memory_region_size(memory);
    uint8_t hdr_type;

    assert(!pci_is_vf(pci_dev)); /* VFs must use pcie_sriov_vf_register_bar */
    assert(region_num &amp;gt;= 0);
    assert(region_num &amp;lt; PCI_NUM_REGIONS);
    assert(is_power_of_2(size));

    /* A PCI bridge device (with Type 1 header) may only have at most 2 BARs */
    hdr_type =
        pci_dev-&amp;gt;config[PCI_HEADER_TYPE] &amp;amp; ~PCI_HEADER_TYPE_MULTI_FUNCTION;
    assert(hdr_type != PCI_HEADER_TYPE_BRIDGE || region_num &amp;lt; 2);

    r = &amp;amp;pci_dev-&amp;gt;io_regions[region_num];
    r-&amp;gt;addr = PCI_BAR_UNMAPPED;
    r-&amp;gt;size = size;
    r-&amp;gt;type = type;
    r-&amp;gt;memory = memory;
    r-&amp;gt;address_space = type &amp;amp; PCI_BASE_ADDRESS_SPACE_IO
                        ? pci_get_bus(pci_dev)-&amp;gt;address_space_io
                        : pci_get_bus(pci_dev)-&amp;gt;address_space_mem;

    wmask = ~(size - 1);
    if (region_num == PCI_ROM_SLOT) {
        /* ROM enable bit is writable */
        wmask |= PCI_ROM_ADDRESS_ENABLE;
    }

    addr = pci_bar(pci_dev, region_num);
    pci_set_long(pci_dev-&amp;gt;config + addr, type);

    if (!(r-&amp;gt;type &amp;amp; PCI_BASE_ADDRESS_SPACE_IO) &amp;amp;&amp;amp;
        r-&amp;gt;type &amp;amp; PCI_BASE_ADDRESS_MEM_TYPE_64) {
        pci_set_quad(pci_dev-&amp;gt;wmask + addr, wmask);
        pci_set_quad(pci_dev-&amp;gt;cmask + addr, ~0ULL);
    } else {
        pci_set_long(pci_dev-&amp;gt;wmask + addr, wmask &amp;amp; 0xffffffff);
        pci_set_long(pci_dev-&amp;gt;cmask + addr, 0xffffffff);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 이어서 msix_init_exclusive_bar 함수를 호출해서 msi 인터럽트에 관련된 데이터를 등록한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781406059908&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (proxy-&amp;gt;nvectors) {
    int err = msix_init_exclusive_bar(&amp;amp;proxy-&amp;gt;pci_dev, proxy-&amp;gt;nvectors,
                                      proxy-&amp;gt;msix_bar_idx, NULL);

    if (err) {
        /* Notice when a system that supports MSIx can't initialize it */
        if (err != -ENOTSUP) {
            warn_report(&quot;unable to init msix vectors to %&quot; PRIu32,
                        proxy-&amp;gt;nvectors);
        }
        proxy-&amp;gt;nvectors = 0;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781406070236&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int msix_init_exclusive_bar(PCIDevice *dev, unsigned short nentries,
                            uint8_t bar_nr, Error **errp)
{
    int ret;
    char *name;
    uint32_t bar_size = 4096;
    uint32_t bar_pba_offset = bar_size / 2;
    uint32_t bar_pba_size = QEMU_ALIGN_UP(nentries, 64) / 8;

    /*
     * Migration compatibility dictates that this remains a 4k
     * BAR with the vector table in the lower half and PBA in
     * the upper half for nentries which is lower or equal to 128.
     * No need to care about using more than 65 entries for legacy
     * machine types who has at most 64 queues.
     */
    if (nentries * PCI_MSIX_ENTRY_SIZE &amp;gt; bar_pba_offset) {
        bar_pba_offset = nentries * PCI_MSIX_ENTRY_SIZE;
    }

    if (bar_pba_offset + bar_pba_size &amp;gt; 4096) {
        bar_size = bar_pba_offset + bar_pba_size;
    }

    bar_size = pow2ceil(bar_size);

    name = g_strdup_printf(&quot;%s-msix&quot;, dev-&amp;gt;name);
    memory_region_init(&amp;amp;dev-&amp;gt;msix_exclusive_bar, OBJECT(dev), name, bar_size);

    ret = msix_init(dev, nentries, &amp;amp;dev-&amp;gt;msix_exclusive_bar, bar_nr,
                    0, &amp;amp;dev-&amp;gt;msix_exclusive_bar,
                    bar_nr, bar_pba_offset,
                    0, errp);
    if (ret) {
        return ret;
    }

    pci_register_bar(dev, bar_nr, PCI_BASE_ADDRESS_SPACE_MEMORY,
                     &amp;amp;dev-&amp;gt;msix_exclusive_bar);

    return 0;
}&lt;/code&gt;&lt;/pre&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; 4) virtio_pci_device_plugged 함수는 또한 VirtPCIProxy 장치 PCI 구성 공간의 읽기/쓰기 함수를 각각 virtio_write_config와 virtio_read_config로 설정한다&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781406099906&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;proxy-&amp;gt;pci_dev.config_write = virtio_write_config;
proxy-&amp;gt;pci_dev.config_read = virtio_read_config;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtio_pci_device_plugged 함수는 일련의 함수 호출을 거쳐서, QEMU 측에서 virtio balloon 장치를 준비한다. 다른 장치도 이와 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio PCI proxy 장치와 virtio 장치의 관련 타입 상속 관계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm6Zk5/dJMcaf1dvUt/gPNtoOZIQ4ZmYgD3FkiuF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm6Zk5/dJMcaf1dvUt/gPNtoOZIQ4ZmYgD3FkiuF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm6Zk5/dJMcaf1dvUt/gPNtoOZIQ4ZmYgD3FkiuF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm6Zk5%2FdJMcaf1dvUt%2FgPNtoOZIQ4ZmYgD3FkiuF0%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;364&quot; height=&quot;260&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size16&quot;&gt;virtio 장치 초기화 과정에서 관련되는 함수와 그것이 대응하는 타입&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J0XXC/dJMcagTpwg8/dwBRTqHGrjmxdMTHsEo9hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J0XXC/dJMcagTpwg8/dwBRTqHGrjmxdMTHsEo9hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J0XXC/dJMcagTpwg8/dwBRTqHGrjmxdMTHsEo9hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ0XXC%2FdJMcagTpwg8%2FdwBRTqHGrjmxdMTHsEo9hK%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;1226&quot; height=&quot;886&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>System Programming/Hypervisor</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/714</guid>
      <comments>https://1000sj.tistory.com/714#entry714comment</comments>
      <pubDate>Thu, 5 Mar 2026 06:45:28 +0900</pubDate>
    </item>
    <item>
      <title>LLVM Sanitizer 작성하는 법</title>
      <link>https://1000sj.tistory.com/683</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 컴파일 파이프라인&lt;/h3&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 프론트엔드 호출 (소스 &amp;rarr; AST &amp;rarr; IR)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 패스 스케줄링 (어떤 패스를 어떤 순서로 실행할지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 런타임 라이브러리 링킹 (compiler-rt 연결)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3xcbU/dJMcadAO9II/WLcs2z4WRtTskeJLBAiFUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3xcbU/dJMcadAO9II/WLcs2z4WRtTskeJLBAiFUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3xcbU/dJMcadAO9II/WLcs2z4WRtTskeJLBAiFUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3xcbU%2FdJMcadAO9II%2FWLcs2z4WRtTskeJLBAiFUk%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;1280&quot; height=&quot;539&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;Clang Frontend&lt;/b&gt;: 소스코드 &amp;rarr; AST &amp;rarr; LLVM IR 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LLVM&lt;/b&gt;: IR에 패스 적용 후 백엔드에서 기계어 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Compiler-rt&lt;/b&gt;: 런타임 라이브러리 링킹&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Sanitizer pass는 항상 마지막&lt;/b&gt;에 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 최적화 패스가 sanitizer가 삽입한 코드를 변경하거나 제거할 수 있기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CodeGen 타입 변환&lt;/h3&gt;
&lt;pre id=&quot;code_1770172228195&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int x = 42;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C/C++ 타입이 LLVM 타입으로 어떻게 변환되는지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVBL57/dJMcadAO9LI/d8rskBh0sf5LMN7GAJzX70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVBL57/dJMcadAO9LI/d8rskBh0sf5LMN7GAJzX70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVBL57/dJMcadAO9LI/d8rskBh0sf5LMN7GAJzX70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVBL57%2FdJMcadAO9LI%2Fd8rskBh0sf5LMN7GAJzX70%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;515&quot; height=&quot;511&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 42.4418%; height: 195px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;C 타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;LLVM 타입&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;크기&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;int&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i32&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;32비트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;char&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i8&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;8비트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;bool&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i8&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;8비트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;long&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i64&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;64비트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;int*&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;i32*&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;포인터&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;Sanitizer가 타입 정보를 활용해서 메모리 접근을 검사할 수 있다. (예를 들어 &quot;이 포인터는 i32를 가리켜야 하는데 i8처럼 접근하고 있다&quot; 같은 것을 탐지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pass Manager&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clang CodeGen이 패스 순서 결정한다. pass manager가 IR을 받아서 패스들을 순서대로 실제 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnayXD/dJMcaaRIQ3j/pVOCTARWmkxK7Qo5XuMcGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnayXD/dJMcaaRIQ3j/pVOCTARWmkxK7Qo5XuMcGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnayXD/dJMcaaRIQ3j/pVOCTARWmkxK7Qo5XuMcGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnayXD%2FdJMcaaRIQ3j%2FpVOCTARWmkxK7Qo5XuMcGK%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;671&quot; height=&quot;463&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&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;0. 원본 코드&lt;/p&gt;
&lt;pre id=&quot;code_1770172519690&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void foo() { bar(); }
void bar() { /* ... */ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Function Inlining 후&lt;/p&gt;
&lt;pre id=&quot;code_1770172527572&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void foo() { /* bar의 내용이 여기에 */ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Loop Unrolling 후&lt;/p&gt;
&lt;pre id=&quot;code_1770172547204&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// (루프가 있다면 펼쳐짐)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Sanitizer Pass 후&lt;/p&gt;
&lt;pre id=&quot;code_1770172565835&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void foo() {
    __sanitizer_check();  // &amp;larr; 삽입됨
    /* bar의 내용 */
    __sanitizer_check();  // &amp;larr; 삽입됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Sanitizer가 먼저 실행되면 Inlining이 sanitizer 체크 코드를 이상하게 변형시키거나, 최적화가 체크 코드를 &quot;불필요하다&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sanitizer 계측 + 런타임 링킹&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vbpgG/dJMcai3c9TU/laEarYZnsT2zLcjVDGkSjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vbpgG/dJMcai3c9TU/laEarYZnsT2zLcjVDGkSjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vbpgG/dJMcai3c9TU/laEarYZnsT2zLcjVDGkSjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvbpgG%2FdJMcai3c9TU%2FlaEarYZnsT2zLcjVDGkSjk%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;1280&quot; height=&quot;571&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계: Pass가 함수 호출 삽입&lt;/p&gt;
&lt;pre id=&quot;code_1770172870813&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Before]                      [After]
┌────────────────┐           ┌────────────────┐
│ Function       │           │ Function       │
│ BasicBlock     │           │ BasicBlock     │
│   Inst         │  ──────▶  │   call SanFunc │ &amp;larr; 새로 삽입!
│   Inst         │           │   Inst         │
│   call Malloc  │           │   Inst         │
└────────────────┘           │   call Malloc  │
                             └────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계: 링킹 시 함수 연결&lt;/p&gt;
&lt;pre id=&quot;code_1770172879536&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────┐              ┌─────────────────┐
│  MyFile.o   │              │   Compiler-RT   │
├─────────────┤              ├─────────────────┤
│ SanFunc ────┼──── 링킹 ────┼&amp;rarr; SanFunc 구현체 │
│ Malloc  ────┼── 가로채기 ──┼&amp;rarr; Interceptor_   │
│             │              │    Malloc       │
└─────────────┘              └─────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 가로채기(Interception)는 링커의 심볼 해석 순서를 이용한 트릭이다. (compiler-rt &amp;rarr; libc)&lt;/p&gt;
&lt;pre id=&quot;code_1770172908809&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 원래 프로그램
ptr = malloc(100);

// Sanitizer가 가로챈 후 실제 실행되는 것
ptr = interceptor_malloc(100);  // compiler-rt 함수

// interceptor_malloc 내부
void* interceptor_malloc(size_t size) {
    __sanitizer_before_malloc(size);  // 검사
    void* ptr = real_malloc(size);     // 진짜 malloc 호출
    __sanitizer_after_malloc(ptr);     // 추적
    return ptr;
}&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 Sanitizer를 만들려면 수정할 곳은&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;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;&lt;b&gt;컴포넌트&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;위치&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Pass&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;llvm/lib/Transforms/&lt;/td&gt;
&lt;td&gt;IR에 계측 코드 삽입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;드라이버&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;clang/lib/Driver/&lt;/td&gt;
&lt;td&gt;-fsanitize=xxx 옵션 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;CodeGen&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;clang/lib/CodeGen/&lt;/td&gt;
&lt;td&gt;패스 등록, 순서 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Runtime&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;compiler-rt/lib/&lt;/td&gt;
&lt;td&gt;실제 검사 로직, 함수 가로채기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://faculty.sist.shanghaitech.edu.cn/faculty/songfu/course/spring2018/CS131/llvm.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://faculty.sist.shanghaitech.edu.cn/faculty/songfu/course/spring2018/CS131/llvm.pdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cs.cornell.edu/~asampson/blog/llvm.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.cs.cornell.edu/~asampson/blog/llvm.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://eli.thegreenplace.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://eli.thegreenplace.net/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://compilers.iecc.com/crenshaw/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://compilers.iecc.com/crenshaw/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.trailofbits.com/2019/06/25/creating-an-llvm-sanitizer-from-hopes-and-dreams/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.trailofbits.com/2019/06/25/creating-an-llvm-sanitizer-from-hopes-and-dreams/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.trailofbits.com/2024/05/16/understanding-addresssanitizer-better-memory-safety-for-your-code/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.trailofbits.com/2024/05/16/understanding-addresssanitizer-better-memory-safety-for-your-code/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Security/Fuzzing</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/683</guid>
      <comments>https://1000sj.tistory.com/683#entry683comment</comments>
      <pubDate>Wed, 4 Feb 2026 11:51:25 +0900</pubDate>
    </item>
    <item>
      <title>sanitizer interceptor의 구현 원리</title>
      <link>https://1000sj.tistory.com/665</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;c/c++ 개발자들은 버퍼 오버플로우, 댕글링 포인터 등의 메모리 오류, data race, dead lock 등의 멀티 스레드 오류를 자주 접하게 된다. 이런 버그들은 바로 크래시가 나지 않고 나중에 엉뚱한 곳에서 터지기 때문에 찾기가 매우 어려워 이런 문제를 빠르게 찾아내는 것은 항상 골칫거리였다. google이 opensource로 공개한 sanitizer 동적 분석 도구는 c/c++ 개발자들이 효율적으로 문제를 찾아내어 개발 효율성을 높이는 데 도움을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sanitizer란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sanitizer는 google이 오픈소스로 공개한 동적 코드 분석 도구로 Clang 3.1과 GCC 4.8 부터 Clang과 GCC에 통합되었다. 프로그래머가 런타임에 프로그램의 메모리 오류와 멀티스레드 오류를 빠르고 정확하게 찾아낼 수 있도록 도와준다.&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;Sanitizer 도구 모음에는 다음이 포함된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AddressSanitizer (ASan): 버퍼 오버플로우, 해제된 메모리 접근, 널포인터 역참조 등의 메모리 오류 감지&lt;/li&gt;
&lt;li&gt;LeakSanitizer (LSan): 메모리 누수 탐지&lt;/li&gt;
&lt;li&gt;ThreadSanitizer (TSan): 멀티 스레드 data race, deadlock 탐지&lt;/li&gt;
&lt;li&gt;UndefinedBehaviorSanitizer (UBSan): 정의되지 않은 동작 탐지&lt;/li&gt;
&lt;li&gt;MemorySanitizer (MSan): 초기화되지 않은 메모리 접근 탐지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 구현 관점에서 모든 sanitizer는 두단계로 동작한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴파일 타임 계측&lt;/li&gt;
&lt;li&gt;런타임 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계: 컴파일 타임 계측&lt;/p&gt;
&lt;pre id=&quot;code_1768784965649&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 원래 코드
arr[i] = 10;

// Sanitizer가 변환한 코드 (개념적)
if (is_memory_valid(&amp;amp;arr[i])) {
    arr[i] = 10;
} else {
    report_error(&quot;잘못된 메모리 접근!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계: 런타임 라이브러리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;malloc, free 같은 함수를 가로채서 자체 버전으로 대체&lt;/li&gt;
&lt;li&gt;할당된 메모리 주변에 red zone 설정&lt;/li&gt;
&lt;li&gt;해제된 메모리를 바로 재사용하지 않고 격리 구역에 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASan을 예로들면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASan은 컴파일 시 모든 메모리 읽기/쓰기 구문 앞에 코드를 삽입하여, 각 메모리 접근에 해당하는 shadow memory (일반 메모리 상태를 기록하기 위한 추가 메모리) 의 상태를 확인하여 해당 메모리 접근이 합법적인지 검사한다. 또한 스택 변수와 전역 변수 주변에 red zone으로 추가 메모리를 할당하여 메모리 버퍼 오버플로우를 탐지한다.&lt;/li&gt;
&lt;li&gt;ASan 런타임 라이브러리는 malloc/free, operator new/delete 등의 메모리 할당 함수 구현을 대체한다. 이로써 어플리케이션의 모든 메모리 할당은 ASan 이 구현한 메모리 할당자가 담당하게 된다. ASan 메모리 할당자는 할당된 힙 메모리 주변에 추가 메모리를 할당하여 힙 메모리 오버플로우를 탐지하고 해제된 메모리를 격리 구역에 우선 배치하여 heap-use-after-free와 같은 힙 메모리 오류를 탐지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 ASan 라이브러리는 malloc, free, operator new/delete, memcpy, memmove, strcpy, strcat, pthread_create 등 매우 많은 라이브러리 함수의 구현을 대체한다.&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;사용법은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1768785148732&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ASan 사용 (메모리 오류 검사)
g++ -fsanitize=address -g mycode.cpp -o mycode
./mycode

# TSan 사용 (데이터 경쟁 검사)
g++ -fsanitize=thread -g mycode.cpp -o mycode
./mycode&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 sanitizer는 어떻게 malloc/free와 같은 함수 구현을 대체할 수 있을까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 sanitizer의 interceptor 개념이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Symbol Interposition&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interceptor는 sanitizier가 malloc, free, memcpy 같은 표준 라이브러리 함수를 가로채는 개념이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 프로그램이 malloc() 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) 원래: libc의 malloc 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Sanitizer 적용 시: ASan의 malloc 실행 (메모리 상태 추적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함수를 가로채서 sanitizer가 메모리 할당/해제를 추적할 수 있게 된다.&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;그럼 어떻게 어플리케이션에서 libc의 malloc 구현을 내가 구현한 버전으로 대체할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 Sanitizer가 하는 일의 핵심이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 간단한 방법은 어플리케이션에서 동일한 이름의 malloc 함수를 정의하는 것이다.&lt;/li&gt;
&lt;li&gt;또 다른 방법은 우리의 malloc 함수를 libmymalloc.so 에 구현한 다음, 어플리케이션을 실행하기 전에 환경 변수 LD_PRELOAD=/path/to/libmymalloc.so 를 설정하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 두 방법이 동작하는 이유는 symbol interposition 때문이다. 동적 링커가 심볼(함수 이름)을 찾는 순서가 정해져 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ELF specification 5장 &quot;Program Loading and Dynamic Linking&quot; 에서 다음과 같이 언급한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;심볼 참조를 해석할 때 동적 링커는 너비 우선 탐색(breadth-first search) 방식으로 심볼 테이블을 검사합니다.&lt;br /&gt;즉, 먼저 실행 파일 전체의 심볼 테이블을 확인하고, 그 다음 DT_NEEDED 항목들의 심볼 테이블을 (순서대로) 확인하며,&lt;br /&gt;그 다음 2단계 DT_NEEDED 항목들을 확인하는 식으로 진행됩니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;1순위: 실행 파일 자체 &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;2순위: 직접 링크된 .so 파일들 (순서대로) &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;3순위: 그 .so 파일들이 의존하는 .so 파일들&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 링커는 심볼 참조를 바인딩할 때 너비 우선 탐색 순서로 심볼을 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 하나의 심볼이 여러 컴포넌트(executable, shared object) 에 정의되어 있다면 동적 링커는 가장 먼저 발견한 정의를 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 다음과 같은 의존구조일때 심볼 탐색 순서는 test-symbind &amp;rarr; libW.so &amp;rarr; libX.so &amp;rarr; libc.so.6 &amp;rarr; libw.so &amp;rarr; libx.so 이다.&lt;/p&gt;
&lt;pre id=&quot;code_1768786482219&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test-symbind
├── libW.so
│   ├── libw.so
│   └── libc.so.6
├── libX.so
│   ├── libx.so
│   └── libc.so.6
└── libc.so.6&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1768786105107&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.c
extern int W(), X();

int main() { return (W() + X()); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768786112183&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// W.c
extern int b();

int a() { return (1); }
int W() { return (a() - b()); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768786119865&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// w.c
int b() { return (2); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768786127018&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// X.c
extern int b();

int a() { return (3); }
int X() { return (a() - b()); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768786134340&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// x.c
int b() { return (4); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768786143560&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc -o libw.so -shared w.c
$ gcc -o libW.so -shared W.c -L. -lw -Wl,-rpath=.
$ gcc -o libx.so -shared x.c
$ gcc -o libX.so -shared X.c -L. -lx -Wl,-rpath=.
$ gcc -o test-symbind main.c -L. -lW -lX -Wl,-rpath=.&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1768786157286&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                    test-symbind
                    /     |     \
                   &amp;darr;      &amp;darr;      &amp;darr;
              libW.so   libX.so   libc.so.6
              /    \    /    \
             &amp;darr;      &amp;darr;  &amp;darr;      &amp;darr;
         libw.so  libc.so.6  libx.so  libc.so.6&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test-symbind 실행 파일은 libW.so, libX.so, libc.so.6에 의존&lt;/li&gt;
&lt;li&gt;libW.so 는 libw.so와 libc.so.6에 의존&lt;/li&gt;
&lt;li&gt;libX.so는 libc.so.6와 libx.so에 의존&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 a() 는 두군데 정의되어있다. (W.c, X.c) 하지만 탐색 순서에서 libW.so가 libX.so 보다 먼저 나오기 때문에 W.c 의 a()가 호출된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sanitizer에서는 -fsanitize=address 옵션이 libasan.so를 의존 라이브러리 목록의 맨 앞에 배치하기 때문에 libc.so 의 malloc 보다 libasan.so 의 malloc 이 먼저 발견되어 sanitizer 버전이 선택된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Symbol Interposition 실제 동작 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명한대로, 심볼 탐색 순서는 test-symbind &amp;rarr; libW.so &amp;rarr; libX.so &amp;rarr; libc.so.6 &amp;rarr; libw.so &amp;rarr; libx.so 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 링커는 LD_DEBUG 환경 변수를 통해 디버그 정보를 출력할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LD_DEBUG=&quot;symbols:bindings&quot;를 설정하여 test-symbind의 symbol binding 과정을 확인해보면&lt;/p&gt;
&lt;pre id=&quot;code_1768793195014&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ LD_DEBUG=&quot;symbols:bindings&quot; ./test-symbind
   1884890:        symbol=a;  lookup in file=./test-symbind [0]
   1884890:        symbol=a;  lookup in file=./libW.so [0]
   1884890:        binding file ./libW.so [0] to ./libW.so [0]: normal symbol `a'
   1884890:        symbol=b;  lookup in file=./test-symbind [0]
   1884890:        symbol=b;  lookup in file=./libW.so [0]
   1884890:        symbol=b;  lookup in file=./libX.so [0]
   1884890:        symbol=b;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
   1884890:        symbol=b;  lookup in file=./libw.so [0]
   1884890:        binding file ./libW.so [0] to ./libw.so [0]: normal symbol `b'
   1884890:        symbol=a;  lookup in file=./test-symbind [0]
   1884890:        symbol=a;  lookup in file=./libW.so [0]
   1884890:        binding file ./libX.so [0] to ./libW.so [0]: normal symbol `a'
   1884890:        symbol=b;  lookup in file=./test-symbind [0]
   1884890:        symbol=b;  lookup in file=./libW.so [0]
   1884890:        symbol=b;  lookup in file=./libX.so [0]
   1884890:        symbol=b;  lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
   1884890:        symbol=b;  lookup in file=./libw.so [0]
   1884890:        binding file ./libX.so [0] to ./libw.so [0]: normal symbol `b'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 a는 libW.so와 libX.so 둘 다 정의되어있지만 최종적으로 a에 대한 모든 참조는 libW.so 의 함수 a 구현에 바인딩된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 b는 libw.so와 libx.so 둘 다에 정의되어있지만 libw.so가 먼저이기 때문에 최종적으로 libw.so 의 함수 b 구현에 바인딩된다.&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;이제 앞서 언급한 두가지 malloc 대체 방식이 왜 동작하는지 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ASan 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 GCC와 Clang에서 ASan을 활성화하여 컴파일해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1768793595772&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// test.cpp
#include &amp;lt;iostream&amp;gt;
int main() {
    std::cout &amp;lt;&amp;lt; &quot;Hello AddressSanitizer!\n&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;GCC 에서의 동작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gcc는 기본적으로 런타임 라이브러리를 동적 링크하므로 다음 명령어로 test-gcc-asan 이 의존하는 동적 라이브러리를 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1768793607883&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ g++ -fsanitize=address test.cpp -o test-gcc-asan
$ objdump -p test-gcc-asan | grep NEEDED
  NEEDED               libasan.so.5
  NEEDED               libstdc++.so.6
  NEEDED               libm.so.6
  NEEDED               libgcc_s.so.1
  NEEDED               libc.so.6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test-gcc-asan 이 의존하는 동적 라이브러리 중 libasan.so 의 순서가 libc.so.6 보다 앞선다는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크 시 -fsanitize=address 옵션은 libasan.so 를 프로그램의 첫번째 의존 라이브러리로 만든다.&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;LD_DEBUG=&quot;bindings&quot; 환경 변수를 설정하여 test-gcc-asan의 symbol binding 과정을 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1768793620738&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ LD_DEBUG=&quot;bindings&quot; ./test-gcc-asan
   3309213:        binding file /lib/x86_64-linux-gnu/libc.so.6 [0] to /usr/lib/x86_64-linux-gnu/libasan.so.5 [0]: normal symbol `malloc' [GLIBC_2.2.5]
   3309213:        binding file /lib64/ld-linux-x86-64.so.2 [0] to /usr/lib/x86_64-linux-gnu/libasan.so.5 [0]: normal symbol `malloc' [GLIBC_2.2.5]
   3309213:        binding file /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0] to /usr/lib/x86_64-linux-gnu/libasan.so.5 [0]: normal symbol `malloc' [GLIBC_2.2.5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 링커가 libc.so.6, ld-linux-x86-64.so, libstdc++.so의 malloc 참조를 모두 libasan.so 의 malloc 구현에 바인딩한 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Clang 의 동작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clang은 기본적으로 ASan 런타임 라이브러리를 정적 링크하므로 test-clang-asan 이 의존하는 동적 라이브러리는 확인하지 않고 바로 symbol binding 과정을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1768794033091&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ clang++ -fsanitize=address test.cpp -o test-clang-asan
$ LD_DEBUG=&quot;bindings&quot; ./test-clang-asan
   3313022:        binding file /lib/x86_64-linux-gnu/libc.so.6 [0] to ./test-clang-asan [0]: normal symbol `malloc' [GLIBC_2.2.5]
   3313022:        binding file /lib64/ld-linux-x86-64.so.2 [0] to ./test-clang-asan [0]: normal symbol `malloc' [GLIBC_2.2.5]
   3313022:        binding file /usr/lib/x86_64-linux-gnu/libstdc++.so.6 [0] to ./test-clang-asan [0]: normal symbol `malloc' [GLIBC_2.2.5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 동적 링커가 libc.so.6, ld-linux-x86-64.so.2, libstdc++.so의 malloc 참조를 모두 test-clang-asan의 malloc 구현에 바인딩한것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/llvm/llvm-project/commit/7fb7330469af52ae1313b2b47c273e62c61a4dd5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/llvm/llvm-project/commit/7fb7330469af52ae1313b2b47c273e62c61a4dd5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/cd/E23824_01/html/819-0690/gehzq.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/cd/E23824_01/html/819-0690/gehzq.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://maskray.me/blog/2021-05-16-elf-interposition-and-bsymbolic&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://maskray.me/blog/2021-05-16-elf-interposition-and-bsymbolic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>System Programming/Kernel</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/665</guid>
      <comments>https://1000sj.tistory.com/665#entry665comment</comments>
      <pubDate>Mon, 19 Jan 2026 10:41:43 +0900</pubDate>
    </item>
    <item>
      <title>Bochs v2.2.1 소스 코드 분석 #2 Bochs 구조와 동작방식</title>
      <link>https://1000sj.tistory.com/660</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Bochs는 C++로 작성된 IA-32 아키텍쳐 PC 에뮬레이터이다. 쉽게 말해 소프트웨어로 PC 하드웨어 전체를 흉내내는 프로그램이다. Intel x86 CPU, 일반적인 IO 장치 및 BIOS를 에뮬레이션한다. 현재 Bochs는 386, 486, Pentium, Pentium Pro, AMD64 CPU를 에뮬레이트할 수 있으며 MMX, SSESSE, 3DNow! 명령어 세트를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bochs가 실행할 수 있는 운영체제에는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;DOS&lt;/li&gt;
&lt;li&gt;Windows&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 포함된다. 다양한 모드의 컴파일을 사용할 수 있으며 일부는 아직 개발 중이다. 전형적인 응용은 완전한 x86 PC 에뮬레이터를 제공하는 것으로 x86 CPU, hw 장치 및 저장장치를 포함한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://bochs.sourceforge.net/&quot;&gt;http://bochs.sourceforge.net/&lt;/a&gt; 에서 최신 버전을 다운로드 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;버전 2.2.1 소스코드 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bochs v2.2.1 은 약 300 개의 소스파일과 20만줄의 코드로 구성되어있다. 주요 폴더로는 BIOS, Cpu, Memory, Iodev, GUI 등이 있다. 루트 디렉토리에는 VC 프로젝트 파일(.dsp, .dsw) 과 일부 헤더 파일(Bochs.h), main 함수가 있는 main.cpp 이 있다.&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;pre id=&quot;code_1768847055064&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;├── bios
├── build
├── bx_debug
├── cpu
├── disasm
├── doc
├── docs-html
├── dynamic
├── font
├── fpu
├── gui
├── host
├── instrument
├── iodev
├── memory
├── misc
├── patches
└── plex86&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 일부 프로젝트는 Bochs 보조 도구를 컴파일하여 생성한다. 예를 들어 bximage 를 컴파일하려면 bximage.exe가 생성되는데 이 도구는 가상 하드디스크를 생성하는데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bochs.exe는 bochs 프로젝트가 컴파일되어 생성된 것으로 다른 여러 프로젝트에 의존한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 bochs.exe 를 컴파일하려면 먼저 iodev, cpu, memory 등의 의존 프로젝트를 컴파일하여 해당 lib 파일을 생성해야 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 클래스 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 클래스는 로그 기능을 제공하는 logfunctions 클래스를 상속받는다. 이 클래스는 간단한 로그 기능을 완성하여 디버깅 시 관련 정보를 출력하기 편하다.&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;모든 클래스 중 핵심 클래스 세가지는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BX_CPU: CPU 기술&lt;/li&gt;
&lt;li&gt;BX_MEM_C: 메모리 기술&lt;/li&gt;
&lt;li&gt;IO device 파생 클래스들: 각종 하드웨어 장치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 PC 시스템 보드 를 기술하는 클래스 bx_pc_system_c 가 이들을 연결하여 완전한 PC 를 구성한다. 각 장치는 stub 클래스를 통해 통일된 인터페이스 (read/write) 를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통일된 인터페이스를 위해 Bochs는 각 장치에 대해 stub 클래스를 정의했다. stub 클래스의 가상 함수는 통일된 인터페이스를 제공하며 예를 들어 저장 장치 읽기/쓰기, I/O 읽기/쓰기 등이 있다. 이러한 가상 함수의 최종 구현은 각 구체적인 장치의 함수에서 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프레임워크 구조 분석&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Bochs 프로젝트의 중요 클래스&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) VM 콘솔 인터페이스 클래스&lt;/p&gt;
&lt;pre id=&quot;code_1768847539945&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BOCHSAPI bx_gui_c : public logfunctions&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bx_gui_c 에서 파생된 클래스&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bx_win32_gui_c&lt;/li&gt;
&lt;li&gt;bx_svga_gui_c&lt;/li&gt;
&lt;li&gt;bx_term_gui_c&lt;/li&gt;
&lt;li&gt;bx_sdl_gui_c&lt;/li&gt;
&lt;li&gt;bx_wx_gui_c&lt;/li&gt;
&lt;li&gt;bx_rfb_gui_c&lt;/li&gt;
&lt;li&gt;bx_nogui_gui_c&lt;/li&gt;
&lt;li&gt;bx_macintosh_gui_c&lt;/li&gt;
&lt;li&gt;bx_beos_gui_c&lt;/li&gt;
&lt;li&gt;bx_amigaos_gui_c&lt;/li&gt;
&lt;li&gt;bx_carbon_gui_c&lt;/li&gt;
&lt;li&gt;bx_x_gui_c&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;config.h 에서 설정하여 콘솔 인터페이스를 선택한다.&lt;/p&gt;
&lt;pre id=&quot;code_1768847709153&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define BX_WITH_X11 0
#define BX_WITH_BEOS 0
#define BX_WITH_WIN32 1      // &amp;larr; 활성화됨
#define BX_WITH_MACOS 0
#define BX_WITH_CARBON 0
#define BX_WITH_NOGUI 0
#define BX_WITH_TERM 0
#define BX_WITH_RFB 0
#define BX_WITH_AMIGAOS 0
#define BX_WITH_SDL 0
#define BX_WITH_SVGA 0
#define BX_WITH_WX 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2) CPU 에뮬레이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 BX_CPU_C 는 CPU를 기술하는데 쓰인다.&lt;/p&gt;
&lt;pre id=&quot;code_1768847744951&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BOCHSAPI BX_CPU_C : public logfunctions&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 bxInstruction_c 는 하나의 명령어를 정의하며 기본 클래스가 없다.&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;(3) Memory 에뮬레이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 BX_MEM_C&lt;/p&gt;
&lt;pre id=&quot;code_1768847793777&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class BOCHSAPI BX_MEM_C : public logfunctions    // memory/memory.h&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수&lt;/p&gt;
&lt;pre id=&quot;code_1768847803941&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#if BX_PROVIDE_CPU_MEMORY==1
  #if BX_SMP_PROCESSORS==1
    BOCHSAPI extern BX_MEM_C    bx_mem;    // 외부 전역 변수
  #else
  BOCHSAPI extern BX_MEM_C    *bx_mem_array[BX_ADDRESS_SPACES]; // 다중 프로세서의 경우, 
                                                                  // 내부 그룹 사용
  #endif   /* BX_SMP_PROCESSORS */

#endif   /* BX_PROVIDE_CPU_MEMORY==1 */&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 변수&lt;/p&gt;
&lt;pre id=&quot;code_1768847839275&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BOCHSAPI BX_CPU_C    bx_cpu;
BOCHSAPI BX_MEM_C    bx_mem;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4) I/O device 에뮬레이션&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;Class bx_devices_c&lt;/b&gt; : public logfunctions&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Class bx_devmodel_c&lt;/b&gt; : public logfunctions &amp;emsp;&amp;emsp; // iodev/iodev.h&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파생 클래스 목록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bx_busm_stub_c &amp;emsp; //bus mouse&lt;/li&gt;
&lt;li&gt;bx_cmos_stub_c&lt;/li&gt;
&lt;li&gt;bx_devmodel_c&lt;/li&gt;
&lt;li&gt;bx_dma_stub_c&lt;/li&gt;
&lt;li&gt;bx_floppy_stub_c&lt;/li&gt;
&lt;li&gt;bx_hard_drive_stub_c&lt;/li&gt;
&lt;li&gt;bx_keyb_stub_c&lt;/li&gt;
&lt;li&gt;bx_ne2k_stub_c&lt;/li&gt;
&lt;li&gt;bx_pci2isa_stub_c&lt;/li&gt;
&lt;li&gt;bx_pci_ide_stub_c&lt;/li&gt;
&lt;li&gt;bx_pci_stub_c&lt;/li&gt;
&lt;li&gt;bx_pic_stub_c&lt;/li&gt;
&lt;li&gt;bx_serial_stub_c&lt;/li&gt;
&lt;li&gt;bx_speaker_stub_c&lt;/li&gt;
&lt;li&gt;bx_usb_stub_c&lt;/li&gt;
&lt;li&gt;bx_vga_stub_c&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;진입 함수 main() 및 Win32 Gui 초기화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main.cpp 는 매크로 정의를 통해 컴파일러 진입 함수를 선택한다. 기본값은 main이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진입합수 선택&lt;/p&gt;
&lt;pre id=&quot;code_1768847994740&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#if defined(__WXMSW__)    WinMain()      // wxWidgets/win32 사용
#if !defined(__WXMSW__)   main()&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main 함수&lt;/p&gt;
&lt;pre id=&quot;code_1768848006490&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main (int argc, char *argv[])
{
    bx_startup_flags.argc = argc;    // 전역 변수, 타입은 BOCHSAPI, 명령행 파라미터 기록용
    bx_startup_flags.argv = argv;
#if BX_WITH_SDL &amp;amp;&amp;amp; defined(WIN32)
    // if SDL/win32, try to create a console window.
    RedirectIOToConsole ();
    #endif
    return bxmain ();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bxmain() 함수&lt;/p&gt;
&lt;pre id=&quot;code_1768848342753&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int bxmain () {
  ...
  bx_init_siminterface ();   // create the SIM object
  
      if (bx_init_main (bx_startup_flags.argc, bx_startup_flags.argv) &amp;lt; 0) 
      return 0;
      ...
    int status = SIM-&amp;gt;configuration_interface (ci_name, CI_START);
    if (status == CI_ERR_NO_TEXT_CONSOLE)
      BX_PANIC ((&quot;Bochs needed the text console, but it was not usable&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 bx_init_siminterface ();&lt;/p&gt;
&lt;pre id=&quot;code_1768848076650&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void bx_init_siminterface ()
{
  siminterface_log = new logfunctions ();
  siminterface_log-&amp;gt;put (&quot;CTRL&quot;);
  siminterface_log-&amp;gt;settype(CTRLLOG);
  if (SIM == NULL) 
    SIM = new bx_real_sim_c();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bx_init_main()&lt;/p&gt;
&lt;pre id=&quot;code_1768848549934&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define BX_USE_TEXTCONFIG 1      // 모드를 텍스트 설정 모드로 정의&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1768848523273&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class bx_real_sim_c : public bx_simulator_interface_c {
...
  config_interface_callback_t ci_callback;
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 ci_callback() 이 bx_real_sim_c::configuration_interface() 에서 호출되며 configuration_interface()는 두 곳에서 호출된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bx_gui_c::config_handler 중에서 config_handler 가 bx_gui_c::init에서 호출됨&lt;/li&gt;
&lt;li&gt;bxmain()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;begin_simulation()에서 bx_begin_simulation() 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 bx_begin_simulation()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. load_and_init_display_lib() 호출 // GUI 객체 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 만약 BX_DEBUGGER가 정의되어있으면 bx_dbg_main(argc, argv) 호출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. bx_init_hardware() 호출 // 이 함수의 실행 전에 GUI가 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. bx_load32bitOSimagehack&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. SIM-&amp;gt;set_init_done(1) // 초기화 완료 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. ...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. BX_CPU(0)-&amp;gt;cpu_loop(1); //CPU 루프 진입&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;bx_init_hardware()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768849441144&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int bx_init_hardware()
{
  // all configuration has been read, now initialize everything.

  if (SIM-&amp;gt;get_param_enum(BXP_BOCHS_START)-&amp;gt;get ()==BX_QUICK_START) {
    for (int level=0; level&amp;lt;N_LOGLEV; level++) {
      int action = SIM-&amp;gt;get_default_log_action (level);
#if !BX_USE_CONFIG_INTERFACE
      if (action == ACT_ASK) action = ACT_FATAL;
#endif
      io-&amp;gt;set_log_action (level, action);
    }
  }

  bx_pc_system.init_ips(bx_options.Oips-&amp;gt;get ());

  Bit32u memSize = bx_options.memory.Osize-&amp;gt;get ()*1024*1024;

// 단일 프로세서 상황에서
#if BX_SMP_PROCESSORS==1
  // 메모리 초기화
  BX_MEM(0)-&amp;gt;init_memory(memSize);

  // 로드 BIOS and VGABIOS
  BX_MEM(0)-&amp;gt;load_ROM(bx_options.rom.Opath-&amp;gt;getptr (), bx_options.rom.Oaddress-&amp;gt;get (), 0);
  BX_MEM(0)-&amp;gt;load_ROM(bx_options.vgarom.Opath-&amp;gt;getptr (), 0xc0000, 1);

  // Then load the optional ROM images
  ...

  // CPU 초기화
  BX_CPU(0)-&amp;gt;init (BX_MEM(0));
  BX_CPU(0)-&amp;gt;set_cpu_id(0);
  // CPU 점검
  BX_CPU(0)-&amp;gt;sanity_checks();
  // 뭘 하는지 모르겠음
  BX_INSTR_INIT(0);
  BX_CPU(0)-&amp;gt;reset(BX_RESET_HARDWARE);

#if BX_DEBUGGER == 0
  // 외부 장치 초기화
  DEV_init_devices();
  DEV_reset_devices(BX_RESET_HARDWARE);
  bx_gui-&amp;gt;init_signal_handlers ();
  bx_pc_system.start_timers();
#endif

  return(0);
}&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;win32 GUI의 시동과 초기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;plugin.h 중에 매크로 정의&lt;/p&gt;
&lt;pre id=&quot;code_1768849484157&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define PLUG_load_plugin(name,type) {lib##name##_LTX_plugin_init(NULL,type,0,NULL);}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;libwin32_LTX_plugin_init 함수를 찾으려했지만 직접 정의된 곳은 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 절차로 IMPLEMENT_GUI_PLUGIN_CODE(win32) 매크로가 컴파일 시점에 함수를 자동 생성한다.&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) 플러그인 로드 매크로 (plugin.h)&lt;/p&gt;
&lt;pre id=&quot;code_1768849982502&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define PLUG_load_plugin(name, type) \
    { lib##name##_LTX_plugin_init(NULL, type, 0, NULL); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 매크로를 사용하면&lt;/p&gt;
&lt;pre id=&quot;code_1768849993693&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PLUG_load_plugin(win32, PLUGTYPE_OPTIONAL)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 확장된다.&lt;/p&gt;
&lt;pre id=&quot;code_1768850014471&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{ libwin32_LTX_plugin_init(NULL, PLUGTYPE_OPTIONAL, 0, NULL); }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계: GUI 플러그인 구현 매크로 (gui/gui.h)&lt;/p&gt;
&lt;pre id=&quot;code_1768850048508&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define IMPLEMENT_GUI_PLUGIN_CODE(gui_name)                              \
    int lib##gui_name##_LTX_plugin_init(plugin_t *plugin,               \
                                         plugintype_t type,              \
                                         int argc, char *argv[]) {       \
        genlog-&amp;gt;info(&quot;installing %s module as the Bochs GUI&quot;, #gui_name);\
        theGui = new bx_##gui_name##_gui_c();                            \
        bx_gui = theGui;                                                  \
        return(0);                                                        \
    }&lt;/code&gt;&lt;/pre&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;3단계: 실제 사용 (gui/win32.cpp)&lt;/p&gt;
&lt;pre id=&quot;code_1768850082658&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;IMPLEMENT_GUI_PLUGIN_CODE(win32)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한줄이 다음과 같이 확장된다.&lt;/p&gt;
&lt;pre id=&quot;code_1768850098721&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int libwin32_LTX_plugin_init(plugin_t *plugin,
                              plugintype_t type,
                              int argc, char *argv[]) {
    genlog-&amp;gt;info(&quot;installing win32 module as the Bochs GUI&quot;);
    theGui = new bx_win32_gui_c();    // Win32 GUI 객체 생성
    bx_gui = theGui;                   // 전역 포인터에 할당
    return(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 gui 정의도 같은 방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1768850118350&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// gui/x11.cpp
IMPLEMENT_GUI_PLUGIN_CODE(x11)
&amp;rarr; libx11_LTX_plugin_init() 함수 생성
&amp;rarr; new bx_x11_gui_c() 호출

// gui/sdl.cpp
IMPLEMENT_GUI_PLUGIN_CODE(sdl)
&amp;rarr; libsdl_LTX_plugin_init() 함수 생성
&amp;rarr; new bx_sdl_gui_c() 호출

// gui/nogui.cpp
IMPLEMENT_GUI_PLUGIN_CODE(nogui)
&amp;rarr; libnogui_LTX_plugin_init() 함수 생성
&amp;rarr; new bx_nogui_gui_c() 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bochs의 작업 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 CPU의 원리와 마찬가지로 Bochs 가상머신 CPU의 실행방식은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 가져오기(fetch) -&amp;gt; 실행 (execute) -&amp;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;CPU 리셋 후 real mode에 진입하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EIP = 0x0000FFF0;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CS의 cache 내의 Base = 0xFFFF0000,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지를 더하면 CS:EIP=FFFFFFF0이 된다. 이 위치는 BIOS ROM이 있는 위치이다. 사용자 프로그램은 이 곳에서부터 실행을 시작하며 CPU 루프에 진입 후 명령어 가져오기와 실행 작업 외에도 매번 이벤트 플래그 비트를 검사한다.&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 루프는 계속 진행된다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oY5HK/dJMcacBSGkw/GTLN5gHp1sm1SVvqFHzH7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oY5HK/dJMcacBSGkw/GTLN5gHp1sm1SVvqFHzH7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oY5HK/dJMcacBSGkw/GTLN5gHp1sm1SVvqFHzH7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoY5HK%2FdJMcacBSGkw%2FGTLN5gHp1sm1SVvqFHzH7k%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;469&quot; height=&quot;479&quot; data-origin-width=&quot;469&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>System Programming/Emulator</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/660</guid>
      <comments>https://1000sj.tistory.com/660#entry660comment</comments>
      <pubDate>Sun, 18 Jan 2026 19:34:16 +0900</pubDate>
    </item>
    <item>
      <title>LLVM Compiler Under the hood #1 컴파일 흐름 이해</title>
      <link>https://1000sj.tistory.com/646</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Compiler 구현 사고방식: 계산기&lt;/h3&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;&amp;nbsp;&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;연산자:&lt;/b&gt; 덧셈, 뺄셈, 곱셈, 나눗셈의 사칙연산 및 지수 연산 지원&lt;/li&gt;
&lt;li&gt;&lt;b&gt;괄호:&lt;/b&gt; 소괄호 지원&lt;/li&gt;
&lt;/ul&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;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;어휘 분석:&lt;/b&gt; 수식 내의 피연산자, 연산자 및 괄호를 스캔하여 토큰 스트림 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구문 분석:&lt;/b&gt; 연산자의 우선순위, 결합 법칙 등의 규칙에 따라 토큰을 조직하여 구문 분석 트리 구축&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해석 실행:&lt;/b&gt; 구문 분석 트리에 따라 연산 결과 계산&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어휘 분석: 피연산자와 연산자 식별&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수석 123+456 을 예로 들면 어휘 분석 과정에서 순서대로 피연산자 123, 연산자 +, 피연산자 456을 식별해야하며 이를 토큰 스트림 &amp;lt;NUM(123)&amp;gt;&amp;lt;ADD&amp;gt;&amp;lt;NUM(456)&amp;gt; 으로 변환해야한다. 알고리즘1은 토큰 식별의 사고 방식을 설명한다. 핵심 버퍼 num을 사용하여 현재 읽어야할 피연산자 자릿수를 기록하는 것이다. 이 단계에서는 수식의 합법성 문제(예: 123+456)는 잠시 고려하지 않으며 &quot;-&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;[알고리즘1: 수식에서 피연산자와 연산자 식별]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력: 문자 스트림&lt;/li&gt;
&lt;li&gt;출력:&amp;nbsp;토큰&amp;nbsp;스트림&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768374653758&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;procedure TOKENIZE(charStream)
    let toks, num = &amp;empty;
    while true do
        let cur = charStream.next();
        match cur :
            case '0'-'9' &amp;rArr; num.append(cur); // num이 비어있으면 맨 앞에 삽입
            case '+' &amp;rArr; toks.add(num); toks.add(ADD); num.clear(); // num이 비어있으면 add(num) 무시
            case '-' &amp;rArr; toks.add(num); toks.add(SUB); num.clear();
            case '*' &amp;rArr; toks.add(num); toks.add(MUL); num.clear();
            case '/' &amp;rArr; toks.add(num); toks.add(DIV); num.clear();
            case '^' &amp;rArr; toks.add(num); toks.add(POW); num.clear();
            case '(' &amp;rArr; toks.add(num); toks.add(LPAR); num.clear();
            case ')' &amp;rArr; toks.add(num); toks.add(RPAR); num.clear();
            case _ &amp;rArr; break; // EOF 또는 잘못된 문자
        end match
    end while
end procedure&lt;/code&gt;&lt;/pre&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;수식 파싱 문제는 매우 고전적이다. 우리가 일반적으로 사용하는 수식 표현 형식은 중위(infix) 표기법이므로 파싱 시, 반드시 연산자의 우선순위와 결합성 규칙을 따라야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;우선순위(precedence):&lt;/b&gt; 지수 연산자 &amp;gt; 곱셈나눗셈 연산자 &amp;gt; 덧셈뺄셈 연산자&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결합성(associativity):&lt;/b&gt; 덧셈, 뺄셈, 곱셈, 나눗셈 연산자는 모두 좌결합, 지수 연산자는 우결합. 예: 2^3^2 = 2^(3^2)이지, (2^3)^2가 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수식 1 + 2 * 3 ^ 4 * 5 * 6의 파싱 과정&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz2NOy/dJMcahpCFFr/1f94vk1FcMJZkRh4yGCqc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz2NOy/dJMcahpCFFr/1f94vk1FcMJZkRh4yGCqc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz2NOy/dJMcahpCFFr/1f94vk1FcMJZkRh4yGCqc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz2NOy%2FdJMcahpCFFr%2F1f94vk1FcMJZkRh4yGCqc1%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;714&quot; height=&quot;350&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&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;수식 파싱 후 최종적으로 얻어지는 파싱 트리는 이진 트리이다. 모든 리프 노드는 피연산자를, 비리프 노드는 연산자를 나타내며 각 연산자는 부모 노드보다 먼저 계산된다. 이는 연산자 우선순위와 결합성을 반영한다.&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;파싱과정은 다음 세가지 상황으로 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 현재 만난 연산자가 좌결합이고 우선순위가 stack에서 top 연산자의 우선순위보다 높으면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 연산자를 stack top 연산자의 오른쪽 자식 노드로 만든다.&lt;/li&gt;
&lt;li&gt;이때 stack top 연산자의 왼쪽 자식 노드는 이미 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 현재 만난 연산자가 좌결합이고 그 우선순위가 stack top 연산자의 우선순위보다 높지 않으면
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 연산자를 stack top 연산자의 부모 노드 또는 조상 노드로 만들어야 한다.&lt;/li&gt;
&lt;li&gt;(stack에서 pop하여 현재 연산자보다 우선순위가 낮은 연산자를 만날때 까지 반복)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 현재 만난 연산자가 우결합이면 이를 stack top 연산자의 오른쪽 자식 노드로 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;table style=&quot;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;우선순위&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;수식:&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;^&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;위치:&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pratt 파싱은 연산자 우선순위와 결합성을 지원하는 파싱 알고리즘이다.&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;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;[알고리즘 2: 연산자 우선순위 파싱 알고리즘]&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&gt;&lt;span&gt;입력: 토큰 스트림, 우선순위 (0으로 초기화) &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;출력: 이진 파싱 트리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1768378105761&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;p[ADD] = (1,2), p[SUB] = (1,2), p[MUL] = (3,4), p[DIV] = (3,4), p[POW] = (6,5);

procedure PRATTPARSE(cur, preced)
    let l = cur.next(); // next()는 cur을 다음 위치로 이동하고 해당 위치의 값을 반환
    if l.type() &amp;ne; TOK::NUM then
        return ERROR;
    end if
    while true do // 연산자 스택에서 연산자를 팝하는 것에 해당
        let op = cur.peek(); // peek()는 다음 위치의 값을 반환
        match op.type() :
            case TOK::NUM &amp;rArr; exit ERROR;
            case TOK::EOF &amp;rArr; return l;
        end match
        (lp, rp) = p[op];
        if lp &amp;lt; preced then
            return l;
        end if
        cur.next();
        let r = PrattParse(cur, rp);
        let l = CreateBinTree(op, l, r);
    end while
    return l;
end procedure&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 2는 Pratt 알고리즘의 의사 코드 구현을 보여준다. 초기 위치의 우선순위를 0으로 가정하고 PrattParse 함수를 호출하면 아까와 같은 구문 분석 트리를 얻을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1768379629347&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        +
       / \
      1   *
         / \
        *   6
       / \
      *   5
     / \
    2   ^
       / \
      3   ^
         / \
        4   5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[알고리즘 2를 적용하여 수식 1 + 2 * 3 ^ 4 * 5 * 6을 파싱하는 단계]&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;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;&lt;b&gt;cur&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;preced&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;l&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;op&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;lp&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;rp&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;조작&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;r = PrattParse(cur, rp); l = CreateBinTree(peek, l, r);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;r = PrattParse(cur, rp); l = CreateBinTree(peek, l, r);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;^&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;r = PrattParse(cur, rp); l = CreateBinTree(peek, l, r);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;r = PrattParse(cur, rp); l = CreateBinTree(peek, l, r);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;return l;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;^(4,5)&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;return l;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;^(3,^(4,5))&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;r = PrattParse(cur, rp); l = CreateBinTree(peek, l, r);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;*(2,^(3,^(4,5)))&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;r = PrattParse(cur, rp); l = CreateBinTree(peek, l, r);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;EOF&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;return l;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;((2,^(3,^(4,5))),6)&lt;/td&gt;
&lt;td&gt;EOF&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;return l;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+(1,((2,^(3,^(4,5))),6))&lt;/td&gt;
&lt;td&gt;EOF&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;return l;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해석 실행: 역폴란드 표기법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구문 분석 트리를 기반으로, 후위 순회를 통해 수식의 계산을 완료할 수 있다. 계산기 프로그램의 관점에서 또다른 일반적인 방법은 수식을 역폴란드 표기법(Reverse Polish Notation) 으로 변환하는 것이다.&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 수식 1 + 2 * 3 ^ 4 * 5 * 6의 역폴란드 표기법은 1 2 3 4 5 ^ ^ * 5 * 6 * +이다. (아래 구문 분석 트리를 후위순회)&lt;/p&gt;
&lt;pre id=&quot;code_1768379758714&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        +
       / \
      1   *
         / \
        *   6
       / \
      *   5
     / \
    2   ^
       / \
      3   ^
         / \
        4   5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역폴란드 표기법의 계산은 매우 직관적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서대로 토큰을 읽어서 피연산자를 만나면 스택에 푸시하고 연산자를 만나면 stack top 의 두 피연산자를 팝하여 계산한후 결과를 다시 stack 에 푸시한다. token읽기가 완료되면 stack top의 원소가 최종 결정된다.&lt;/p&gt;
&lt;table style=&quot;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;&lt;b&gt;단계&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;토큰&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;동작&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;stack 상태&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;[1]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;[1, 2]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;[1, 2, 3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;[1, 2, 3, 4]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;[1, 2, 3, 4, 5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;^&lt;/td&gt;
&lt;td&gt;4^5 = 1024&lt;/td&gt;
&lt;td&gt;[1, 2, 3, 1024]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;3*1024 = 3072&lt;/td&gt;
&lt;td&gt;[1, 2, 3072]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;2*3072 = 6144&lt;/td&gt;
&lt;td&gt;[1, 6144]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;[1, 6144, 6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;*&lt;/td&gt;
&lt;td&gt;6144*6 = 36864&lt;/td&gt;
&lt;td&gt;[1, 36864]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;1+36864 = 36865&lt;/td&gt;
&lt;td&gt;[36865]&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;결과: 36865&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일러 흐름&lt;/h3&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;
&lt;pre id=&quot;code_1768380007276&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                    소스 코드
                       &amp;darr;
                    토큰 스트림
                       &amp;darr;
                   구문 분석 트리
                       &amp;darr;
    ┌─────────────────┼─────────────────┐
    &amp;darr;                 &amp;darr;                 &amp;darr;
해석 실행      추상 구문 트리    &amp;rarr;    타입 추론/검사
① 계산기              &amp;darr;
                   선형 IR
                       &amp;darr;
           ┌──────────┴──────────┐
           &amp;darr;                     &amp;darr;
       해석 실행              어셈블리 코드
       ② 가상 머신                &amp;darr;
          JIT               실행 프로그램
                           ③ 네이티브 실행&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vaughan R. Pratt, &quot;Top down operator precedence.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>System Programming/Compiler</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/646</guid>
      <comments>https://1000sj.tistory.com/646#entry646comment</comments>
      <pubDate>Wed, 14 Jan 2026 16:37:53 +0900</pubDate>
    </item>
    <item>
      <title>Linux KCOV(Kernel Coverage)</title>
      <link>https://1000sj.tistory.com/628</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;KCOV란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 코드에 &lt;b&gt;컴파일 시점&lt;/b&gt;에 계측(instrumentation)을 삽입해서, 테스트 케이스가 어떤 코드 경로를 실행했는지 알려주는 도구이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Syzkaller&lt;/b&gt; 같은 커버리지 기반 퍼저가 &quot;흥미로운&quot; 테스트 케이스(새로운 코드 경로를 발견한 케이스)를 판별하는 데 주로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 컴파일 시점 계측&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KCOV는 &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;GCC/Clang의 -fsanitize-coverage=trace-pc 플래그를 사용하면 컴파일러가 각 &lt;b&gt;기본 블록(basic block)&lt;/b&gt; 시작점에 __sanitizer_cov_trace_pc() 함수 호출을 삽입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수가 호출되면 해당 위치의 주소(PC)를 공유 메모리에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1단계: 컴파일러 플래그&lt;/h4&gt;
&lt;pre id=&quot;code_1767350312802&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-fsanitize-coverage=trace-pc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 플래그를 주면 GCC/Clang이 각 &lt;b&gt;기본 블록(basic block)&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;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;진입점 하나, 출구점 하나, 중간에 분기 없는 코드 덩어리&lt;/li&gt;
&lt;li&gt;블록의 일부가 실행되면 전체가 실행된 것으로 간주 가능&lt;/li&gt;
&lt;li&gt;그래서 블록 시작점에만 계측하면 충분함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2단계: 삽입되는 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일러가 삽입하는 건 호출뿐이고, 실제 함수 구현은 커널(kernel/kcov.c)에 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1767350354815&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void notrace __sanitizer_cov_trace_pc(void)
{
    struct task_struct *t;
    unsigned long *area;
    unsigned long ip = canonicalize_ip(_RET_IP_);  // 이 함수의 리턴 주소 = 호출 위치
    unsigned long pos;

    t = current;
    // 이 스레드에서 KCOV가 켜져 있는지 확인
    if (!check_kcov_mode(KCOV_MODE_TRACE_PC, t))
        return;  // 아니면 즉시 리턴

    area = t-&amp;gt;kcov_area;
    // area[0]에는 지금까지 수집된 PC 개수가 저장됨
    pos = READ_ONCE(area[0]) + 1;
    if (likely(pos &amp;lt; t-&amp;gt;kcov_size)) {
        WRITE_ONCE(area[0], pos);
        barrier();
        area[pos] = ip;  // 새 PC 기록
    }
}&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실제 예시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 코드가 이렇다면&lt;/p&gt;
&lt;pre id=&quot;code_1767350399022&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void some_function(int x) {
    if (x &amp;gt; 0) {
        do_something();
    } else {
        do_other();
    }
}&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1767350414752&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void some_function(int x) {
    __sanitizer_cov_trace_pc();  // 블록 1 진입
    if (x &amp;gt; 0) {
        __sanitizer_cov_trace_pc();  // 블록 2 진입
        do_something();
    } else {
        __sanitizer_cov_trace_pc();  // 블록 3 진입
        do_other();
    }
}&lt;/code&gt;&lt;/pre&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;div&gt;
&lt;table style=&quot;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;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;_RET_IP_&lt;/td&gt;
&lt;td&gt;이 함수를 호출한 위치의 주소 (= 어느 기본 블록인지 식별)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;t-&amp;gt;kcov_area&lt;/td&gt;
&lt;td&gt;유저 공간과 공유하는 메모리 버퍼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;area[0]&lt;/td&gt;
&lt;td&gt;수집된 PC 개수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;area[1..n]&lt;/td&gt;
&lt;td&gt;실제 PC 주소들&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KCOV가 꺼져 있으면 check_kcov_mode()에서 바로 리턴하므로 오버헤드가 매우 적다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 커널 설정 (Kconfig)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KCOV 관련 설정들&lt;/p&gt;
&lt;pre id=&quot;code_1767350817922&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CONFIG_KCOV_ENABLE_COMPARISONS=y # 비교문/분기문 주변에 추가 계측 삽입
CONFIG_KCOV_IRQ_AREA_SIZE=0x40000 # 소프트 인터럽트에서 커버리지 수집용 per-CPU 버퍼 크기
CONFIG_DEBUG_INFO_DWARF4=y       # DWARF 디버그 정보를 커널에 포함
CONFIG_KCOV=y                    # KCOV 활성화
CONFIG_KCOV_INSTRUMENT_ALL=y     # 전체 커널 계측
CONFIG_DEBUG_FS=y                # debugfs 활성화
CONFIG_RANDOMIZE_BASE=n          # KASLR 비활성화 (디버깅 편의), 켜면 부팅마다 커널 주소가 랜덤하게 바뀜&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 커버리지 수집 방법&lt;/h3&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;h4 data-ke-size=&quot;size20&quot;&gt;Local Coverage&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 스레드에서 발생하는 커버리지를 수집한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KCOV는 &lt;b&gt;스레드 단위&lt;/b&gt;로 동작한다. __sanitizer_cov_trace_pc()는 현재 스레드에서 KCOV가 켜져 있는지 확인하고, 아니면 바로 리턴한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 공간에서 시스템 콜을 하면 스레드 ID(PID/TID)가 유지된 채로 커널로 진입하므로, KCOV가 커널 코드 실행을 추적할 수 있다.&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;pre id=&quot;code_1767351030582&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;

#define KCOV_INIT_TRACE   _IOR('c', 1, unsigned long)
#define KCOV_ENABLE       _IO('c', 100)
#define KCOV_DISABLE      _IO('c', 101)
#define COVER_SIZE        (64&amp;lt;&amp;lt;10)    // 64K 엔트리

#define KCOV_TRACE_PC     0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ioctl 명령어들은 커널의 include/uapi/linux/kcov.h와 동일&lt;/li&gt;
&lt;li&gt;COVER_SIZE는 수집할 PC 개수 (필요에 따라 증가 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계: KCOV 파일 열기&lt;/p&gt;
&lt;pre id=&quot;code_1767351055454&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int fd = open(&quot;/sys/kernel/debug/kcov&quot;, O_RDWR);
if (fd &amp;lt; 0)
    perror(&quot;open&quot;), exit(1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;debugfs의 KCOV 파일을 열어서 커널과 통신 채널 생성&lt;/li&gt;
&lt;li&gt;파일이 없으면: mount -t debugfs none /sys/kernel/debug&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3단계: 버퍼 초기화&lt;/p&gt;
&lt;pre id=&quot;code_1767351075735&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))
    perror(&quot;ioctl$KCOV_INIT_TRACE&quot;), exit(1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널 메모리에 커버리지 버퍼 할당&lt;/li&gt;
&lt;li&gt;KCOV를 trace 모드로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4단계: 공유 메모리 매핑&lt;/p&gt;
&lt;pre id=&quot;code_1767351129953&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unsigned long *cover = (unsigned long*)mmap(
    NULL,
    COVER_SIZE * sizeof(unsigned long),
    PROT_READ | PROT_WRITE,
    MAP_SHARED,
    fd,
    0
);
if ((void*)cover == MAP_FAILED)
    perror(&quot;mmap&quot;), exit(1);&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;커널이 할당한 버퍼를 유저 공간에 매핑&lt;/li&gt;
&lt;li&gt;이제 cover 배열로 커버리지 데이터를 직접 읽을 수 있음&lt;/li&gt;
&lt;/ul&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5단계: KCOV 활성화&lt;/p&gt;
&lt;pre id=&quot;code_1767351150529&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC))
    perror(&quot;ioctl$KCOV_ENABLE&quot;), exit(1);

__atomic_store_n(&amp;amp;cover[0], 0, __ATOMIC_RELAXED);&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 스레드에서 KCOV 수집 시작&lt;/li&gt;
&lt;li&gt;cover[0]을 0으로 리셋 (이전 ioctl에서 발생한 커버리지 무시)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6단계: 시스템 콜 실행&lt;/p&gt;
&lt;pre id=&quot;code_1767351170058&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 여기서 테스트하고 싶은 시스템 콜 실행
read(fd, buf, size);
write(fd, buf, size);
// 등등...&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시스템 콜들이 커널에서 실행될 때 KCOV가 PC를 수집한다.&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;7단계: 커버리지 읽기&lt;/p&gt;
&lt;pre id=&quot;code_1767351213670&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 수집된 PC 개수 확인
unsigned long n = __atomic_load_n(&amp;amp;cover[0], __ATOMIC_RELAXED);

// 2. 유저 공간에 작업 버퍼 할당
unsigned long *work_cover = (unsigned long *)malloc(COVER_SIZE * sizeof(unsigned long));

// 3. 공유 버퍼 &amp;rarr; 작업 버퍼로 복사
memcpy(work_cover, *covermem, n);

// 4. 공유 버퍼 리셋 (다음 수집을 위해)
__atomic_store_n(&amp;amp;(*covermem)[0], 0, __ATOMIC_RELAXED);

// 5. 복사본에서 PC 출력
// work_cover[0]: 개수
// work_cover[1..n]: 실제 PC들
for (int i = 0; i &amp;lt; n; i++)
    printf(&quot;0x%lx\n&quot;, cover[i + 1]);
    
free(work_cover);
work_cover = NULL;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cover[0]: 수집된 PC 개수&lt;/li&gt;
&lt;li&gt;cover[1..n]: 실제 PC 주소들&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8단계: 정리&lt;/p&gt;
&lt;pre id=&quot;code_1767351288400&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (ioctl(fd, KCOV_DISABLE, 0))
    perror(&quot;ioctl$KCOV_DISABLE&quot;), exit(1);

munmap(cover, COVER_SIZE * sizeof(unsigned long));
close(fd);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 프로그램 종료 시 KCOV가 알아서 정리한다.&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Remote Coverage (백그라운드 스레드, 워커, softirq 등)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;백그라운드 스레드&lt;/span&gt;&lt;span style=&quot;color: #abb2bf;&quot;&gt;,&lt;/span&gt;&lt;span&gt; 워커 스레드&lt;/span&gt;&lt;span style=&quot;color: #abb2bf;&quot;&gt;,&lt;/span&gt;&lt;span&gt; softirq 등 &lt;/span&gt;&lt;span&gt;다른 컨텍스트&lt;/span&gt;&lt;span&gt;의 커버리지를 수집한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로컬 커버리지는 현재 스레드만 추적합니다&lt;/span&gt;&lt;span style=&quot;color: #abb2bf;&quot;&gt;.&lt;/span&gt;&lt;span&gt; 하지만 커널에는 워커 스레드, softirq 핸들러, USB 드라이버 스레드등 별도로 실행되는 코드가 많다.&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&gt;Handle &lt;/span&gt;&lt;span&gt;시스템&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;리모트 KCOV는 &lt;/span&gt;&lt;span&gt;핸들&lt;/span&gt;&lt;span&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;b&gt;서브시스템 종류&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;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;&lt;b&gt;subsystem&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;값&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;용도&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KCOV_SUBSYSTEM_COMMON&lt;/td&gt;
&lt;td&gt;0x00&lt;/td&gt;
&lt;td&gt;일반 워커 스레드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KCOV_SUBSYSTEM_USB&lt;/td&gt;
&lt;td&gt;0x01&lt;/td&gt;
&lt;td&gt;USB 드라이버&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ID 결정 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;COMMON: 유저가 임의로 지정, task_struct로 전파됨&lt;/li&gt;
&lt;li&gt;USB: USB 버스 번호&lt;/li&gt;
&lt;/ul&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;pre id=&quot;code_1767352074350&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static inline __u64 kcov_remote_handle(__u64 subsys, __u64 inst)
{
    if (subsys &amp;amp; ~KCOV_SUBSYSTEM_MASK || inst &amp;amp; ~KCOV_INSTANCE_MASK)
        return 0;
    return subsys | inst;
}&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계: 추가 상수와 구조체 정의&lt;/p&gt;
&lt;pre id=&quot;code_1767352091927&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct kcov_remote_arg {
    __u32           trace_mode;
    __u32           area_size;
    __u32           num_handles;
    __aligned_u64   common_handle;
    __aligned_u64   handles[0];    // 가변 길이 배열
};

#define KCOV_REMOTE_ENABLE  _IOW('c', 102, struct kcov_remote_arg)

#define KCOV_SUBSYSTEM_COMMON  (0x00ull &amp;lt;&amp;lt; 56)
#define KCOV_SUBSYSTEM_USB     (0x01ull &amp;lt;&amp;lt; 56)

#define KCOV_COMMON_ID    0x42
#define KCOV_USB_BUS_NUM  1&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KCOV_REMOTE_ENABLE ioctl 사용&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;현재 지원: KCOV_SUBSYSTEM_COMMON, KCOV_SUBSYSTEM_USB&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리모트 KCOV가 작동하려면 커널 개발자가 &lt;b&gt;직접&lt;/b&gt; 해당 코드에 kcov_remote_start/stop()을 삽입해야 함&amp;nbsp;&lt;/li&gt;
&lt;li&gt;현재 mainline 커널에는 다음 두 곳에만 삽입되어 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;COMMON&lt;/b&gt;: vhost 워커 등 일부 워커 스레드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;USB&lt;/b&gt;: USB 드라이버&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계: 초기 설정 (로컬과 동일)&lt;/p&gt;
&lt;pre id=&quot;code_1767352103999&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int fd = open(&quot;/sys/kernel/debug/kcov&quot;, O_RDWR);
ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE);
unsigned long *cover = mmap(...);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3단계: 리모트 인자 구성&lt;/p&gt;
&lt;pre id=&quot;code_1767352116909&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct kcov_remote_arg *arg;
// 구조체 + 핸들 1개 공간 할당
arg = calloc(1, sizeof(*arg) + sizeof(uint64_t));

arg-&amp;gt;trace_mode = KCOV_TRACE_PC;
arg-&amp;gt;area_size = COVER_SIZE;
arg-&amp;gt;num_handles = 1;  // uncommon 핸들 개수 (USB)

// COMMON 핸들: 워커 스레드용
arg-&amp;gt;common_handle = kcov_remote_handle(KCOV_SUBSYSTEM_COMMON, KCOV_COMMON_ID);

// USB 핸들: 버스 1번
arg-&amp;gt;handles[0] = kcov_remote_handle(KCOV_SUBSYSTEM_USB, KCOV_USB_BUS_NUM);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;핸들(handle) 시스템으로 어떤 스레드의 커버리지를 수집할지 지정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4단계: 리모트 KCOV 활성화&lt;/p&gt;
&lt;pre id=&quot;code_1767352136201&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (ioctl(fd, KCOV_REMOTE_ENABLE, arg))
    perror(&quot;ioctl$KCOV_REMOTE_ENABLE&quot;), exit(1);
free(arg);  // 활성화 후 해제 가능&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5단계: 커버리지 읽기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬과 동일하게 cover 배열에서 읽는다.&lt;/p&gt;
&lt;pre id=&quot;code_1767352167694&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unsigned long n = __atomic_load_n(&amp;amp;cover[0], __ATOMIC_RELAXED);
for (int i = 0; i &amp;lt; n; i++)
    printf(&quot;0x%lx\n&quot;, cover[i + 1]);&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COMMON의 경우 핸들이 전파되는 방식은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user가 vhost 디바이스를 열면&lt;/p&gt;
&lt;pre id=&quot;code_1767353098975&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int fd = open(&quot;/dev/vhost-net&quot;, O_RDWR);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 현재 태스크의 kcov_handle이 vhost 워커에 복사된다.&lt;/p&gt;
&lt;pre id=&quot;code_1767353119749&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;current-&amp;gt;kcov_handle &amp;rarr; worker-&amp;gt;kcov_handle&lt;/code&gt;&lt;/pre&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 코드에서 kcov_remote_start(handle)이 호출되면&lt;/p&gt;
&lt;pre id=&quot;code_1767353149241&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// drivers/vhost/vhost.c 
// vhost_worker(): VM I/O 처리하는 워커 스레드의 메인 루프
static int vhost_worker(void *data)
{
    ...
    kcov_remote_start_common(worker-&amp;gt;kcov_handle);
    // 작업 수행
    kcov_remote_stop();
    ...
}

// 스레드에서 커버리지 수집 start 함수
void kcov_remote_start(u64 handle)
{
    // 1. 이 핸들이 등록되어 있나?
    kcov = kcov_remote_find(handle);
    if (!kcov)
        return;  // 등록 안 됨 &amp;rarr; 무시

    // 2. 등록되어 있으면 현재 스레드에서 KCOV 활성화
    t = current;
    t-&amp;gt;kcov = kcov;
    t-&amp;gt;kcov_mode = KCOV_MODE_TRACE_PC;
    t-&amp;gt;kcov_area = kcov-&amp;gt;area;  // 유저와 공유하는 버퍼
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 __sanitizer_cov_trace_pc()가 호출되면&lt;/p&gt;
&lt;pre id=&quot;code_1767353227789&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void __sanitizer_cov_trace_pc(void)
{
    t = current;
    
    // kcov_remote_start()가 설정해둔 값 확인
    if (!check_kcov_mode(KCOV_MODE_TRACE_PC, t))
        return;
    
    // PC 기록!
    area = t-&amp;gt;kcov_area;
    area[++area[0]] = _RET_IP_;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;전달된 핸들이 등록되어 있는지 확인&lt;/li&gt;
&lt;li&gt;등록되어 있으면 해당 스레드에서 KCOV 활성화&lt;/li&gt;
&lt;li&gt;kcov_remote_stop() 호출 시 비활성화&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kcov_remote_start/stop은 그냥 스위치이다. 실제 일은 vhost_worker가 한다.&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 서브시스템 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IPv6 스택의 커버리지를 수집하기 위해 직접 커널을 수정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 IPv6용 서브시스템이 mainline 커널에 없어서 직접 만들어야한다.&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: 서브시스템 ID 정의&lt;/p&gt;
&lt;pre id=&quot;code_1767353587094&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// include/uapi/linux/kcov.h

#define KCOV_SUBSYSTEM_COMMON  (0x00ull &amp;lt;&amp;lt; 56)
#define KCOV_SUBSYSTEM_USB     (0x01ull &amp;lt;&amp;lt; 56)
#define KCOV_SUBSYSTEM_IPV6    (0x02ull &amp;lt;&amp;lt; 56)  // &amp;larr; 새로 추가!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 2: 헬퍼 함수 추가&lt;/p&gt;
&lt;pre id=&quot;code_1767353635783&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// include/linux/kcov.h

// 일반 컨텍스트용
static inline void kcov_remote_start_ipv6(u64 id)
{
    kcov_remote_start(kcov_remote_handle(KCOV_SUBSYSTEM_IPV6, id));
}

// softirq 전용 (중복 시작 방지)
static inline void kcov_remote_start_ipv6_softirq(u64 id)
{
    if (in_serving_softirq()) {  // softirq 안에서만 실행
        kcov_remote_start(kcov_remote_handle(KCOV_SUBSYSTEM_IPV6, id));
    }
}&lt;/code&gt;&lt;/pre&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&gt;왜 softirq 체크가 필요한가&lt;/span&gt;&lt;span style=&quot;color: #61afef;&quot;&gt;?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://ics.uci.edu/~jbursey/kcov-for-dummies.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ics.uci.edu/~jbursey/kcov-for-dummies.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ㄴㅇㄹㄴㅇㄹ&lt;/li&gt;
&lt;/ul&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;&lt;/p&gt;</description>
      <category>Security/Fuzzing</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/628</guid>
      <comments>https://1000sj.tistory.com/628#entry628comment</comments>
      <pubDate>Fri, 2 Jan 2026 20:34:04 +0900</pubDate>
    </item>
    <item>
      <title>Compiler 개발 #1 컴파일러 동작 원리</title>
      <link>https://1000sj.tistory.com/586</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴파일러란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컴파일러&lt;/b&gt;: 소스 언어 &amp;rarr; 타겟 언어로 &lt;b&gt;번역&lt;/b&gt;하는 프로그램&lt;/p&gt;
&lt;pre id=&quot;code_1766384824699&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Source Code  │ ──▶ │   Compiler   │ ──▶ │ Target Code  │
│   (C, Java)  │     │              │     │ (기계어/어셈블리)│
└──────────────┘     └──────────────┘     └──────────────┘&lt;/code&gt;&lt;/pre&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;div&gt;
&lt;table style=&quot;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;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;컴파일러&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;인터프리터&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;동작&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;전체 번역 후 실행&lt;/td&gt;
&lt;td&gt;한 줄씩 즉시 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;실행 빠름 ✅&lt;/td&gt;
&lt;td&gt;실행 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;디버깅&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;에러 위치 파악 어려움&lt;/td&gt;
&lt;td&gt;에러 진단 쉬움 ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;C, C++, Go&lt;/td&gt;
&lt;td&gt;Python, Ruby&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;div&gt;
&lt;table style=&quot;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;&lt;b&gt;Analysis (분석)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;소스 코드를 쪼개고 구조 파악&lt;/td&gt;
&lt;td&gt;Front-end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Synthesis (합성)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;중간 표현 &amp;rarr; 타겟 코드 생성&lt;/td&gt;
&lt;td&gt;Back-end&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&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;컴파일러의 단계 (Phases)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1766384944671&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;position = initial + rate * 60
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│  1. Lexical Analyzer (어휘 분석기)                           │
│     문자 스트림 &amp;rarr; 토큰 스트림                                 │
│     결과: ⟨id,1⟩ ⟨=⟩ ⟨id,2⟩ ⟨+⟩ ⟨id,3⟩ ⟨*⟩ ⟨60⟩            │
└─────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│  2. Syntax Analyzer (구문 분석기)                            │
│     토큰 &amp;rarr; 파스 트리 (Parse Tree)                            │
│                    =                                         │
│                   / \                                        │
│              ⟨id,1⟩  +                                       │
│                     / \                                      │
│                ⟨id,2⟩  *                                     │
│                       / \                                    │
│                  ⟨id,3⟩  60                                  │
└─────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│  3. Semantic Analyzer (의미 분석기)                          │
│     타입 검사, 의미 검증                                      │
│     예: 60 (int) &amp;rarr; 60.0 (float) 타입 변환 삽입               │
└─────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│  4. Intermediate Code Generator (중간 코드 생성기)            │
│     기계 독립적인 중간 표현                                   │
│     t1 = inttofloat(60)                                     │
│     t2 = id3 * t1                                           │
│     t3 = id2 + t2                                           │
│     id1 = t3                                                │
└─────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│  5. Code Optimizer (코드 최적화기)                           │
│     중간 코드 개선                                           │
│     t1 = id3 * 60.0                                         │
│     id1 = id2 + t1                                          │
└─────────────────────────────────────────────────────────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────┐
│  6. Code Generator (코드 생성기)                             │
│     타겟 기계어 생성                                         │
│     LDF  R2, id3                                            │
│     MULF R2, R2, #60.0                                      │
│     LDF  R1, id2                                            │
│     ADDF R1, R1, R2                                         │
│     STF  id1, R1                                            │
└─────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&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;Symbol Table&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 단계에서 공유하는 자료구조:&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;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;&lt;b&gt;이름&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;타입&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;주소&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;position&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;initial&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rate&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&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;pre id=&quot;code_1766384665709&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ 소스 코드    │ ──▶ │   렉서      │ ──▶ │   파서      │ ──▶ │ 코드 생성    │
│ (텍스트)    │     │ (Lexer)     │     │ (Parser)    │     │ (CodeGen)   │
└─────────────┘     └──────┬──────┘     └──────┬──────┘     └─────────────┘
                           │                   │
                           ▼                   ▼
                    token_vec             AST (추상 구문 트리)&lt;/code&gt;&lt;/pre&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. 렉서: 소스 코드 &amp;rarr; 토큰 벡터 (`lex_process-&amp;gt;token_vec`)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;파서: 토큰 벡터 &amp;rarr; AST (추상 구문 트리) &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3, 코드 생성: AST &amp;rarr; 기계어/어셈블리&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dragon Book Compilers Principle Techniques and Tools 2nd Edtion.pdf&lt;/li&gt;
&lt;li&gt;dfsdf&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-pm-slice=&quot;1 1 []&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;</description>
      <category>System Programming/Compiler</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/586</guid>
      <comments>https://1000sj.tistory.com/586#entry586comment</comments>
      <pubDate>Fri, 19 Dec 2025 17:06:59 +0900</pubDate>
    </item>
    <item>
      <title>linux vm stack 개발 노트 #1 kvm 내부 구조</title>
      <link>https://1000sj.tistory.com/585</link>
      <description>&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;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;References&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://zserge.com/posts/kvm/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zserge.com/posts/kvm/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zserge.com/posts/containers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zserge.com/posts/containers/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spotify/linux/blob/master/Documentation/kvm/api.txt&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/spotify/linux/blob/master/Documentation/kvm/api.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://iovec.net/2024-01-29&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://iovec.net/2024-01-29&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lwn.net/Articles/658511/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://lwn.net/Articles/658511/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://david942j.blogspot.com/2018/10/note-learning-kvm-implement-your-own.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://david942j.blogspot.com/2018/10/note-learning-kvm-implement-your-own.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>System Programming/Emulator</category>
      <author>1000sj</author>
      <guid isPermaLink="true">https://1000sj.tistory.com/585</guid>
      <comments>https://1000sj.tistory.com/585#entry585comment</comments>
      <pubDate>Fri, 19 Dec 2025 17:01:57 +0900</pubDate>
    </item>
  </channel>
</rss>