Fender Mustang MG-69 MH CAR

Hobby 2017. 12. 27. 20:11


(아즈냥)

 Mustang은 일렉기타의 대표적인 브랜드 중 하나인 펜더에서 생산한 모델입니다.

싱싱 픽업인데다 원 볼륨 원 톤의 단순한 조합을 갖고 있습니다. 더 자세한 정보는 위키 참조...

 일렉기타를 독학으로 시작하고 나서 꼭 사고 싶었던 모델이었는데 뮬 장터에 민트급 제품이 올라와서 월급날만을 목빠지게 기다렸다가 받자마자 바로 달려가서 구매하였습니다. 사실 산지는 몇주 되었으나 사소한 일로라도 블로깅을 자주 하자는 생각을 하게 되어서 이렇게 올리게 되네요.



 헤드에는 펜더 로고와 함께 MUSTANG이 씌여 있습니다.

Matching Head 모델이라 바디와 똑같은 색이 칠해져 있는데 아주 예쁩니다.

사실 MG-69 CAR MH 모델은 단종되어 구하기 힘들 것이라고 생각했기 때문에 꼭 MH가 아니더라도 머스탱이기만 하면 구하려고 했습니다만, 운 좋게도 뮬에 바로 올라와서 이렇게 구하게 되어 정말 기쁩니다.



 위에서 언급했던 것처럼 싱싱 픽업에 볼륨 노브 하나, 톤 노브 하나로 아주 심플한 구성입니다.

위의 버튼은 서킷 셀렉터라고 하는 건데 아직은 그 용도를 잘 모르겠으나 둘 모두 가운데에 두면 픽업이 Off되는 듯 합니다.

색상은 Candy Apple Red이고 픽가드에는 화이트펄이 들어가 있습니다. 사진으로는 잘 표현되지 않지만 바디에 빛을 비추면 반짝반짝 빛나는게 정말 예쁩니다.


원래 싱글픽업의 까랑거리는 톤을 좋아하기도 했고,

카게프로 곡들도 대체로 그런 톤을 이용하여 커버하는 것이 어울릴 것 같아서 마음이 기울기도 했고,

케이온과 아즈냥에 대한 덕심도 크게 한 몫 했고,

단종되어 더이상 구할 수 없다는 희귀성에 대한 메리트도 있어서 지르게 되었네요.


 실제로 연주해본 소감은 '확실히 짧다' 였습니다.

머스탱을 쓰기 전까지는 평범한 스트랫만 가지고 연습했었는데 머스탱은 스케일이 확실히 스트랫보다 짧아서 저-고음간 점프도 쉽고 빠르게 가능했습니다. 넥감은 아직 잘 모르겠으나 상당히 부드럽고 만족스럽고, 확실히 스트랫에 비해 서스테인은 짧은 게 느껴집니다. 흔히들 말하는 쨉쨉이 연습하기에 좋아보이네요.

사실 아직 기타에 대해 많이 아는것도 아니고 멀티이펙터 하나도 구비하지 못한 채 방구석에서 헤드폰 앰프 하나가지고 들으니 큰 평가나 소감은 내놓기 어렵습니다만 저는 대만족하고 있습니다.

싱글픽업 기타는 이렇게 하나 구했으니 험버커 픽업을 메인으로 한 기타도 한번 구해봐야 하는데, 역시 깁슨이려나요 (;;;)

아마 새 기타를 사는 것은 먼 미래가 될 것 같지만 벌써부터 기대가 됩니다 xD

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Windows에서 python-magic 사용하기


리눅스 환경에서는 python-magic을 사용하기 위한 Dependency가 이미 충족되어 있기 때문에, 혹은 쉽게 설치가 가능하기 때문에 python-magic을 사용하는 데 큰 문제가 없지만, 윈도우에서는 그런 게 없기 때문에 몇 가지 절차를 거친 후에야 python-magic을 사용할 수 있다.


python-magic Github : https://github.com/ahupp/python-magic


 python-magic의 Github에 씌여 있는 설명대로 진행하면 윈도우에서도 python-magic을 문제없이 사용할 수 있다.

우선 magic의 사용에 필요한 라이브러리를 준비해야 한다.

magic1.dll, regex2.dll, zlib1.dll의 세 가지인데, 이는 각각 다음 주소에서 다운받을 수 있다.


magic1.dll : http://gnuwin32.sourceforge.net/packages/file.htm

regex2.dll : http://gnuwin32.sourceforge.net/packages/regex.htm

