일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 백준#BOJ#8012#한동이는영업사원
- 백준#BOJ#14501#퇴사#브루트포스
- 백준#boj#12755
- 백준#BOJ#1939#중량제한
- 백준#boj#16932#모양만들기
- 백준#BOJ#12865#평범한배낭
- 백준#BOJ#2615#오목
- Today
- Total
순간을 성실히, 화려함보단 꾸준함을
[OSS] sds 를 사용하여 명령어에 문자열 추가하기 본문
안녕하세요. 4주차 OSS 과제를 포스팅 해보려고 합니다.
과제 내용
- echo2 123 이라고 명령어를 입력하면 echo2_123 이런식으로 출력이 되게끔 명령어를 만들어보세요.
- 다음 구조체 함수들을 참고하세요
- sds
- sdscatfmt
- sdsempty
- addReplyBulkSds
해설
먼저 sds 가 대체 무엇일까요???
http://www.redisgate.com/redis/configuration/internal_string.php
해당 링크에 자세하게 잘 나와있는데요. 간략하게 말씀드리면 문자열 처리를 위한 라이브러리라고 생각하시면 될 것 같습니다.
typedef char *sds;
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
링크해드린 글을 보시면 알겠지만 redis 3.2.0 부터는 길이의 따라 sdshdr(sds header) 의 구조체의 종류가 세분화 되었습니다.
문자열의 길이(sds)의 따라서 세분화 되어 있는 것을 확인할 수 있습니다.
그럼 sdshdr 객체는 내가 입력한 '문자열' 과 이 문자열에 대한 '메타데이터' 들이 들어가 있는 구조체 변수이구나!! 라고 생각하시면 됩니다. 그리고 sds 는 sdshdr 의 char buf[] 의 시작 주소를 가리키고 있는 변수입니다.
즉, 내가 입력한 문자열을 가리키고 있는 변수가 sds 라고 생각하시면 됩니다.
(해당 소스는 sds.c 에서 _sdsnewlen 이라는 함수를 참고하시면 됩니다)
sds 객체를 다루는 방법은 첨부한 링크에 들어가시면 친절하게 잘 나와있습니다.
그들 중 몇개만 확인해볼까요?
- sdsnew(char *) : 일반 문자열을 sds 객체로 변환해줍니다.
- sdsempty() : 빈 sds 객체를 생성해줍니다.
- sdscatfmt(sds s, char const *fmt, ...) : 일반 문자열을 sds 객체로 변환해주는데 이때 포맷팅을 실행할 수 있습니다.
- sdsfree(sds s) : sds 문자열 메모리를 해제합니다.
직접 한번 sds 객체를 생성해보도록 해보겠습니다.
sds info = sdsnew(strcat(strcat(c->argv[0]->ptr,"_"),c->argv[1]->ptr));
먼저 sdsnew 함수를 사용하여 sds 객체를 생성하는 방법입니다. 과제에서는 입력한 명령어와 key 값을 이어 붙여야하니 strcat 함수를 사용하여 직접 문자열들을 붙여서 생성해주었습니다.
sds info = sdscatfmt(sdsempty(),"%s_%s",c->argv[0]->ptr,c->argv[1]->ptr);
2번째 방법은 sdscatfmt 함수를 이용하였습니다. 이 함수는 포맷팅을 사용할 수 있어서 strcat 과 같은 함수를 사용해서 직접 문자열을 조작하지 않아도 되서 굉장히 편리한 것 같습니다. 이때 첫번째 파라미터에는 반드시 sds 객체가 들어가야 하니 빈 sds 를 만들어줄 수 있는 sdsempty() 함수를 이용하였습니다.
위와 같이 sds 객체를 생성한 뒤에는 이를 정상적으로 응답해줄 수 있는 과정을 거쳐야겠죠?
- addReplyBulkSds
위 함수를 사용하면 됩니다.
기존 echo 명령어는 addReplyBulk 함수를 사용했었는데요. 두 함수의 차이점은 addReplyBulk 함수는 파라미터로 sds 가 아닌 robj 객체를 받습니다.
/* Add a Redis Object as a bulk reply */
void addReplyBulk(client *c, robj *obj) {
addReplyBulkLen(c,obj);
addReply(c,obj);
addReplyProto(c,"\r\n",2);
}
robj 를 따라가다 보면
struct serverObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
};
위와 같이 serverObject 라는 구조체이구나 라는 걸 볼 수 있으실텐데 해당 구조체에 관한 설명은 제가 첨부해드린 링크에 자세히 나와있으니 꼭 참고해보시길 바라겠습니다.
어쨌건 우리는 문자열을 이용한 명령어의 응답을 받으려고 addReplyBulkSds 함수를 사용하면 됩니다.
void echoSeongJuCommand(client *c){
// sds info = sdscatfmt(sdsempty(),"%s_%s",c->argv[0]->ptr,c->argv[1]->ptr);
// sds info = sdscatprintf(sdsempty(),"%s_%s",c->argv[0]->ptr,c->argv[1]->ptr);
sds info = sdsnew(strcat(strcat(c->argv[0]->ptr,"_"),c->argv[1]->ptr));
addReplyBulkSds(c,info);
}
결국 최종적으로 위와 같이 코드를 작성하면 과제를 잘 끝마칠 수 있습니다.
이때, addReplyBulkSds 라는 함수를 뜯어보면 addReplySds 함수를 호출하는 것을 볼 수 있으실텐데
/* Add the SDS 's' string to the client output buffer, as a side effect
* the SDS string is freed. */
void addReplySds(client *c, sds s) {
if (prepareClientToWrite(c) != C_OK) {
/* The caller expects the sds to be free'd. */
sdsfree(s);
return;
}
_addReplyToBufferOrList(c,s,sdslen(s));
sdsfree(s);
}
sdsfree 함수를 사용해서 문자열 메모리를 할당해줬던걸 해제하는 것을 알 수 있습니다.
즉, addReplyBulkSds 에 파라미터로 넣는 sds 객체는 더 이상 사용할 수 없음을 뜻합니다.
/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
if (s == NULL) return;
s_free((char*)s-sdsHdrSize(s[-1]));
}
sdsfree 함수는 위와 같이 코드가 짜여져있는데,,,,,,대체 free 를 해줄 때 왜 대체 저런식으로 해주는 걸까요????
바로 sds 객체는 저희가 입력한 문자열의 시작주소만 가리키고 있는 변수이기 때문입니다.
무슨말이냐면 우리가 해제하고 싶은건 key 를 입력한 문자열만 해제하고 싶은 것이 아니라 sds header 도 같이 해제하고 싶습니다. free 함수는 해제하고 싶은 메모리의 시작주소만 알면 연속적인 메모리를 해제해주는 역할을 수행하죠?
그렇다면 sds 에 관한 정보의 모든 것을 해제하고 싶을테니 sds header 의 시작주소를 알아야지 올바른 메모리 해제가 가능한 것 입니다.
sds 객체 중에 아무거나 가져와 보겠습니다.
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
이 구조체의 크기는 얼마일까요???
uint8_t : 1byte
unsigned char : 1byte
char buf[] : 0byte
총 3byte 입니다. 이는 sdshdr8 의 구조체의 크기가 3byte 라는 것인데요.
제가 만약 echoSeongJu 123 이라고 입력했다면 buf 에는 echoSeongJu_123 이라는 값이 들어가게 될 것이고 len 에는 해당 문자열의 길이 값 alloc,flags 에도 뭔지는 모르겠지만 임의의 값이 들어가게 될 것 입니다.
그러나 저희에게 주어진 정보는 buf의 시작 주소 밖에 모르죠?? sds 밖에 모르기 때문입니다.
이때, 각 변수들이 연속적인 메모리에 위치하니 buf 의 시작주소에서 sdshdr8 구조체의 크기(3byte) 를 빼게 되면 echoSeongJu_123 이라는 문자열을 가지고 있는 sds header 의 시작주소를 가지고 올 수 있는 것 입니다.
아래는 멘토님이 위의 내용을 쉽게 이해할 수 있게끔 알려주신 소스입니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct memory_header {
unsigned long size;
char ptr[];
}memheader;
char *STRING = "hello, OSSCA!";
void test(char *ptr) {
printf("%s\n", ptr);
memheader *mh = (memheader *)(ptr-sizeof(memheader));
printf("mh->size: %d\n", mh->size);
printf("mh->ptr: %s\n", mh->ptr);
}
int main(int argc, char *argv[]) {
printf("usigned long size = %d\n",sizeof(unsigned long));
printf("memheader size = %d\n", sizeof(memheader));
memheader *mh = (memheader *)malloc(sizeof(memheader) + 16);
printf("STRING len = %d\n",strlen(STRING));
mh->size = strlen(STRING);
memcpy(mh->ptr, STRING, mh->size);
mh->ptr[mh->size] = 0;
test(mh->ptr);
return 0;
}
위 소스에서도 우린 mh->ptr 이라는 문자열의 시작주소만 알고 있습니다.
그러나 mh 구조체의 맴버변수들은 연속적인 메모리에 위치하고 있으므로 mh->ptr 의 시작주소에서 memheader 크기만큼 빼주게 되면 결국 'hello, OSSCA!' 라는 문자열을 가지고 있는 memheader 객체의 시작주소를 알 수 있어 정상적으로 free 를 수행할 수 있는 것 입니다.
마무리
결코 쉽지 않은 수업이었습니다. 사실 강의가 1시간인데 1시간안에 이 모든 내용을 담기에는 멘토님도 조금 버거워보이긴 하더라구요. 그래서 최대한 키워드랑 핵심 내용만 메모해두고 강의가 끝난 뒤에 따로 찾아보면서 학습을 하는 방향으로 수업을 따라가려고 하고 있습니다.
오늘의 최대 수확은 바로 위에서 언급한 구조체의 시작주소를 이용한 메모리해제 기법인데 실제 C 에서 많이 사용하는 기법이라고 말씀하더라구요.
어쨌건 이번주도 무사히 잘 과제를 수행한 것 같아서 다행입니다.
'나의 개발 메모장' 카테고리의 다른 글
nginx(reverse proxy) 를 이용하여 cookie samesite 문제 해결하기 (1) | 2024.12.22 |
---|---|
[토이 프로젝트] 마무리 (0) | 2024.05.04 |
[OSS] 나만의 Redis Command 만들어보기 (0) | 2024.04.27 |
긴 여정을 마치고....(토이 프로젝트 마무리) (2) | 2024.03.19 |
대용량 데이터는 어떻게 관리해야 되는 걸까? (0) | 2024.01.20 |