2025. 2. 3. 23:21ㆍCS/OS
1. C 표준 라이브러리
기본 타입과 메모리 조작, 문자열 조작 함수를 직접 구현해 본다.
가. 기본 타입들
common.h
typedef int bool;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef uint32_t size_t;
typedef uint32_t paddr_t;
typedef uint32_t vaddr_t;
#define true 1
#define false 0
#define NULL ((void *) 0)
#define align_up(value, align) __builtin_align_up(value, align)
#define is_aligned(value, align) __builtin_is_aligned(value, align)
#define offsetof(type, member) __builtin_offsetof(type, member)
#define va_list __builtin_va_list
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
void *memset(void *buf, char c, size_t n);
void *memcpy(void *dst, const void *src, size_t n);
char *strcpy(char *dst, const char *src);
int strcmp(const char *s1, const char *s2);
void printf(const char *fmt, ...);
#define NULL ((void *) 0)
:(void *)
는 어떤 타입의 데이터든 가리킬 수 있는 범용 포인터 타입
: 메모리 주소0
을 의미하며, 이는 운영체제에서 일반적으로 접근이 허용되지 않는 예약된 메모리 영역
: 포인터가 아무것도 가리키지 않는다는 것을 나타내는 특별한 값paddr_t
: 물리 메모리 주소를 나타내는 타입.vaddr_t
: 가상 메모리 주소를 나타내는 타입. 표준 라이브러리의uintptr_t
에 해당.align_up
:value
를align
의 배수로 맞춰 올림. 여기서align
은 2의 거듭제곱.is_aligned
:value
가align
의 배수인지 확인. 여기서align
은 2의 거듭제곱.offsetof
: 구조체 내에서 특정 멤버가 시작되는 위치(바이트 단위)를 반환.
예를 들어 align_up(0x1234, 0x1000)
은 0x2000
이 되고, is_aligned(0x2000, 0x1000)
은 true를 반환하지만 is_aligned(0x2f00, 0x1000)
은 false를 반환한다고 한다.
메모리 정렬(alignment)은 시스템의 성능과 안정성을 위해 필요하다.
- 하드웨어 접근 효율성: CPU가 메모리에 접근할 때 정렬된 주소에 더 효율적으로 접근할 수 있다.
- 메모리 접근 오류 방지: 일부 CPU 아키텍처에서는 정렬되지 않은 메모리 접근 시 오류가 발생할 수 있음.
- 운영체제 메모리 관리: 페이지 단위의 메모리 관리를 위해 주소 정렬이 필수적.
순수 C 코드로 구현할 수도 있지만 교제에서는 Clang의 확장 기능인 __builtin_
함수를 사용함.
나. 메모리 조작 함수
common.c
에 구현한다.
1) memcpy
void *memcpy(void *dst, const void *src, size_t n) {
uint8_t *d = (uint8_t *) dst;
const uint8_t *s = (const uint8_t *) src;
while (n--)
*d++ = *s++;
return dst;
}
src
에서 dst
로 n
바이트를 복사.
uint8_t*
로 캐스팅하는 이유는 1바이트 단위로 메모리에 접근하기 위해서다.
uint32_t
와 같이 4바이트 큰 단위를 사용하면 메모리 경계를 넘어설 수 있고, 정렬되지 않은 접근으로 인한 문제가 발생할 수 있습니다.
추가적으로, 1바이트 단위로 접근하면 문자열이나 네트워크 패킷 처리 시 바이트 단위 조작이 용이하다.
문자열은 기본적으로 1바이트 단위로 처리되며, 네트워크 패킷도 바이트 스트림 형태로 전송되기 때문이다.
4바이트를 단위로 설정하면 1바이트를 위해서 3바이트가 낭비될 수 있다.
따라서 일반적인 32비트 시스템에서는…
- 주소 버스가 32비트이므로 2^32개의 고유한 메모리 주소를 가질 수 있다.
- 각 주소는 1바이트(8비트)를 나타내므로, 최대 메모리 용량은 2^32 bytes = 4,294,967,296 bytes = 4GB가 된다!!! 오오옷!!
이것이 32비트 운영체제가 일반적으로 4GB의 물리적 메모리만 직접 주소 지정할 수 있는 이유다.
4GB 메모리는 부족하기 때문에 64비트 운영체제를 사용하고 이는 최대 16 엑사바이트(EB), 즉 16,384,000GB의 메모리를 이론적으로 주소 지정할 수 있다.
2) memset
void *memset(void *buf, char c, size_t n) {
uint8_t *p = (uint8_t *) buf;
while (n--)
*p++ = c;
return buf;
}
buf
의 시작 위치부터 n
바이트를 문자 c
로 채운다.
4장 Boot chapter에서 BSS 섹션 초기화 용도로 kernel.c
에 구현한 코드를 common.c
로 옮긴다.
다. 문자열 조작 함수
1) strcpy
char *strcpy(char *dst, const char *src) {
char *d = dst;
while (*src)
*d++ = *src++;
*d = '\0';
return dst;
}
src
문자열을 dst
로 복사.
원본 포인터 dst
를 직접 수정하는 대신 임시 포인터 d
를 사용하는 이유.
- 원본 포인터 dst의 시작 주소 보존하기 위해서. 함수가 끝날 때 반환해야 한다.
- 코드 안정성: 포인터 연산 중에 발생할 수 있는 실수나 부작용을 방지.
2) strcmp
Condition | Return |
s1 == s2 | 0 |
s1 > s2 | 양수 |
s1 < s2 | 음수 |
int strcmp(const char *s1, const char *s2) {
while (*s1 && *s2) {
if (*s1 != *s2)
break;
s1++;
s2++;
}
return *(unsigned char *)s1 - *(unsigned char *)s2;
}
'CS > OS' 카테고리의 다른 글
[1000줄 OS 구현하기] 예외 (0) | 2025.02.05 |
---|---|
[1000줄 OS 구현하기] 커널 패닉 (0) | 2025.02.03 |
[1000줄 OS 구현하기] Hello World! (1) | 2025.02.03 |
[1000줄 OS 구현하기] Boot (0) | 2025.01.21 |
[1000줄 OS 구현하기] RISC-V Assembly (0) | 2025.01.17 |