zlib1.dll  : http://gnuwin32.sourceforge.net/packages/zlib.htm


 굳이 Complete package를 다운받을 필요 없이 Binaries만 다운받아서 압축을 풀고, bin 폴더 안으로 들어가면 필요한 dll을 확인할 수 있다. 이 세개의 dll 파일을 다운받고, DLL이 들어 있는 폴더의 경로를 환경변수 PATH에 추가하던지 환경변수 PATH에 있는 폴더에 DLL을 복사하던지 둘 중 편한 방식을 사용하면 된다. 필자의 경우 후자가 편하기 때문에 PATH 내에 들어 있는 C:\Python27 내부에 라이브러리를 복사해 두었다.

 다음으로 위에서 magic1.dll을 위해 다운받아서 압축을 푼 폴더 기준으로 share\misc\ 에 있는 magic과 magic.mgc 파일을 원하는 위치에 두고 사용하면 된다.

 python-magic은 magic.Magic 클래스의 인스턴스를 생성하여 이 인스턴스의 메서드를 호출하여 파일의 타입을 판별하도록 해야 한다. 사용법 자체는 Github에도 있고 구글링해도 쉽게 나오므로 따로 작성하지는 않겠다.


** magic 파일을 환경변수 PATH에 지정된 폴더에 넣고 사용할 경우 다음과 같은 에러가 발생할 수 있다.

파이썬에서 import magic을 할 경우 다음과 같은 에러가 발생한다.

에러가 발생한 C:\Python27\lib\site-packages\magic.py를 열고 확인해 보면 다음과 같은 부분이 문제이다.

이 소스대로라면 리눅스의 magic 라이브러리를 먼저 탐색하기 때문에 먼저 'magic'을 찾고, 찾지 못했을 경우에 'magic1'을 찾는다. 우리가 다운로드 받은 magic 라이브러리는 magic1.dll 이기 때문에 magic을 먼저 찾는 과정에서 magic 바이너리가 발견되었고, 이것을 그대로 로드하려다가 문제가 발생한 것이다.

 따라서 ctypes.util.find_library('magic') 부분을 뒤로 보내서 먼저 'magic1'을 찾게 하던지, 소스 자체를 삭제하던지 편할대로 하면 된다. 필자는 확실하게 하기 위해 아예 삭제해버렸고, 그 소스는 다음과 같이 변한다.

이제 이를 저장하고 위에 적힌 대로 사용하면 된다.


** 64비트 파이썬을 사용하는 경우에는 magic1.dll 대신 64비트로 빌드된 libmagic 라이브러리를 사용해야 한다.

이는 다음 링크에서 다운로드 받을 수 있다.

64-bit libmagic: https://github.com/pidydx/libmagicwin64


블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

C# 공부 시작

잡담 2017. 9. 26. 22:08

C++ Qt나 Python의 PyQt, Tkinter등을 이용해 GUI 프로그래밍을 몇 번 해본 적이 있는데, 상당히 불편하기도 하고 파이썬의 경우 배포가 너무 힘들어서 GUI도 쉽게 하고 범용적으로 쓰기 편하면서도 파이썬만큼 생산성 있는 C#을 공부해보기로 했다.

처음엔 초급 강의같은거 보면서 따라가보려고 했는데 보다보니 초보자를 위한 강의다 보니 너무 답답해서 늘 그랬듯이 적당한 프로젝트를 하나 정해서 검색해가며 짜보기로 했다.

아는 굇수 동생한테 듣자하니 Nuget이라는 것을 이용하면 정말 많은 라이브러리를 사용할 수 있다고 하던데, 기억상 C++ REST SDK인 casablanca 설치때 써봤던 것 같기도 하다...


요즘 너무 게을러진 것 같아서 어서 마음 다잡고 다시 공부를 시작해야겠다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,


DLL_Injector.cpp

NotepadHook.cpp

 전에 올렸던것과 같지만 다시 후킹을 공부할겸 복습용으로 코드를 좀더 안정적으로 바꾸었다. 코딩 스타일을 통일하고 에러가 날만한 부분에 빠짐없이 리턴값을 확인하여 로깅한 것 뿐이지만 확실히 더 심리적으로 안정감이 느껴진다. 실무에서 로깅을 제대로 안해서 버그 잡기 힘들었던 것을 떠올리면 로깅은 제대로 해야한다는 생각이 들더라.

 두 번째 인자로 들어가는 DLL 파일 경로는 notepad.exe와 DLL파일이 같은 경로 내에 있거나 DLL파일이 환경변수 'PATH'에 등록되어있지 않다면 절대 경로를 써 주어야 정상 작동한다. 이유는 후킹 방식이 DLL 인젝션으로 notepad.exe 프로세스에 Thread를 만들어 돌리는 것이라 실행 경로도 notepad.exe 파일이 놓인 곳일 것이기 때문이라고 추론하고 있다. 혹시나 다른 이유라면 댓글로 알려주시길 바란다.


## 2019-06-17 추가 : 이 포스트의 소스코드는 WriteFile함수가 KernelBase.dll로 구현이 옮겨진 후의 환경에서만 동작합니다. (참고 : https://sanseolab.tistory.com/17)


DLL_Injector.cpp

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <Shlwapi.h>
 
