Stream Socket은 연결 지향형이고 Datagram은 비연결형의 Socket 통신 방법입니다. 신뢰성은 그만큼 떨어지지만 단순하고 빠른 전송을 위한 것으로 UDP 프로토콜을 사용합니다. Stream Socket과는 다르게 Datagram 소켓을 사용하여 간단히 Server - Client 통신 소스코드를 짜보도록 합시다. Stream Socket은 서버와 클라이언트 사이에 연결을 맺어주느라 소스 코드의 길이가 길지요. 지금 볼 datagram socket은 이러한 연결(Connection) 과정이 생략이 됩니다.
그러니까 Stream Socket과는 다르게 listen, accept를 datagram socket에서 사용할 필요가 없어졌습니다. 그래서 다행이라고 할까요? Stream socket보다는 코드의 길이가 짧습니다.
서버 코드
아래에서 구현한 소스코드는 client의 메시지를 받아서 그대로 client쪽으로 돌려주는 echo 서버의 소스코드입니다.
//dgram-server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 12346
#define BUF_SIZE 128
void err_exit(const char *err){
perror(err);
exit(1);
}
int main(void){
int socket_fd, n;
struct sockaddr_in host_addr, client_addr;
socklen_t size;
char buffer[BUF_SIZE] = {0,};
//Datagram socket 생성
socket_fd=socket(PF_INET,SOCK_DGRAM,0);
if(socket_fd < 0) err_exit("socket error ");
//server ip와 bind하기 위해 주소 설정
memset(&host_addr, 0, sizeof(host_addr));
host_addr.sin_family=AF_INET;
host_addr.sin_port=htons(PORT);
host_addr.sin_addr.s_addr=0; //자신의 주소로 자동으로 설정
//socket과 host_addr과 bind
if(bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr)) < 0)
err_exit("bind error ");
//Client의 메시지를 그대로 다시 돌려준다.
memset(&client_addr, 0, sizeof(struct sockaddr));
while(1){
size = sizeof(struct sockaddr);
//accept에서 client_addr을 얻어온 stream socket방식과 다르게
//accept가 없는 datagram socket은
//recvfrom에서 client 정보를 얻어올 수 있다.
n = recvfrom(socket_fd, buffer, BUF_SIZE, 0,
(struct sockaddr*)&client_addr, &size);
if(n < 0) err_exit("recvfrom error ");
//Client의 접속 정보를 출력
printf("Client Info : IP %s, Port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
//quit라는 메시지를 받으면 종료
if(!strcmp(buffer,"quit") || n == 0) break;
//보낼 client_addr 객체까지 있어야 전달 가능
n = sendto(socket_fd, buffer, n, 0,
(struct sockaddr*)&client_addr, sizeof(struct sockaddr));
if(n < 0)err_exit("send error ");
}
shutdown(socket_fd, SHUT_RDWR);
return 0;
}
이전 stream socket과 다른 점이 있다면, SOCK_STREAM이 아닌 SOCK_DGRAM을 사용한다는 점입니다.
socket_fd=socket(PF_INET,SOCK_DGRAM,0);
stream socket은 send, recv를 사용하였는데, Datagram socket에서는 sendto, recvfrom을 사용하는 것을 알 수 있네요.
이 두 함수들의 끝 2개는 sockaddr의 구조체와 크기입니다. Server는 accept과정이 없기 때문에 client의 주소를 알 방법이 없습니다. 그래서 다시 echo할 주소가 없다는 거죠. recvfrom은 메시지를 받을때 송신자의 주소를 가져오기 위해서 사용합니다. sendto도 마찬가지로 보낼 곳을 지정할때 사용합니다. 이 주소 정보가 없다면 아래와 같은 관계가 성립됩니다.
epoll은 poll과 비슷한 방식으로 동작하지만 전체 파일 디스크립터가 아닌 이벤트가 발생한 객체만 되돌려줍니다. 그리고 두 가지 이벤트 트리거 방식을 선택하여 동작할 수 있는데, Level Trigger 방식과 Edge Trigger 방식입니다. 이 둘을 줄여서 LT, ET라고 하겠습니다.
이번 글에서는 epoll에 대한 개념을 설명한 후에 Level Trigger 방식의 에코 서버, Edge Trigger 방식의 에코서버의 예제코드를 작성해보도록 하겠습니다. 서버의 소스코드나 클라이언트 소스코드가 길게 느껴질 수 있는데, 한줄 한줄 읽어보면 진짜 별거없습니다!
Level Trigger
LT방식은 어떤 일이 일어났을 때 상태에 따라서 트리거를 지속 시킵니다. 예를 들어서 입력 버퍼가 채워져있는 상태를 1, 아닌 상태를 0으로 놓고, 입력 버퍼가 채워져있는 상태는 계속 1인 상태이기 때문에 이벤트가 지속된다는 뜻입니다.
위의 그림에서도 볼 수 있듯이 1인 상태는 버퍼가 채워져있는 상태로 정의하고, 빨간색 부분에 대해서 지속적으로 이벤트 발생을 알려줍니다.
Edge Trigger
반면 ET방식은 사건이 발생한 시점에 딱 한번 트리거 됩니다. 그러니까 버퍼가 채워지는 그 시점에만 이벤트가 발생한다는 뜻입니다.
이러한 LT, ET 방식인지에 따라서 잠시 후에 나올 epoll_wait 함수가 다음 이벤트까지 기다리는 시점이 달라집니다. 이제 epoll을 다루기 위한 세가지 함수를 소개합니다.
1. epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
epol l객체를 생성합니다. 커널 2.6.8 버전부터 이 size라는 인자는 무시되며 그냥 0보다 큰 값으로 설정만 해주면 됩니다. 실패시에는 -1을 반환하고 정상 반환일때는 0보다 큰 값이 반환됩니다. 이 반환 값을 파일 디스크립터입니다. 그래서 close로 닫을 수 있습니다. 이렇게 나오는 epoll의 파일 디스크립터는 제어, 이벤트 대기시에 사용이 됩니다.
2. epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl 함수는 epfd라는 epoll 파일 디스크립터에의해 참조되는 관심 리스트(interest list)에 fd를 더할지, 변경할지, 삭제할지를 도와주는 함수입니다. event는 그 fd에 대해서 관심있게 주시할 event를 설정해줍니다. poll과 유사합니다. event에는 아까 소개한 ET 방식도 설정할 수 있습니다. operation을 뜻하는 op는 아래의 표와 같이 세가지가 있습니다.
Op
설명
EPOLL_CTL_ADD
epfd의 관심 리스트에 entry를 더해줍니다. entry는 관심있게 볼 파일 디스크립터 fd와 주시할 event를 포함한 개념이라고 보시면 됩니다.
EPOLL_CTL_MOD
관심 리스트의 fd와 연관된 세팅을 변경합니다. 여기서 fd는 전달받은 인자인 event로 새롭게 세팅 됩니다.
EPOLL_CTL_DEL
관심 리스트에 fd를 등록 해제합니다. 여기서 인자 event는 무시되니까 NULL을 전달하면 됩니다.
epoll_event 구조체는 아래와 같이 정의되어있습니다.
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events의 설정할 수 있는 이벤트들은 poll과 비슷합니다.
Event
설명
EPOLLIN
데이터를 읽을 수 있는 event를 설정합니다. fd에 대해서 데이터를 읽을 수 있을 때 event가 발생됩니다.
EPOLLOUT
데이터를 쓸 수 있는 event를 설정합니다. 데이터를 쓸 수 있을때 event가 발생됩니다.
파일 디스크립터에 에러가 발생했을때 이벤트가 발생됩니다. 예외적인 이벤트이므로 이러한 이벤트는 사용자가 설정할 필요없습니다.
EPOLLHUP
파일 디스크립터가 끊겼을 때 발생합니다. 역시 사용자가 일부러 이벤트를 지정할 필요없습니다.
EPOLLET
Edge Trigger 방식으로 이벤트 트리거 방식을 변경시킵니다. epoll은 기본적으로 Level Trigger 방식을 사용하고 있습니다.
EPOLLONESHOT
파일 디스크립터에 대한 이벤트를 한번만 발생시키고자 할 때 이 이벤트를 지정하면 됩니다.
3. epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
epfd에 대해서 이벤트가 발생함을 기다리는 역할을 합니다. Event에는 발생한 event들이 있습니다. 최대 기다릴 events수를 maxevents에 넣을 수 있습니다. Timeout에 따라서 epoll_wait이 계속 기다릴지, 특정 시간 동안 기다릴지, 바로 반환할 지를 정해줄 수 있습니다.
timeout = -1 : 이벤트 발생시까지 무한히 대기합니다.
timeout = 0 : 곧 장 반환합니다.
timeout > 0 : ms만큼 대기하다가 반환됩니다.
예제 코드 - Level Trigger Epoll
아래는 Level Trigger 방식의 epoll 에코 서버의 예제입니다.
server
//epoll_server-level.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#FD_MAX 1024
#define PORT 12346
#define BUF_SIZE 1
const char *welcome_message = "Welcome!\n";
void err_exit(const char *err){
perror(err);
exit(1);
}
void clear_fd(const int epoll_fd, const int fd){
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
int main(void){
int socket_fd, accepted_fd;
struct sockaddr_in host_addr, client_addr;
socklen_t size;
int epoll_fd, i, n, ret;
char buffer[BUF_SIZE] = {0,};
struct epoll_event events[FD_MAX];
int pos = 0;
//STREAM socket 생성
socket_fd=socket(PF_INET,SOCK_STREAM,0);
if(socket_fd < 0) err_exit("socket error ");
//server ip와 bind하기 위해 주소 설정
memset(&host_addr, 0, sizeof(host_addr));
host_addr.sin_family=AF_INET;
host_addr.sin_port=htons(PORT);
host_addr.sin_addr.s_addr = 0; //자신의 주소로 자동으로 설정
//socket과 host_addr과 bind
if(bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr)) < 0)
err_exit("bind error ");
//접속 대기
if(listen(socket_fd,3) < 0)
err_exit("listen error ");
for(i = 0; i < FD_MAX; i++)
events[i].data.fd = -1;
//그냥 0보다 크면 된다.
epoll_fd = epoll_create(1024);
if(epoll_fd < 0) err_exit("epoll_create error ");
struct epoll_event event;
event.data.fd = socket_fd;
event.events = EPOLLIN;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event) < 0)
err_exit("epoll_ctl error ");
while(1){
ret = epoll_wait(epoll_fd, events, FD_MAX, -1);
//-1은 이벤트가 발생할때까지 무한정 대기
if(ret == -1) err_exit("epoll_wait error ");
//ret는 이벤트가 발생한 entry의 갯수, events는 발생한 events의 배열이 저장된다.
for(i = 0; i < ret; i++){
//accept할 것이 있는가?
if(events[i].data.fd == socket_fd && events[i].events & EPOLLIN){
size = sizeof(struct sockaddr_in);
//Client가 connect할때까지 기디린다.
accepted_fd =
accept(socket_fd,(struct sockaddr *)&client_addr,&size);
if(accepted_fd < 0)
err_exit("accept error ");
struct epoll_event client;
client.data.fd = accepted_fd;
client.events = EPOLLIN;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accepted_fd, &client) < 0)
err_exit("epoll_ctl error ");
//Client의 접속 정보를 출력하고 접속 잘됐다고 메시지 전송
printf("Client Info : IP %s, Port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
n = send(accepted_fd, welcome_message, strlen(welcome_message), 0);
if(n < 0) err_exit("send error ");
continue;
}
if(events[i].events & EPOLLIN){
n = recv(events[i].data.fd, &buffer[pos], BUF_SIZE, 0);
if(n < 0) err_exit("recv error ");
//n == 0일 경우에 epoll_fd 관심 리스트에서 비워준다.
if(n == 0) {
clear_fd(epoll_fd, events[i].data.fd);
pos = 0;
continue;
}
if(buffer[pos] == '\0'){
printf("rcv msg : %s\n", buffer);
//quit라는 메시지를 받으면 종료
if(!strcmp(buffer,"quit")){
clear_fd(epoll_fd, events[i].data.fd);
pos = 0;
continue;
}
n = send(events[i].data.fd, buffer, pos, 0);
if(n < 0) err_exit("send error ");
pos = 0;
} else
pos++;
}
}
}
printf("end\n");
shutdown(socket_fd, SHUT_RDWR);
return 0;
}
# ./server Client Info : IP 127.0.0.1, Port 42872 rcv msg : hello epoll server Client Info : IP 127.0.0.1, Port 46286 rcv msg : This is level trigger rcv msg : good bye~~~~~ rcv msg : quit rcv msg : GOOD BYE ^C
Client1
Client2
# ./client 127.0.0.1 12346 Welcome! message:hello epoll server hello epoll server message: GOOD BYE GOOD BYE message:^C
# ./client 127.0.0.1 12346 Welcome! message:This is level trigger This is level trigger message: good bye~~~~~ good bye~~~~~ message:quit quit message: message:
예제 코드 - Edge Trigger Epoll
위 LT방식은 ET방식으로 바꿔볼까요? event에다가 EPOLLET를 추가하시면 ET방식으로 동작하게 됩니다. accepted_fd를 EPOLLET event를 같이 추가해서 실행해보세요.
우리가 구현한 방식은 한 글자씩 버퍼에서 읽어옵니다. 이는 epoll_wait이 반환된 후에 읽을 수가 있죠. LT방식일때는 버퍼에 데이터가 남아있으면 epoll_wait에 의해서 반환되게 됩니다. 하지만 ET방식은 버퍼가 채워지는 이벤트 순간 한번만 epoll_wait이 반환되기 때문에 버퍼에서 한글자만 읽고 대기하게 되는 겁니다.
그럼 여기서 질문, select와 poll은 어떤 방식을 쓰고 있는 걸까요?
우리가 현상을 보게 되면 버퍼가 남이있다면 select와 poll은 return됩니다. 이런 결과를 봐서 우리는 select와 poll은 LT방식인 것을 알 수 있습니다. 이러한 문제를 해결하기 위해서는 non-blocking 방식의 read를 해야합니다.즉, 더 이상 버퍼에 남아있는 데이터가 없어서 오류를 발생할때까지 읽어야합니다. 이때 남아있는 데이터가 없으면 EAGAIN 에러를 발생하게 됩니다. 그래서 이런 방식으로 수정해야합니다.
비차단 모드로 설정
//NON-BLOCKING 모드로 전환
int flags = fcntl(accepted_fd, F_GETFL);
flags |= O_NONBLOCK;
if(fcntl(accepted_fd, F_SETFL, flags) < 0)
err_exit("fcntl error ");
//epoll_server-edge.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#define PORT 12346
#define BUF_SIZE 1
const char *welcome_message = "Welcome!\n";
void err_exit(const char *err){
perror(err);
exit(1);
}
void clear_fd(const int epoll_fd, const int fd){
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
int main(void){
int socket_fd, accepted_fd;
struct sockaddr_in host_addr, client_addr;
socklen_t size;
int epoll_fd, i, n, ret;
char buffer[BUF_SIZE] = {0,};
struct epoll_event events[FD_MAX];
int pos = 0;
//STREAM socket 생성
socket_fd=socket(PF_INET,SOCK_STREAM,0);
if(socket_fd < 0) err_exit("socket error ");
//server ip와 bind하기 위해 주소 설정
memset(&host_addr, 0, sizeof(host_addr));
host_addr.sin_family=AF_INET;
host_addr.sin_port=htons(PORT);
host_addr.sin_addr.s_addr = 0; //자신의 주소로 자동으로 설정
//socket과 host_addr과 bind
if(bind(socket_fd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr)) < 0)
err_exit("bind error ");
//접속 대기
if(listen(socket_fd,3) < 0)
err_exit("listen error ");
for(i = 0; i < FD_MAX; i++)
events[i].data.fd = -1;
//그냥 0보다 크면 된다.
epoll_fd = epoll_create(1024);
if(epoll_fd < 0) err_exit("epoll_create error ");
struct epoll_event event;
event.data.fd = socket_fd;
event.events = EPOLLIN;
//event.events = EPOLLIN | EPOLLET;
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event) < 0)
err_exit("epoll_ctl error ");
while(1){
ret = epoll_wait(epoll_fd, events, FD_MAX, -1);
//-1은 이벤트가 발생할때까지 무한정 대기
if(ret == -1) err_exit("epoll_wait error ");
//ret는 이벤트가 발생한 entry의 갯수, events는 발생한 events의 배열이 저장된다.
for(i = 0; i < ret; i++){
//accept할 것이 있는가?
if(events[i].data.fd == socket_fd && events[i].events & EPOLLIN){
size = sizeof(struct sockaddr_in);
//Client가 connect할때까지 기디린다.
accepted_fd =
accept(socket_fd,(struct sockaddr *)&client_addr,&size);
if(accepted_fd < 0)
err_exit("accept error ");
struct epoll_event client;
client.data.fd = accepted_fd;
client.events = EPOLLIN | EPOLLET;
//NON-BLOCKING 모드로 전환
int flags = fcntl(accepted_fd, F_GETFL);
flags |= O_NONBLOCK;
if(fcntl(accepted_fd, F_SETFL, flags) < 0)
err_exit("fcntl error ");
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accepted_fd, &client) < 0)
err_exit("epoll_ctl error ");
//Client의 접속 정보를 출력하고 접속 잘됐다고 메시지 전송
printf("Client Info : IP %s, Port %d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
n = send(accepted_fd, welcome_message, strlen(welcome_message), 0);
if(n < 0) err_exit("send error ");
continue;
}
if(events[i].events & EPOLLIN){
while(1){
n = recv(events[i].data.fd, &buffer[pos], BUF_SIZE, 0);
if(n < 0)
if(errno == EAGAIN) break;
if(n == 0) {
clear_fd(epoll_fd, events[i].data.fd);
pos = 0;
break;
}
pos++;
}
//quit라는 메시지를 받으면 종료
if(!strcmp(buffer,"quit") || n == 0){
clear_fd(epoll_fd, events[i].data.fd);
pos = 0;
continue;
}
printf("rcv msg : %s\n", buffer);
n = send(events[i].data.fd, buffer, pos, 0);
if(n < 0) err_exit("send error ");
pos = 0;
}
}
}
printf("end\n");
shutdown(socket_fd, SHUT_RDWR);
return 0;
}
Server
# ./server Client Info : IP 127.0.0.1, Port 34820 Client Info : IP 127.0.0.1, Port 34832 rcv msg : hello! epoll ET server rcv msg : HI HI ~~~~ rcv msg : BYE!!! rcv msg : edge trigger~~~~ rcv msg : GOOD Client Info : IP 127.0.0.1, Port 60190 rcv msg : I'm back rcv msg : Bye
Client1
Client2
# ./client 127.0.0.1 12346 Welcome! message:hello! epoll ET server hello! epoll ET server message:edge trigger~~~~ edge trigger~~~~ message:GOOD GOOD message:^C # ./client 127.0.0.1 12346 Welcome! message:I'm back I'm back message:Bye Bye message:^C
# ./client 127.0.0.1 12346 Welcome! message:HI HI ~~~~ HI HI ~~~~ message:BYE!!! BYE!!! message:quit quit message: message:
시큐어부트(Secure Boot)는 말 그대로 안전한 부팅을 의미합니다. 시큐어 부팅은 ROM으로부터 시작되는 부팅의 시작부터 파일 시스템이 얹어지는 부팅 완료까지 안전하게 부팅을 하는 절차입니다. 주로 세 단계의 인증을 거치게 됩니다. 맨 처음 부팅의 시작인 부트로더들의 인증, 그리고 부트로더에 의해 커널 이미지가 올라오게 되면 커널 이미지를 인증, 이후 커널에 의해 파일 시스템이 마운트되기 전에 파일 시스템의 인증을 거치게 됩니다.
결론은 부트 로더 인증 -> 커널 인증 -> 파일 시스템 인증 순으로 이어지게 된다는 것이죠.
부트 로더 인증
임베디드 시스템에서 Boot Loader는 한 가지만 존재하지 않습니다. ROM에서 시작되는 부트 로더1부터 시작해 부트로더2, 부트로더 3 등이 있으며, 여기서도 부트로더 순서대로 인증하는 CoT(Chain Of Trust)라는 기술이 존재하지만 이 포스팅에는 부트로더와 다른 Firmware 이미지들을 한 꺼번에 인증하는 FIP 인증을 다룹니다.
FIP(Firmware Image Package)라고하는 펌웨어 이미지 바이너리를 인증합니다. FIP는 단순히 부트로더들의 모임이라고 생각하시면 되고, FIP를 서명한 서명값이 FIP끝에 달리게 됩니다. 이때 인증은 각 SoC 업체의 Firmware를 사용하여 인증을 하게 되는데요. NXP사의 s32g 칩의 경우에는 HSE(Hardware Security Engine)이라는 펌웨어가 그 역할을 하게 되지요.
인증을 하는 경우에는 공개키가 있어야겠죠? 이 공개키는 어딘가에 저장이 되어야하는데 nxp s32g의 경우 FAT 파일 시스템을 통해서 특정 파티션에 저장하고 있습니다.
커널 인증
부트로더 인증이 되었다면 그 이후 커널 인증을 거쳐야합니다. 커널 인증은 커널이 변조되었는지 아닌지를 판별하게 됩니다. 커널 인증을 하는 대표적인 방법은 Verified Boot라는 건데요. U-boot에서 사용하는 커널 인증 방법으로 FIT(Flattened Image Tree)를 이용합니다. 이 FIT는 아래와 같은 형식의 .its라는 파일을 가지고 생성되어 집니다. its는 FIT에 대한 정보를 갖고 있는 소스 파일입니다.
자, 이제 마지막 인증 과정입니다. 바로 실제 데이터들이 존재하는 파일 시스템을 마운트하기전 파일 시스템을 인증하는 과정이 필요하게 되지요. 파일 시스템을 인증하는 대표적인 기술로 사용되는 것이 dm-verity라는 기술입니다. dm-verity는 파일 시스템 이미지의 원본 블록 을 1차로 나온 Hash 데이터를 다시 2차 Hash를 가하고, 이후 n 번의 Hash를 가하게 되면 결국 마지막 나오는 hash값을 갖고 파일 시스템의 무결성을 검증합니다. 하지만 인증을 위해서는 이 hash값을 서명해야됩니다. 결국 우리는 파일 시스템 원본 이미지 + 해쉬 블록을 미리 생성하고, root hash까지 미리 생성하여 안전하게 보관해야합니다. 이때 hash가 계산되는 모습을 보면 트리 형태로 보여지는데 이를 merkle Tree 라고합니다. 결과적으로 나온 root hash는 노출되어서는 안돼요. 부팅이 되고 마운트될때 파일 시스템의 이미지를 root hash로 만들어서 갖고 있는 root hash와 같다면 인증에 성공합니다.
한가지 중요한 것은 파일 시스템의 쓰기가 발생하면 안된다는 점입니다. 쓰기가 일어나면 인증은 다음 부팅때 당연히 실패합니다. 그래서 파일 시스템은 read-only 파일 시스템에서만 dm-verity가 사용이 가능합니다. 이 dm-verity에 관한 내용과 파일 시스템 인증에 관한 방법은 아래의 페이지에서 세세하게 다루고 있으니 이 페이지를 참고하시기 바랍니다.