DWORD findPID(LPCTSTR szProcessName);
BOOL injectDLL(DWORD dwPID, LPCTSTR szDLLName);
 
int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("[*] Usage : %s [Target] [DLL]\n", argv[0]);
        return 1;
    }
 
    if (!PathFileExists(argv[2])) {
        printf("[-] DLL Not Exists : %s\n", argv[2]);
        return 1;
    }
 
    DWORD pid = findPID(argv[1]);
    if (pid == 0xFFFFFFFF) {
        printf("[-] Process not found\n");
        return 1;
    }
    else {
        printf("[*] pid : %u\n", pid);
    }
    if (!injectDLL(pid, argv[2])) {
        printf("[-] Injection Failed\n");
        return 1;
    }
    else {
        printf("[*] Injection Successed\n");
    }
 
    return 0;
}
 
DWORD findPID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapshot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe;
 
    pe.dwSize = sizeof(PROCESSENTRY32);
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        printf("[*] CreateToolhelp32Snapshot Error\n");
        return 0xFFFFFFFF;
    }
 
    Process32First(hSnapshot, &pe);
    do {
        if (!_stricmp(szProcessName, pe.szExeFile)) {
            dwPID = pe.th32ProcessID;
            break;
        }
    } while (Process32Next(hSnapshot, &pe));
 
    CloseHandle(hSnapshot);
    return dwPID;
}
 
BOOL injectDLL(DWORD dwPID, LPCTSTR szDLLName)
{
    HANDLE hProcess, hThread;
    HMODULE hMod;
 
    LPVOID pRemoteBuf;
    DWORD dwBufSize = lstrlen(szDLLName) + 1;
    LPTHREAD_START_ROUTINE pThreadProc;
 
    // Get target process handle
    if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) == INVALID_HANDLE_VALUE) {
        printf("[-] OpenProcess Error\n");
        printf("[-] gle : 0x%x\n", GetLastError());
        return FALSE;
    }
 
    // Allocate memory to target process
    if ((pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE)) == INVALID_HANDLE_VALUE) {
        printf("[-] VirtualAllocEx Error\n");
        printf("[-] gle : 0x%x\n", GetLastError());
        return FALSE;
    }
    
    // Write DLL name to target process memory
    if (WriteProcessMemory(hProcess, pRemoteBuf, szDLLName, dwBufSize, NULL== FALSE) {
        printf("[-] WriteProcessMemory Error\n");
        printf("[-] gle : 0x%x\n", GetLastError());
        return FALSE;
    }
    
    // Get handle of "kernel32.dll"
    if ((hMod = GetModuleHandle("kernel32.dll")) == INVALID_HANDLE_VALUE) {
        printf("[-] GetModuleHandle Error\n");
        printf("[-] gle : 0x%x\n", GetLastError());
        return FALSE;
    }
 
    // Get address of "LoadLibraryA"
    if ((pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA")) == INVALID_HANDLE_VALUE) {
        printf("[-] GetProcAddress Error\n");
        printf("[-] gle : 0x%x\n", GetLastError());
        return FALSE;
    }
 
    // Create and run remote thread in target process
    if ((hThread = CreateRemoteThread(hProcess, NULL0, pThreadProc, pRemoteBuf, 0NULL)) == INVALID_HANDLE_VALUE) {
        printf("[-] CreateRemoteThread Error\n");
        printf("[-] gle : 0x%x\n", GetLastError());
        return FALSE;
    }
 
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    return TRUE;
}
cs


NotepadHook.cpp

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <stdio.h>
#include <Windows.h>
 
#pragma pack(1)
struct IAT_STRUCT
{
    SHORT Opcode;
    LPVOID lpTarget;
};
 
typedef BOOL WINAPI tWriteFile(
    _In_        HANDLE       hFile,
    _In_        LPCVOID      lpBuffer,
    _In_        DWORD        nNumberOfBytesToWrite,
    _Out_opt_   LPDWORD      lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
);
 
tWriteFile* prevFunction;
tWriteFile* newFunction;
 
BOOL WINAPI NewWriteFile(
    _In_        HANDLE       hFile,
    _In_        LPCVOID      lpBuffer,
    _In_        DWORD        nNumberOfBytesToWrite,
    _Out_opt_   LPDWORD      lpNumberOfBytesWritten,
    _Inout_opt_ LPOVERLAPPED lpOverlapped
)
{
    if (nNumberOfBytesToWrite > 0)
        MessageBoxA(NULL, (LPCSTR)lpBuffer, NULLNULL);
    return prevFunction(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
}
 
DWORD WINAPI tryHook()
{
    // Get address of target function
    LPVOID lpOrgFunc = NULL;
    if ((lpOrgFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile")) == NULL)
        return -1;
 
    // Backup old protect
    DWORD dwOldProtect;
    if (VirtualProtect(lpOrgFunc, 6, PAGE_EXECUTE_READWRITE, &dwOldProtect) == NULL)
        return -1;
 
    // Backup old function IAT
    IAT_STRUCT*  lpSavedFunc = (IAT_STRUCT*)VirtualAlloc(NULLsizeof(IAT_STRUCT), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    IAT_STRUCT   newFuncObj;
    memcpy_s(lpSavedFunc, 6, lpOrgFunc, 6);
    prevFunction = (tWriteFile*)lpSavedFunc;
 
    // Absolute Jump
    newFuncObj.Opcode = 0x25FF;
 
    // Set new functon to replace
    newFunction = &NewWriteFile;
    newFuncObj.lpTarget = &newFunction;
 
    // Replacing
    memcpy_s(lpOrgFunc, 6&newFuncObj, 6);
 
    // Rollback protection
    VirtualProtect(lpOrgFunc, 6, dwOldProtect, NULL);
    return 0;
}
 
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    HANDLE hThread;
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        tryHook();
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}
cs


블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

리눅스 커널 모듈 프로그래밍 - mychardev


이전 글에서 올렸던 이 문서 이후 예제를 이용하여 아주 간단한 Character Device Driver를 작성했다. 사실상 거의 코드를 가져다 썼지만 내용 이해를 위해 printk를 몇번 하여 좀더 보기 편해졌다.




결과 화면은 다음과 같다.



블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

리눅스 커널 모듈 프로그래밍 - Hello World


 리눅스 커널에는 모듈의 개념이 있어 커널을 통째로 컴파일하고 Reboot하지 않고도 커널의 기능을 확장할 수 있다. 리눅스 커널에 대해 책만 보고 공부하다가 도저히 안 될것 같아 일단 간단하게나마 리눅스 커널 모듈을 만들고 마운트하는 실습을 해 보았다. 실습에 사용한 소스와 내용 모두 이 문서를 이용하였다.


 예제는 프로그래밍을 처음 배우는 사람이라면 누구나 한번쯤은 해보는 "Hello World" 출력이다. 단, 커널은 로드와 언로드 두가지 이벤트에서 출력이 가능하므로 "Goodbye World"도 출력하도록 하겠다.

 소스코드는 다음과 같이 매우 간단하다.


1
2
3
4
5
6
7
8
9
10
11
12
#include <linux/module.h>
#include <linux/kernel.h>
 
int init_module() {
    printk(KERN_INFO "Hello world!\n");
    return 0;
}
 
 
void cleanup_module() {
    printk(KERN_INFO "Goodbye world!\n");
}
cs


 커널 모듈은 적어도 모듈이 커널에 로딩될 때 호출되는 init_module()과 모듈이 커널에서 언로딩될 때 호출되는 cleanup_module() 두 가지 함수를 가져야 한다. 커널 버전 2.3.13 이후로는 함수명에 관계없이 시작/종료함수를 지정할 수 있지만 여기서는 편의상 그냥 사용한다.


 C언어를 배웠다면 출력함수로 printf가 익숙하겠지만 커널 모듈에서는 printk를 사용한다. printk 함수에는 우선순위로 8가지의 속성을 입력할 수 있는데, 이는 <linux/kernel.h> 내부에 정의되어 있으므로 가져다 쓰면 된다. 참고로 8가지의 속성은 다음과 같다.


33
34
35
36
37
38
39
40
#define    KERN_EMERG    "<0>"    /* system is unusable            */
#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define    KERN_CRIT    "<2>"    /* critical conditions            */
#define    KERN_ERR    "<3>"    /* error conditions            */
#define    KERN_WARNING    "<4>"    /* warning conditions            */
#define    KERN_NOTICE    "<5>"    /* normal but significant condition    */
#define    KERN_INFO    "<6>"    /* informational            */
#define    KERN_DEBUG    "<7>"    /* debug-level messages            */
cs


 커널 모듈을 컴파일할때는 make 명령과 Makefile을 이용한다. Makefile 문법을 여기 적긴 뭐하고 다음 코드를 Makefile 이라는 이름의 파일에 작성하고 저장하면 된다. Makefile은 make로 컴파일을 진행하기 위해 사용하는 명세라고 보면 편하다. all과 clean 부분은 사실 필요없지만 편의를 위해 작성했다고 한다.


 전부 작성했으면 해당 디렉토리 내에서 make 명령을 실행해 보자. 결과가 다음과 같이 나온다면 성공이다.



 이제 디렉토리에는 HelloWorld.ko 파일이 만들어졌을 것이다. 이것이 커널 모듈 파일이며, 이는 insmod 명령을 이용해 커널에 로드할 수 있다. 또한 rmmod 명령을 이용해 커널에서 제거도 가능하다.


1
2
insmod HelloWorld.ko
rmmod HelloWorld
cs


위의 두 명령을 수행해 보고, /var/log/kern.log 파일을 열어서 확인해 보면 다음과 같이 가장 밑에 코드에 적어둔 "Hello World"와 "Goodbye World"가 적혀 있는 것을 볼 수 있을것이다.



 이걸로 간단히 커널 모듈을 컴파일하고 로드/언로드하는 실습을 해보았다.

나중에 공부하면서 다른 모듈도 좀 더 작성해봐야겠다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

둘 모두 자원에 대해 락을 걸고 사용하려고 할 시에 락이 풀릴 때까지 기다려야 한다는 점은 같지만, 둘은 내부적으로 로우레벨에서 차이점이 있다.


 우선 뮤텍스의 경우, 자원에 이미 락이 걸려 있을 경우 락이 풀릴 때까지 기다리며 컨텍스트 스위칭을 실행한다. 

즉, 다른 병렬적인 태스크를 처리하기 위해 CPU를 양보할 수 있다는 것이며 이는 자원을 얻기 위해 오랜 시간을 기다려야 할 것이 예상될 때 다른 작업을 동시에 진행할 수 있다는 것이다. 하지만 이는 자원이 단시간 내로 얻을 수 있게 될 경우 컨텍스트 스위칭에 더 큰 자원을 낭비하게 될 수 있다는 문제가 있다.

 스핀 락의 경우에는 이름에서부터 알 수 있듯이, 자원에 락이 걸려 있을 경우 이를 얻을 때까지 무한 루프를 돌면서 다른 태스크에 CPU를 양보하지 않는 것이다. 자원이 단시간 내로 얻을 수 있게 된다면 컨텍스트 스위칭 비용이 들지 않으므로 효율을 높일 수 있지만, 그 반대의 경우 다른 태스크에 CPU를 양보하지 않으므로 오히려 CPU 효율을 떨어뜨릴 수 있는 문제가 있다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

한빛미디어의 '리눅스 커널의 이해'를 읽고 내용을 정리하는 글입니다. **


- 가상 파일 시스템의 역할

 '가상 파일 시스템(VFS, Virtual File System)'은 표준 유닉스 파일 시스템이 제공하는 모든 시스템 콜을 처리하는 커널 소프트웨어 계층이다. VFS의 장점은 여러 종류의 파일 시스템에 대해 공통 인터페이스를 제공한다는 것이다. 예를 들어, cp 명령을 통해 MS-DOS 디스켓의 파일을 일반 Ext2(2차 확장 파일 시스템) 디렉토리에 복사한다고 해도 cp 프로그램은 복사 대상과 복사 목적지의 파일 시스템 유형을 알 필요가 없다. 대신 cp는 일반 시스템 콜(open, read, write 등)을 통해 VFS와 상호 작용한다.

VFS가 지원하는 파일 시스템은 크게 다음과 같이 세 부류로 나눌 수 있다.

1. 디스크 기반 파일 시스템

로컬 디스크 파티션의 기억 장소 또는 디스크를 흉내내는 몇 가지 다른 장치(USB 등)를 관리한다. VFS가 지원하는 잘 알려진 디스크 기반 파일 시스템은 다음과 같다.

- 널리 사용되는 Ext2, 최신 Ext3, 또한 ReiserFS같은 리눅스의 독자적인 파일 시스템

- SYSV(시스템 V, Coherent, XENIX)와 UFS(BSD, Solaris, NEXTSTEP), MINIX 파일 시스템, 베리타스 VxFS(SCO 유닉스웨어) 등 다른 유닉스를 위한 파일 시스템

- MS-DOS, VFAT, NTFS 같은 마이크로소프트 파일 시스템

- ISO9660 CD-ROM 파일 시스템, UDF DVD 파일 시스템

- IBM OS/2(HPFS), 애플 매킨토시(HFS), 아미가 패스트 파일 시스템(AFFS), 아콘 디스크 파일 시스템(ADFS) 등 기타 전용 파일 시스템

- IBM의 JFS, SGI의 XFS 등 리눅스 이외의 시스템에서 유래한 추가적인 저널링 파일 시스템


2. 네트워크 파일 시스템

 네트워크로 연결된 다른 컴퓨터의 파일에 쉽게 접근하게 해준다. VFS가 지원하는 잘 알려진 네트워크 파일 시스템으로는 NFS, Coda, AFS, CIFS, SMB, NCP 등이 있다. (책에는 없었지만 GlusterFS도 있다)


3. 특수 파일 시스템(가상 파일 시스템이라고도 함)

 이 파일 시스템은 자신의 컴퓨터나 다른 컴퓨터의 실제 디스크 공간을 관리하지 않는다. /proc이 대표적인 특수 파일 시스템이다.


- 공통 파일 모델 

 VFS의 핵심 개념은 지원하는 모든 파일 시스템을 표현할 수 있는 '공통 파일 모델(Common File Model)'을 도입하는 것이다. 이 모델은 전통적인 유닉스 파일 시스템이 제공하는 파일 모델을 충실히 따른다. 

 공통 파일 모델에서는 각 디렉토리를 파일의 목록과 다른 디렉토리들을 포함하는 파일처럼 생각한다. 하지만 몇몇 비 유닉스 계열의 디스크 기반 파일 시스템은 디렉토리 트리에서 각 파일의 위치를 저장한 파일 할당 테이블(FAT)을 사용한다. 이런 파일 시스템에서는 디렉토리를 파일로 간주하지 않는다. FAT 파일 시스템을 리눅스에서 구현하기 위해서는 VFS의 공통 파일 모델을 따르도록 하기 위해 실행 중에 디렉토리에 대응하는 파일을 생성할 수 있어야 한다. 이 파일은 커널 메모리 객체로만 존재한다.

 본래 리눅스 커널은 read()나 ioctl() 작업을 처리하는 특정 함수를 직접 구현할 수 없고, 대신 각 작업에 대해 포인터를 사용해야 한다. 포인터는 접근할 특정 파일 시스템을 위한 적절한 함수를 가리키게 한다. 파일은 커널 메모리에서 file 자료 구조로 표현되는데, 이 자료 구조에는 f_op라는 필드가 있어서 각 파일 시스템을 처리하는 함수에 대한 포인터를 포함한다. 함수 중에는 파일을 읽는 함수도 있으며, read()함수를 실행했을 때 커널 내부에서 sys_read()는 이 함수에 대한 포인터를 찾아서 호출한다. 따라서 다음과 같이 간접적으로 호출하게 되는 것이다.

file->f_op->read(...);

 이와 비슷하게 write()연산은 출력 파일에 대응하는 적당한 쓰기 함수를 실행하도록 한다. 간단히 말해서 커널은 각 열린 파일에 대해 file 변수에 알맞은 함수 포인터를 할당하고, f_op 필드가 가리키는 각 파일 시스템별 호출을 실행해야 한다.


공통 파일 모델은 다음과 같은 객체 유형으로 이루어진다.

- 슈퍼 블록 객체

 마운트 된 파일 시스템에 대한 정보를 저장한다. 디스크 기반 파일 시스템의 경우, 이 객체는 일반적으로 디스크에 저장한 파일 시스템 제어 블록(File System Control Block)에 대응한다.


- 아이노드 객체

 특정 파일에 대한 일반 정보를 저장한다. 디스크 기반 파일 시스템의 경우, 이 객체는 일반적으로 디스크에 저장한 파일 제어 블록(File Control Block)에 대응한다.


- 디엔트리 객체

 디렉토리 항목(즉 특정 파일 이름)과 이에 대응하는 파일의 연결에 대한 정보를 제공한다.


 프로세스 3개가 같은 파일 하나에 접근하며, 이 중 두 프로세스는 동일한 하드 링크를 사용하여 접근한다고 하면 세 프로세스 모두 각각의 파일 객체를 소유하지만 디엔트리 객체는 하드 링크 하나당 하나만 필요하기 때문에 두 프로세스는 같은 디엔트리 객체에 접근하게 된다. VFS는 모든 파일 시스템에 대한 공통 인터페이스를 제공하는 일 외에도 가장 최근에 사용한 디엔트리 객체를 디엔트리 캐시(Dentry Cache)라는 디스크 캐시에 저장함으로 파일 경로명을 경로의 마지막 구성 요소인 파일 아이노드로 변환하는 속도를 높인다.


( 실제 파일 블록당 아이노드 객체는 하나씩 존재함. 디엔트리 객체는 하드링크 하나당 하나씩 존재하며 같은 파일을 가리키는 하드링크는 같은 아이노드 객체를 참조하여 실제 파일에 접근함. )

(디엔트리 객체는 ext 구조에서는 디스크에 저장된다고 하는데 다른 파일 시스템에서는 커널 메모리 영역에 저장된다고 함. 하드 링크는 같은 파일 시스템에 대해서만 만들 수 있다는게 이것때문인 것 같은데, 만약에 NTFS 파일 시스템에서 같은 NTFS 파일 시스템에 하드 링크를 만들 경우 어떻게 저장되는지는 좀 더 찾아봐야 할 듯 하다.)

'Knowledge' 카테고리의 다른 글

Kali Linux 설치하기  (0) 2018.02.20
Windows에서 python-magic 사용하기  (0) 2017.10.27
리눅스 커널 공부 정리 0x03  (0) 2017.06.27
리눅스 커널 공부 정리 0x02  (1) 2017.06.21
리눅스 커널 공부 정리 0x01  (0) 2017.06.20
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

** 한빛미디어의 '리눅스 커널의 이해'를 읽고 내용을 정리하는 글입니다. **

- 시그널과 프로세스 간 통신

 유닉스 '시그널(Signal)'은 프로세스에 시스템 이벤트를 알려주는 메커니즘을 제공한다. 각 이벤트는 SIGTERM과 같이 기호로 된 상수로 참조하는 자신만의 시그널 번호를 가지고 있으며, 이에는 두 종류가 있다.


- 비동기적 알림(Asynchronous Notification)

 예를 들어, 사용자가 터미널에서 인터럽트 키를 눌러(Ctrl + C) Foreground Process에 SIGINT 인터럽트 시그널을 보낼 수 있다.

- 동기적 에러(Synchronous Error)나 예외(Exception)

 예를 들어, 프로세스가 잘못된 주소에 있는 메모리 위치에 접근하려고 하면 커널은 프로세스에 SIGSEGV 시그널을 보낸다. (BOF 등에서 자주 등장한다.)


 POSIX 표준에서는 20개 가량의 시그널을 정의하고 있으며 이중 2개는 사용자가 정의할 수 있다. 프로세스는 시그널을 받을 때 보통 시그널을 무시하거나, 비동기적으로 특별한 절차(시그널 핸들러)를 실행하는 두 가지 반응을 보인다.

(POSIX 시그널 : http://www.comptechdoc.org/os/linux/programming/linux_pgsignals.html)


 프로세스가 이 중 하나를 지정하지 않으면 커널은 시그널 번호에 따라 정해진 기본 동작을 수행한다. 기본 동작은 다음의 다섯 가지이다.

- 프로세스를 종료한다

- 실행 컨텍스트와 주소 공간의 내용을 파일에 기록하고(Core dump) 프로세스를 종료한다. (BOF시 자주 본다)

- 시그널을 무시한다

- 프로세스를 보류한다

- 프로세스가 중단된 상태라면 프로세스 실행을 재개한다

SIGKILL과 SIGSTOP 시그널은 프로세스가 직접 처리할 수 없으며, 무시할 수도 없다.


- 프로세스 관리

 유닉스에서는 프로세스와 프로세스가 실행하는 프로그램을 명확하게 구별한다. 새로운 프로세스를 생성하고 종료하는 데는 각각 fork()와 _exit() 시스템 콜을 사용하지만, 새로운 프로그램을 로드할 때는 exec()계열의 시스템 콜을 사용한다.


 fork()를 호출하는 프로세스는 '부모(Parent)', 새로운 프로세스는 '자식(Child)'이 된다. 프로세스를 기술하는 자료 구조에는 친부모와 모든 친자식들을 가리키는 포인터가 있어 부모와 자식은 서로를 알 수 있다.  fork() 함수를 단순하게 구현하면 부모의 코드 및 데이터를 전부 자식에게 복사하면 되지만 이럴 경우 시간이 많이 걸린다. 그래서 최신 커널은 페이징 유닛(Paging Unit)기능을 활용하여 'Copy On Write'한다. 이는 페이지 복사를 최후의 순간까지 미루는 것이다.

 _exit() 시스템 콜은 프로세스를 종료한다. 커널은 프로세스가 점유하고 있는 자원을 반납하고 부모 프로세스에 SIGCHLD 시그널을 보내는데, 기본적으로 부모는 이 시그널을 무시한다.


- 좀비 프로세스

 부모 프로세스는 wait4() 시스템 콜을 호출하여 자식 프로세스 중 하나가 종료할 때까지 기다릴 수 있다. 이 시스템 콜은 종료한 자식의 PID(Process ID)를 돌려준다. 이 시스템 콜을 호출하면 커널은 이미 종료한 자식이 있는지 검사하는데, wait4() 시스템 콜을 호출하기 전까지 종료한 프로세스는 '좀비 프로세스(Zombie Process)' 상태로 남는다. wait4() 시스템 콜을 호출할 때, 그 전에 종료한 자식 프로세스가 없으면 커널은 보통 자식 프로세스가 종료할 때까지 부모 프로세스를 대기 상태로 만든다.

 만약 부모 프로세스가 wait4() 시스템 콜을 호출하지 않고 종료한다면, 커널은 좀비 프로세스로 남아 있는 자식 프로세스를 'init'이라는 시스템 프로세스의 자식으로 만든다. init 프로세스는 모든 자식의 실행 상태를 지켜보고 정기적으로 wait4() 시스템 콜을 호출하여 모든 좀비 프로세스를 제거하는 효과를 갖는다.


- 가상 메모리

 모든 최신 유닉스 시스템은 '가상 메모리(Virtual Memory)'라는 유용한 추상화 개념을 제공한다. 가상 메모리를 사용하는 데는 다양한 목적과 장점이 있다.

- 여러 프로세스를 동시에 실행할 수 있다.

- 사용할 수 있는 물리 메모리보다 많은 메모리를 필요로 하는 애플리케이션을 실행할 수 있다.

- 프로그램 코드 중 일부만 메모리에 로드해도 프로세스를 실행할 수 있다.

- 각 프로세스는 사용 가능한 물리 메모리의 일부에만 접근할 수 있다.

- 라이브러리나 프로그램의 메모리 이미지 하나를 프로세스 사이에서 공유할 수 있다.

- 프로그램을 재배치(Relocation)할 수 있다.

- 프로그래머는 물리 메모리의 구조에 신경 쓸 필요가 없으므로 편하게 코드를 작성할 수 있다.

 프로세스가 가상 주소를 사용하면 커널과 MMU(Memory Management Unit)가 서로 협력하여 요구한 메모리 주소의 실제 물리적 위치를 찾는다. 현재의 CPU에는 자동으로 가상 주소를 물리 주소로 변환하는 하드웨어 회로가 있다. 이에 따라 사용 가능한 램을 일반적으로 4KB나 8KB단위의 '페이지 프레임(Page Frame)'으로 쪼개고 페이지 테이블(Page Table)을 통해 가상 주소와 물리 주소 사이의 대응 관계를 지정한다. 이 회로를 사용하면 연속된 가상 주소에 메모리 블록을 할당하려는 요청을 실제 물리 주소에서는 연속되지 않는 페이지 프레임 그룹에 할당하여 처리할 수 있어 메모리 할당이 간단해진다.


- 램 사용

 모든 유닉스 운영체제는 RAM(Random Access Memory)을 두 부분으로 나누어 구분한다.

 몇 메가바이트는 커널 이미지(커널 코드 및 커널의 정적 자료 구조)를 저장하는 데  사용한다. 나머지는 보통 가상 메모리 시스템이 다루는 부분으로, 다음의 세 가지 용도로 사용한다.

- 커널에서 필요로 하는 버퍼와 디스크립터, 다른 동적으로 만들어지는 커널 자료 구조용

- 프로세스가 필요로 하는 일반적인 메모리 영역과 파일 메모리 매핑용

- 디스크나 버퍼를 통하는 다른 장치로부터 더 나은 성능을 얻기 위한 캐시용

 사용 가능한 램은 제한되어 있기 때문에 위의 세 가지 요구 사이에 균형을 맞추는 것은 매우 중요하다. 


- 커널 메모리 할당자

 '커널 메모리 할당자(KMA, Kernel Memory Allocator)'는 시스템의 모든 부분에서 오는 메모리 영역의 관련 요청을 처리하는 서브시스템이다. 이 요청 중 일부는 커널에서 사용할 메모리를 위한 것이고, 다른 일부는 사용자 프로그램이 자신의 프로세스 주소 공간을 늘리기 위해 시스템 콜을 통해 호출한 것이다. 좋은 커널 메모리 할당자는 다음과 같은 특징을 갖추어야 한다.

- 빨라야 한다.

- 낭비되는 메모리 양을 최소화해야 한다.

- 메모리 단편화 문제를 줄여야 한다.

- 다른 메모리 관리 서브시스템과 협력하여 이들로부터 메모리를 빌려오거나 이들의 메모리를 해지할 수 있어야 한다.


  프로세스 주소 공간에는 프로세스가 접근할 수 있는 모든 가상 메모리 주소가 들어 있다. 커널은 프로세스의 가상 주소 공간을 '메모리 영역 디스크립터(Memory Area Descriptor)'의 리스트로 저장한다. 예를 들어, 프로세스가 exec() 계열의 시스템 콜을 통해 어떤 프로그램을 실행하면 커널은 프로세스에 다음과 같은 메모리 영역으로 구성된 가상 주소 공간을 할당한다.

- 프로그램의 실행 코드

- 프로그램의 초기화된 데이터

- 프로그램의 초기화되지 않은 데이터

- 초기 프로그램 스택(즉 유저 모드 스택)

- 프로그램이 필요로 하는 공유 라이브러리의 실행 코드와 데이터

- 힙(Heap)


- 장치 드라이버

 커널은 '장치 드라이버(Device Driver)'를 통해 입출력 장치와 상호 작용한다. 장치 드라이버는 커널에 들어 있으며 하드 디스크나 키보드, 마우스, 모니터, SCSI 버스에 연결된 장치, 네트워크 카드 같은 장치를 하나 이상 제어하는 자료 구조와 함수로 이루어진다. 각 드라이버는 정해진 인터페이스에 따라 커널의 다른 부분과 상호 작용한다. 이런 접근 방법에는 다음과 같은 이점이 있다.

- 장치에 따라 고유한 코드를 특정 모듈 속으로 넣을 수 있다.

- 제품을 파는 사람은 커널 소스 코드를 모르더라도 인터페이스 명세(Interface Specification)만 알면 새로운 장치를 추가할 수 있다.

- 커널은 모든 장치를 동일한 방법으로 다루고, 동일한 인터페이스로 접근한다.

- 시스템을 재부팅하지 않고도 커널에 동적으로 로드할 수 있는 모듈 형태로 장치 드라이버를 만들 수 있으며 더 이상 필요하지 않은 모듈을 동적으로 언로드하여 커널 이미지가 램에서 차지하는 크기를 줄일 수 있다.


블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,