Python 으로 Windows API 와 상호 작용하는 방법

PythonBeginner
지금 연습하기

소개

이 포괄적인 튜토리얼은 Python 의 ctypes 라이브러리를 사용하여 Windows API 와 상호 작용하는 방법을 안내합니다. Windows 시스템 기능에 액세스하고, 프로세스를 관리하며, Windows 관련 기능을 Python 에서 직접 활용하는 방법을 배우게 됩니다. 이 랩을 마치면 Windows 운영 체제와 원활하게 통합되고 강력한 시스템 수준 작업을 수행할 수 있는 애플리케이션을 구축하는 방법을 이해하게 될 것입니다.

Python 의 ctypes 라이브러리 이해

이 단계에서는 Windows API 와 상호 작용하기 위한 기반 역할을 하는 Python 의 ctypes 라이브러리의 기본 사항을 살펴보겠습니다. ctypes 라이브러리는 Python 과 네이티브 C 라이브러리 간의 브리지 역할을 하며, DLL(Dynamic Link Libraries) 에서 직접 함수를 호출할 수 있도록 합니다.

ctypes 란 무엇인가요?

ctypes는 C 호환 데이터 형식을 제공하고 DLL 또는 공유 라이브러리에서 함수를 호출할 수 있는 Python 용 외부 함수 라이브러리입니다. 다음과 같은 경우에 특히 유용합니다.

  • 저수준 시스템 기능에 액세스
  • 하드웨어와 상호 작용
  • 플랫폼별 라이브러리에서 함수 호출

필요한 패키지 설치

코드를 작성하기 전에 필요한 패키지를 설치해 보겠습니다. WebIDE 에서 터미널을 열고 다음을 실행합니다.

pip install pywin32-ctypes

이렇게 하면 환경에서 작동하는 호환 가능한 라이브러리가 설치됩니다.

ctypes 의 기본 사용법

ctypes가 어떻게 작동하는지 이해하기 위해 간단한 Python 스크립트를 만들어 보겠습니다. WebIDE 에서 /home/labex/project 디렉토리에 ctypes_basics.py라는 새 파일을 만들고 다음 내용을 입력합니다.

import ctypes

## Load a standard C library
libc = ctypes.CDLL('libc.so.6')

## Call a simple C function
print("Random number from C library:", libc.rand())

## Find out the size of int type on this machine
print("Size of int:", ctypes.sizeof(ctypes.c_int), "bytes")

## Create and use a C-compatible string
message = ctypes.create_string_buffer(b"Hello from ctypes!")
print("C-compatible string:", message.value.decode())
print("String buffer size:", ctypes.sizeof(message), "bytes")

다음 명령을 사용하여 스크립트를 실행합니다.

python3 ctypes_basics.py

다음과 유사한 출력이 표시됩니다.

Random number from C library: 1804289383
Size of int: 4 bytes
C-compatible string: Hello from ctypes!
String buffer size: 19 bytes

Python 에서 C 데이터 형식 이해

ctypes는 C 데이터 형식에 대한 Python 호환 래퍼를 제공합니다. 다음은 일반적인 C 데이터 형식과 해당 ctypes의 등가물에 대한 참조 테이블입니다.

C 형식 ctypes 형식 Python 형식
char c_char 1-문자 바이트 객체
int c_int int
unsigned int c_uint int
long c_long int
void * c_void_p int 또는 None
char * c_char_p bytes 또는 None
wchar_t * c_wchar_p str 또는 None

다른 예제를 통해 이러한 데이터 형식을 살펴보겠습니다. ctypes_types.py라는 파일을 만듭니다.

import ctypes

## Integer types
i = ctypes.c_int(42)
ui = ctypes.c_uint(123)
print(f"Integer: {i.value}, Unsigned Integer: {ui.value}")

## Floating point types
f = ctypes.c_float(3.14)
d = ctypes.c_double(2.71828)
print(f"Float: {f.value}, Double: {d.value}")

## Character and string types
c = ctypes.c_char(b'A')
s = ctypes.c_char_p(b"Hello, World!")
print(f"Character: {c.value.decode()}, String: {s.value.decode()}")

## Create an array of integers
int_array = (ctypes.c_int * 5)(1, 2, 3, 4, 5)
print("Array elements:", [int_array[i] for i in range(5)])

스크립트를 실행합니다.

python3 ctypes_types.py

예상 출력:

Integer: 42, Unsigned Integer: 123
Float: 3.140000104904175, Double: 2.71828
Character: A, String: Hello, World!
Array elements: [1, 2, 3, 4, 5]

ctypes에 대한 이러한 기본적인 이해는 더 복잡한 시스템 라이브러리 및 특히 Windows API 와 상호 작용하기 위해 앞으로 나아가는 데 도움이 될 것입니다.

ctypes 를 사용하여 시스템 라이브러리에 액세스

이 단계에서는 ctypes를 사용하여 시스템 라이브러리에 액세스하고 해당 함수를 호출하는 방법을 배우겠습니다. Linux 환경에서 작업하고 있으므로 Windows API 액세스에도 적용되는 원리를 설명하면서 Linux 시스템 라이브러리에 중점을 두겠습니다.

동적 라이브러리 로드

Python 에서는 ctypes에서 제공하는 여러 메서드를 사용하여 동적 라이브러리를 로드할 수 있습니다.

  • CDLL - 표준 C 라이브러리를 로드하기 위한 것
  • WinDLL - Windows DLL 을 로드하기 위한 것 (Windows 에서)
  • OleDLL - COM 라이브러리를 로드하기 위한 것 (Windows 에서)

표준 라이브러리를 사용하여 시스템 정보를 탐색하기 위해 system_info.py라는 파일을 만들어 보겠습니다.

import ctypes
import os
import platform

print(f"Python Platform: {platform.platform()}")
print(f"System: {platform.system()}")
print(f"Machine: {platform.machine()}")
print(f"Processor: {platform.processor()}")

## Load the C standard library
libc = ctypes.CDLL('libc.so.6')

## Get system information
print("\n--- System Information ---")

## Get hostname
hostname_buffer = ctypes.create_string_buffer(1024)
if libc.gethostname(hostname_buffer, ctypes.sizeof(hostname_buffer)) == 0:
    print(f"Hostname: {hostname_buffer.value.decode()}")
else:
    print("Failed to get hostname")

## Get user information
uid = os.getuid()
pwd = libc.getpwuid(uid)
if pwd:
    print(f"Current user ID: {uid}")
else:
    print(f"Current user ID: {uid} (failed to get user info)")

## Memory page size
page_size = libc.getpagesize()
print(f"Memory page size: {page_size} bytes")

## Get system uptime (if available)
try:
    class timespec(ctypes.Structure):
        _fields_ = [
            ('tv_sec', ctypes.c_long),
            ('tv_nsec', ctypes.c_long)
        ]

    time_buf = timespec()
    CLOCK_BOOTTIME = 7  ## Linux specific
    if hasattr(libc, 'clock_gettime') and libc.clock_gettime(CLOCK_BOOTTIME, ctypes.byref(time_buf)) == 0:
        uptime_seconds = time_buf.tv_sec
        days, remainder = divmod(uptime_seconds, 86400)
        hours, remainder = divmod(remainder, 3600)
        minutes, seconds = divmod(remainder, 60)
        print(f"System uptime: {days} days, {hours} hours, {minutes} minutes, {seconds} seconds")
    else:
        print("Could not get system uptime")
except Exception as e:
    print(f"Error getting uptime: {e}")

스크립트를 실행합니다.

python3 system_info.py

다음과 유사한 시스템에 대한 자세한 정보가 표시됩니다.

Python Platform: Linux-5.15.0-1031-aws-x86_64-with-glibc2.35
System: Linux
Machine: x86_64
Processor: x86_64

--- System Information ---
Hostname: labex-container
Current user ID: 1000
Memory page size: 4096 bytes
System uptime: 0 days, 1 hours, 23 minutes, 45 seconds

프로세스 모니터 만들기

이제 더 실용적인 애플리케이션을 만들어 보겠습니다. 실행 중인 프로세스를 나열하는 간단한 프로세스 모니터입니다. process_monitor.py라는 파일을 만듭니다.

import ctypes
import os
import time
from datetime import datetime

def list_processes():
    """List all running processes using /proc filesystem"""
    processes = []

    ## On Linux, process information is available in the /proc filesystem
    for pid in os.listdir('/proc'):
        if pid.isdigit():
            try:
                ## Read process name from /proc/[pid]/comm
                with open(f'/proc/{pid}/comm', 'r') as f:
                    name = f.read().strip()

                ## Get process status
                with open(f'/proc/{pid}/status', 'r') as f:
                    status_lines = f.readlines()
                    status = {}
                    for line in status_lines:
                        if ':' in line:
                            key, value = line.split(':', 1)
                            status[key.strip()] = value.strip()

                ## Get memory usage (VmRSS is physical memory used)
                memory_kb = int(status.get('VmRSS', '0 kB').split()[0]) if 'VmRSS' in status else 0

                processes.append({
                    'pid': int(pid),
                    'name': name,
                    'state': status.get('State', 'Unknown'),
                    'memory_kb': memory_kb
                })
            except (IOError, FileNotFoundError, ProcessLookupError):
                ## Process might have terminated while we were reading
                continue

    return processes

def display_processes(processes, top_n=10):
    """Display processes sorted by memory usage"""
    ## Sort processes by memory usage (highest first)
    sorted_processes = sorted(processes, key=lambda p: p['memory_kb'], reverse=True)

    ## Display only top N processes
    print(f"\nTop {top_n} processes by memory usage at {datetime.now().strftime('%H:%M:%S')}:")
    print(f"{'PID':<7} {'NAME':<20} {'STATE':<10} {'MEMORY (KB)':<12}")
    print("-" * 50)

    for proc in sorted_processes[:top_n]:
        print(f"{proc['pid']:<7} {proc['name'][:19]:<20} {proc['state']:<10} {proc['memory_kb']:<12}")

## Monitor processes continuously
print("Simple Process Monitor")
print("Press Ctrl+C to exit")

try:
    while True:
        processes = list_processes()
        display_processes(processes)
        time.sleep(3)  ## Update every 3 seconds
except KeyboardInterrupt:
    print("\nProcess monitoring stopped")

프로세스 모니터를 실행합니다.

python3 process_monitor.py

다음과 유사한 출력이 표시되며 3 초마다 새로 고쳐집니다.

Simple Process Monitor
Press Ctrl+C to exit

Top 10 processes by memory usage at 14:30:25:
PID     NAME                 STATE      MEMORY (KB)
-------------------------------------------------------
1234    python3              S (sleeping) 56789
2345    node                 S (sleeping) 34567
3456    code                 S (sleeping) 23456
...

프로세스 모니터를 약 10 초 동안 실행한 다음 Ctrl+C를 눌러 중지합니다.

이러한 예제에서 다음을 수행하는 방법을 배웠습니다.

  1. ctypes를 사용하여 시스템 라이브러리를 로드하고 사용합니다.
  2. 시스템 정보에 액세스합니다.
  3. 실용적인 프로세스 모니터링 도구를 만듭니다.

이러한 원리는 ctypes를 통해 Windows API 와 상호 작용하는 데 사용할 원리와 동일하며, 라이브러리 이름과 함수 호출만 다릅니다.

ctypes 를 사용하여 C 구조체 생성 및 사용

이 단계에서는 Python 에서 C 구조체를 정의하고 사용하는 방법을 배우겠습니다. 구조체는 시스템 API 를 사용할 때 필수적입니다. Python 과 시스템 라이브러리 간에 데이터를 교환하는 데 일반적으로 사용되기 때문입니다.

Python 에서 C 구조체 이해

C 구조체는 ctypes.Structure 클래스를 사용하여 Python 에서 표현할 수 있습니다. 이를 통해 C 함수에서 예상하는 메모리 레이아웃과 일치하는 복잡한 데이터 구조를 만들 수 있습니다.

구조체로 작업하는 방법을 탐색하기 위해 struct_example.py라는 파일을 만들어 보겠습니다.

import ctypes

## Define a simple C structure
class Point(ctypes.Structure):
    _fields_ = [
        ("x", ctypes.c_int),
        ("y", ctypes.c_int)
    ]

## Create an instance of the Point structure
p = Point(10, 20)
print(f"Point coordinates: ({p.x}, {p.y})")

## Modify structure fields
p.x = 100
p.y = 200
print(f"Updated coordinates: ({p.x}, {p.y})")

## Create a Point from a dictionary
values = {"x": 30, "y": 40}
p2 = Point(**values)
print(f"Point from dictionary: ({p2.x}, {p2.y})")

## Get the raw memory view (pointer) of the structure
p_ptr = ctypes.pointer(p)
print(f"Memory address of p: {ctypes.addressof(p)}")
print(f"Access via pointer: ({p_ptr.contents.x}, {p_ptr.contents.y})")

## Define a nested structure
class Rectangle(ctypes.Structure):
    _fields_ = [
        ("top_left", Point),
        ("bottom_right", Point),
        ("color", ctypes.c_int)
    ]

## Create a rectangle with two points
rect = Rectangle(Point(0, 0), Point(100, 100), 0xFF0000)
print(f"Rectangle: Top-Left: ({rect.top_left.x}, {rect.top_left.y}), "
      f"Bottom-Right: ({rect.bottom_right.x}, {rect.bottom_right.y}), "
      f"Color: {hex(rect.color)}")

## Calculate the area (demonstrating structure manipulation)
width = rect.bottom_right.x - rect.top_left.x
height = rect.bottom_right.y - rect.top_left.y
print(f"Rectangle area: {width * height}")

스크립트를 실행합니다.

python3 struct_example.py

다음과 유사한 출력이 표시됩니다.

Point coordinates: (10, 20)
Updated coordinates: (100, 200)
Point from dictionary: (30, 40)
Memory address of p: 140737345462208
Access via pointer: (100, 200)
Rectangle: Top-Left: (0, 0), Bottom-Right: (100, 100), Color: 0xff0000
Rectangle area: 10000

시스템 시간 유틸리티 만들기

이제 C 구조체를 사용하여 시스템 시간 함수와 상호 작용하는 실용적인 애플리케이션을 만들어 보겠습니다. time_utility.py라는 파일을 만듭니다.

import ctypes
import time
from datetime import datetime

## Define the timespec structure (used in Linux for high-resolution time)
class timespec(ctypes.Structure):
    _fields_ = [
        ("tv_sec", ctypes.c_long),
        ("tv_nsec", ctypes.c_long)
    ]

## Define the tm structure (used for calendar time representation)
class tm(ctypes.Structure):
    _fields_ = [
        ("tm_sec", ctypes.c_int),     ## seconds (0 - 60)
        ("tm_min", ctypes.c_int),     ## minutes (0 - 59)
        ("tm_hour", ctypes.c_int),    ## hours (0 - 23)
        ("tm_mday", ctypes.c_int),    ## day of month (1 - 31)
        ("tm_mon", ctypes.c_int),     ## month of year (0 - 11)
        ("tm_year", ctypes.c_int),    ## year - 1900
        ("tm_wday", ctypes.c_int),    ## day of week (0 - 6, Sunday = 0)
        ("tm_yday", ctypes.c_int),    ## day of year (0 - 365)
        ("tm_isdst", ctypes.c_int)    ## is daylight saving time in effect
    ]

## Load the C library
libc = ctypes.CDLL("libc.so.6")

def get_system_time():
    """Get the current system time using C functions"""
    ## Get current time as seconds since epoch
    time_t_ptr = ctypes.pointer(ctypes.c_long())
    libc.time(time_t_ptr)  ## time() function gets current time

    ## Convert to printable time string
    time_val = time_t_ptr.contents.value
    time_str = ctypes.string_at(libc.ctime(time_t_ptr))

    return {
        "timestamp": time_val,
        "formatted_time": time_str.decode().strip()
    }

def get_high_resolution_time():
    """Get high resolution time using clock_gettime"""
    ts = timespec()

    ## CLOCK_REALTIME is usually 0
    CLOCK_REALTIME = 0

    ## Call clock_gettime to fill the timespec structure
    if libc.clock_gettime(CLOCK_REALTIME, ctypes.byref(ts)) != 0:
        raise OSError("Failed to get time")

    return {
        "seconds": ts.tv_sec,
        "nanoseconds": ts.tv_nsec,
        "precise_time": ts.tv_sec + (ts.tv_nsec / 1_000_000_000)
    }

def time_breakdown():
    """Break down the current time into its components using localtime"""
    ## Get current time
    time_t_ptr = ctypes.pointer(ctypes.c_long())
    libc.time(time_t_ptr)

    ## Get local time
    tm_ptr = libc.localtime(time_t_ptr)
    tm_struct = ctypes.cast(tm_ptr, ctypes.POINTER(tm)).contents

    ## Return time components
    return {
        "year": 1900 + tm_struct.tm_year,
        "month": 1 + tm_struct.tm_mon,  ## tm_mon is 0-11, we adjust to 1-12
        "day": tm_struct.tm_mday,
        "hour": tm_struct.tm_hour,
        "minute": tm_struct.tm_min,
        "second": tm_struct.tm_sec,
        "weekday": tm_struct.tm_wday,  ## 0 is Sunday
        "yearday": tm_struct.tm_yday + 1  ## tm_yday is 0-365, we adjust to 1-366
    }

## Main program
print("Time Utility using C Structures")
print("-" * 40)

## Get and display system time
sys_time = get_system_time()
print(f"System time: {sys_time['formatted_time']}")
print(f"Timestamp (seconds since epoch): {sys_time['timestamp']}")

## Get and display high-resolution time
hi_res = get_high_resolution_time()
print(f"\nHigh resolution time:")
print(f"Seconds: {hi_res['seconds']}")
print(f"Nanoseconds: {hi_res['nanoseconds']}")
print(f"Precise time: {hi_res['precise_time']}")

## Get and display time breakdown
components = time_breakdown()
print(f"\nTime breakdown:")
print(f"Date: {components['year']}-{components['month']:02d}-{components['day']:02d}")
print(f"Time: {components['hour']:02d}:{components['minute']:02d}:{components['second']:02d}")
print(f"Day of week: {components['weekday']} (0=Sunday)")
print(f"Day of year: {components['yearday']}")

## Compare with Python's datetime
now = datetime.now()
print(f"\nPython datetime: {now}")
print(f"Python timestamp: {time.time()}")

시간 유틸리티를 실행합니다.

python3 time_utility.py

다음과 유사한 출력이 표시됩니다.

Time Utility using C Structures
----------------------------------------
System time: Wed Jun 14 15:22:36 2023
Timestamp (seconds since epoch): 1686756156

High resolution time:
Seconds: 1686756156
Nanoseconds: 923456789
Precise time: 1686756156.923457

Time breakdown:
Date: 2023-06-14
Time: 15:22:36
Day of week: 3 (0=Sunday)
Day of year: 165

Python datetime: 2023-06-14 15:22:36.923499
Python timestamp: 1686756156.9234989

ctypes 를 사용한 메모리 관리 이해

ctypes 및 C 구조체로 작업할 때는 메모리 관리를 이해하는 것이 중요합니다. 이를 보여주기 위해 memory_management.py라는 예제를 하나 더 만들어 보겠습니다.

import ctypes
import gc  ## Garbage collector module

## Define a simple structure
class MyStruct(ctypes.Structure):
    _fields_ = [
        ("id", ctypes.c_int),
        ("value", ctypes.c_double),
        ("name", ctypes.c_char * 32)  ## Fixed-size character array
    ]

def demonstrate_memory_management():
    print("Memory Management with ctypes")
    print("-" * 40)

    ## Create a structure instance
    my_data = MyStruct(
        id=1,
        value=3.14159,
        name=b"Example"
    )

    print(f"Structure size: {ctypes.sizeof(my_data)} bytes")
    print(f"Memory address: {hex(ctypes.addressof(my_data))}")

    ## Create a pointer to the structure
    data_ptr = ctypes.pointer(my_data)
    print(f"Pointer value: {hex(ctypes.cast(data_ptr, ctypes.c_void_p).value)}")

    ## Access through pointer
    print(f"Access via pointer: id={data_ptr.contents.id}, value={data_ptr.contents.value}")

    ## Allocate memory for a new structure
    new_struct_ptr = ctypes.POINTER(MyStruct)()
    new_struct_ptr = ctypes.cast(
        ctypes.create_string_buffer(ctypes.sizeof(MyStruct)),
        ctypes.POINTER(MyStruct)
    )

    ## Initialize the allocated memory
    new_struct = new_struct_ptr.contents
    new_struct.id = 2
    new_struct.value = 2.71828
    new_struct.name = b"Allocated"

    print(f"\nAllocated structure memory address: {hex(ctypes.addressof(new_struct))}")
    print(f"Allocated structure content: id={new_struct.id}, value={new_struct.value}, name={new_struct.name.decode()}")

    ## Create an array of structures
    StructArray = MyStruct * 3
    struct_array = StructArray(
        MyStruct(10, 1.1, b"First"),
        MyStruct(20, 2.2, b"Second"),
        MyStruct(30, 3.3, b"Third")
    )

    print("\nArray of structures:")
    for i, item in enumerate(struct_array):
        print(f"  [{i}] id={item.id}, value={item.value}, name={item.name.decode()}")

    ## Force garbage collection
    print("\nForcing garbage collection...")
    gc.collect()

    ## Memory is automatically managed by Python

## Run the demonstration
demonstrate_memory_management()

메모리 관리 스크립트를 실행합니다.

python3 memory_management.py

다음과 유사한 출력이 표시됩니다.

Memory Management with ctypes
----------------------------------------
Structure size: 48 bytes
Memory address: 0x7f3c2e32b040
Pointer value: 0x7f3c2e32b040
Access via pointer: id=1, value=3.14159

Allocated structure memory address: 0x7f3c2e32b0a0
Allocated structure content: id=2, value=2.71828, name=Allocated

Array of structures:
  [0] id=10, value=1.1, name=First
  [1] id=20, value=2.2, name=Second
  [2] id=30, value=3.3, name=Third

Forcing garbage collection...

이러한 예제에서 다음을 수행하는 방법을 배웠습니다.

  1. ctypes를 사용하여 Python 에서 C 구조체를 정의하고 사용합니다.
  2. 구조체에 대한 포인터를 만들고 해당 내용을 액세스합니다.
  3. 중첩 구조체 및 구조체 배열을 만듭니다.
  4. 시스템 시간 함수를 사용하여 실용적인 애플리케이션을 구축합니다.
  5. ctypes로 작업할 때 메모리를 관리합니다.

이러한 기술은 Windows API 함수를 사용할 때 필수적입니다. Windows API 함수는 정보를 앞뒤로 전달하기 위해 복잡한 데이터 구조가 필요한 경우가 많기 때문입니다.

완전한 시스템 모니터 애플리케이션 구축

이 마지막 단계에서는 모든 지식을 모아 포괄적인 시스템 모니터 애플리케이션을 구축합니다. 이 애플리케이션은 ctypes를 사용하여 시스템 정보를 수집하고, 실시간 메트릭을 표시하며, 시스템 라이브러리와 상호 작용하는 더 큰 Python 애플리케이션을 구조화하는 방법을 보여줍니다.

시스템 모니터 만들기

다음 내용으로 system_monitor.py라는 새 파일을 만듭니다.

import ctypes
import os
import time
import platform
from datetime import datetime

class SystemMonitor:
    """System monitoring application using ctypes"""

    def __init__(self):
        """Initialize the system monitor"""
        self.libc = ctypes.CDLL("libc.so.6")
        self.running = False

        ## Define a timespec structure for time-related functions
        class timespec(ctypes.Structure):
            _fields_ = [
                ("tv_sec", ctypes.c_long),
                ("tv_nsec", ctypes.c_long)
            ]
        self.timespec = timespec

        ## Print basic system information
        print(f"System Monitor for {platform.system()} {platform.release()}")
        print(f"Python Version: {platform.python_version()}")
        print(f"Machine: {platform.machine()}")
        print("-" * 50)

    def get_uptime(self):
        """Get system uptime information"""
        try:
            CLOCK_BOOTTIME = 7  ## Linux specific
            time_buf = self.timespec()

            if hasattr(self.libc, 'clock_gettime') and self.libc.clock_gettime(CLOCK_BOOTTIME, ctypes.byref(time_buf)) == 0:
                uptime_seconds = time_buf.tv_sec
                days, remainder = divmod(uptime_seconds, 86400)
                hours, remainder = divmod(remainder, 3600)
                minutes, seconds = divmod(remainder, 60)
                return {
                    "total_seconds": uptime_seconds,
                    "days": days,
                    "hours": hours,
                    "minutes": minutes,
                    "seconds": seconds
                }
            return None
        except Exception as e:
            print(f"Error getting uptime: {e}")
            return None

    def get_memory_info(self):
        """Get memory usage information using /proc/meminfo"""
        memory_info = {}
        try:
            with open('/proc/meminfo', 'r') as f:
                for line in f:
                    if ':' in line:
                        key, value = line.split(':', 1)
                        ## Remove 'kB' and convert to integer
                        value = value.strip()
                        if 'kB' in value:
                            value = int(value.split()[0]) * 1024  ## Convert to bytes
                        memory_info[key.strip()] = value

            ## Calculate memory usage percentage
            if 'MemTotal' in memory_info and 'MemAvailable' in memory_info:
                total = int(memory_info['MemTotal'])
                available = int(memory_info['MemAvailable'])
                used = total - available
                memory_info['UsedPercentage'] = (used / total) * 100

            return memory_info
        except Exception as e:
            print(f"Error getting memory info: {e}")
            return {}

    def get_cpu_info(self):
        """Get CPU information using /proc/stat"""
        cpu_info = {'cpu_percent': 0}
        try:
            ## We need two readings to calculate CPU usage
            def get_cpu_sample():
                with open('/proc/stat', 'r') as f:
                    line = f.readline()
                cpu_values = [int(x) for x in line.split()[1:8]]
                return sum(cpu_values), cpu_values[3]  ## Return total and idle

            ## First sample
            total1, idle1 = get_cpu_sample()
            time.sleep(0.5)  ## Wait for 0.5 second

            ## Second sample
            total2, idle2 = get_cpu_sample()

            ## Calculate CPU usage
            total_delta = total2 - total1
            idle_delta = idle2 - idle1

            if total_delta > 0:
                cpu_info['cpu_percent'] = 100 * (1 - idle_delta / total_delta)

            return cpu_info
        except Exception as e:
            print(f"Error getting CPU info: {e}")
            return cpu_info

    def get_disk_info(self):
        """Get disk usage information"""
        try:
            ## Get disk usage for the root filesystem
            stat = os.statvfs('/')

            ## Calculate total, free, and used space
            total = stat.f_blocks * stat.f_frsize
            free = stat.f_bfree * stat.f_frsize
            used = total - free

            return {
                'total_bytes': total,
                'free_bytes': free,
                'used_bytes': used,
                'used_percent': (used / total) * 100 if total > 0 else 0
            }
        except Exception as e:
            print(f"Error getting disk info: {e}")
            return {}

    def get_process_count(self):
        """Count running processes"""
        try:
            return len([p for p in os.listdir('/proc') if p.isdigit()])
        except Exception as e:
            print(f"Error counting processes: {e}")
            return 0

    def format_bytes(self, bytes_value):
        """Format bytes into a human-readable format"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if bytes_value < 1024 or unit == 'TB':
                return f"{bytes_value:.2f} {unit}"
            bytes_value /= 1024

    def display_dashboard(self):
        """Display the system monitoring dashboard"""
        ## Clear the screen (works in most terminals)
        print("\033c", end="")

        ## Display header
        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"System Monitor - {current_time}")
        print("-" * 50)

        ## Display uptime
        uptime = self.get_uptime()
        if uptime:
            print(f"Uptime: {uptime['days']} days, {uptime['hours']} hours, "
                  f"{uptime['minutes']} minutes, {uptime['seconds']} seconds")

        ## Display CPU usage
        cpu_info = self.get_cpu_info()
        print(f"CPU Usage: {cpu_info['cpu_percent']:.1f}%")

        ## Display memory information
        memory_info = self.get_memory_info()
        if memory_info and 'MemTotal' in memory_info:
            print("\nMemory Information:")
            total = int(memory_info['MemTotal'])
            available = int(memory_info.get('MemAvailable', 0))
            used = total - available
            print(f"  Total: {self.format_bytes(total)}")
            print(f"  Used: {self.format_bytes(used)} ({memory_info.get('UsedPercentage', 0):.1f}%)")
            print(f"  Available: {self.format_bytes(available)}")
            if 'SwapTotal' in memory_info:
                swap_total = int(memory_info['SwapTotal'])
                swap_free = int(memory_info.get('SwapFree', 0))
                swap_used = swap_total - swap_free
                if swap_total > 0:
                    print(f"  Swap Used: {self.format_bytes(swap_used)} "
                          f"({(swap_used / swap_total) * 100:.1f}%)")

        ## Display disk information
        disk_info = self.get_disk_info()
        if disk_info:
            print("\nDisk Information (/):")
            print(f"  Total: {self.format_bytes(disk_info['total_bytes'])}")
            print(f"  Used: {self.format_bytes(disk_info['used_bytes'])} "
                  f"({disk_info['used_percent']:.1f}%)")
            print(f"  Free: {self.format_bytes(disk_info['free_bytes'])}")

        ## Display process count
        process_count = self.get_process_count()
        print(f"\nRunning Processes: {process_count}")

        print("\nPress Ctrl+C to exit")

    def start_monitoring(self, interval=2):
        """Start the system monitoring with the specified refresh interval"""
        self.running = True
        try:
            while self.running:
                self.display_dashboard()
                time.sleep(interval)
        except KeyboardInterrupt:
            print("\nMonitoring stopped.")
            self.running = False

## Create and start the system monitor
if __name__ == "__main__":
    monitor = SystemMonitor()
    monitor.start_monitoring()

시스템 모니터를 실행합니다.

python3 system_monitor.py

시스템 모니터는 시스템에 대한 실시간 정보를 표시하며 2 초마다 업데이트됩니다. 다음이 포함된 대시보드가 표시됩니다.

  • 시스템 가동 시간
  • CPU 사용량
  • 메모리 정보 (총, 사용, 사용 가능)
  • 디스크 사용량
  • 프로세스 수

잠시 동안 모니터를 실행하여 메트릭이 어떻게 변경되는지 확인한 다음 Ctrl+C를 눌러 종료합니다.

시스템 모니터 구조 이해

시스템 모니터의 주요 구성 요소를 분석해 보겠습니다.

  1. 클래스 구조: 객체 지향 프로그래밍을 사용하여 코드를 구성합니다.
  2. 메서드 구성: 기능을 별도의 메서드로 분리합니다.
  3. 오류 처리: 잠재적인 오류를 처리하기 위해 try-except 블록을 사용합니다.
  4. 데이터 형식 지정: 원시 데이터를 사람이 읽을 수 있는 형식으로 변환합니다.
  5. 실시간 업데이트: 주기적인 업데이트를 위해 time.sleep() 을 사용하는 루프를 사용합니다.

설명서 추가

이제 시스템 모니터에 대한 설명 파일을 추가해 보겠습니다. README.md라는 파일을 만듭니다.

Python 시스템 모니터

시스템 상호 작용을 위해 ctypes를 사용하여 Python 으로 구축된 포괄적인 시스템 모니터링 애플리케이션입니다.

기능

  • 실시간 CPU 사용량 모니터링
  • 메모리 사용량 추적
  • 디스크 공간 분석
  • 프로세스 수
  • 시스템 가동 시간 표시

요구 사항

  • Python 3.6 이상
  • Linux 운영 체제

실행 방법

메인 스크립트를 실행하기만 하면 됩니다.

python system_monitor.py
### 작동 방식

이 애플리케이션은 Python 의 `ctypes` 라이브러리를 사용하여 시스템 라이브러리와 인터페이스하고 하위 수준 시스템 정보에 액세스합니다. 또한 `/proc` 파일 시스템을 사용하여 시스템 상태에 대한 추가 메트릭을 수집합니다.

모니터링은 2 초마다 업데이트되는 실시간으로 수행됩니다 (구성 가능).

### 아키텍처

애플리케이션은 다음과 같은 주요 구성 요소가 있는 객체 지향 방식을 따릅니다.

1. **SystemMonitor 클래스**: 모니터링을 조율하는 메인 클래스
2. **데이터 수집 메서드**: 다양한 시스템 메트릭을 수집하기 위한 메서드
3. **표시 메서드**: 수집된 데이터를 형식 지정하고 표시하기 위한 메서드

### 모니터 확장

새로운 모니터링 기능을 추가하려면:

1. 원하는 데이터를 수집하기 위해 `SystemMonitor` 클래스에 새 메서드를 만듭니다.
2. 새 정보를 표시하도록 `display_dashboard` 메서드를 업데이트합니다.
3. 견고성을 위해 적절한 오류 처리를 보장합니다.

### 학습 자료

- [Python ctypes 설명서](https://docs.python.org/3/library/ctypes.html)
- [Linux Proc 파일 시스템 설명서](https://man7.org/linux/man-pages/man5/proc.5.html)

### 검토 및 요약

이 랩에서 무엇을 달성했는지 검토해 보겠습니다.

1. Python 의 `ctypes` 라이브러리를 사용하여 시스템 라이브러리와 상호 작용하는 방법을 배웠습니다.
2. Python 에서 C 데이터 유형 및 구조체를 탐색했습니다.
3. 몇 가지 실용적인 애플리케이션을 만들었습니다.
   - 기본 시스템 정보 검색
   - 프로세스 모니터링
   - 시간 유틸리티
   - 메모리 관리 데모
   - 포괄적인 시스템 모니터

4. 다음을 수행하는 방법을 배웠습니다.
   - 동적 라이브러리 로드
   - Python 에서 C 함수 호출
   - C 구조체 정의 및 조작
   - 포인터 및 메모리 주소로 작업
   - 시스템 수준 오류 처리

이러한 기술은 Windows 시스템에서 개발할 때 Windows API 로 작업하기 위한 기반을 형성합니다. 원리는 동일하게 유지됩니다. libc 대신 Windows 관련 라이브러리 (예: kernel32.dll, user32.dll 등) 를 로드하지만 구조체 정의, 함수 호출 및 데이터 처리 방식은 일관되게 유지됩니다.

요약

이 랩에서는 Python 의 ctypes 라이브러리를 사용하여 시스템 라이브러리와 상호 작용하고 시스템 수준 작업을 수행하는 방법을 배웠습니다. 이 랩은 Linux 환경에서 진행되었지만, 배운 원리와 기술은 Windows API 프로그래밍에도 직접 적용됩니다.

이 랩의 주요 내용은 다음과 같습니다.

  1. ctypes 기본 사항 이해: 동적 라이브러리를 로드하고, C 데이터 유형을 정의하며, Python 에서 시스템 함수를 호출하는 방법을 배웠습니다.

  2. C 구조체 작업: 시스템 라이브러리와 복잡한 데이터를 교환하는 데 필수적인 Python 에서 C 구조체를 생성하고 조작하는 방법을 익혔습니다.

  3. 메모리 관리: 시스템 라이브러리로 작업할 때 메모리 할당, 포인터 및 메모리 관리에 대한 통찰력을 얻었습니다.

  4. 실용적인 애플리케이션 구축: 포괄적인 시스템 모니터로 절정에 달하는 유용한 애플리케이션을 만들기 위해 지식을 적용했습니다.

  5. 시스템 통합: ctypes 브리지를 통해 Python 이 하위 수준 시스템 기능과 통합될 수 있는 방법을 보았습니다.

이러한 기술은 Linux 또는 Windows 에서 운영 체제와 하위 수준에서 상호 작용해야 하는 애플리케이션을 개발하기 위한 견고한 기반을 제공합니다. Windows 시스템에서 작업할 때는 동일한 접근 방식을 사용하지만 Windows 관련 라이브러리 및 API 함수를 사용합니다.

추가 학습에는 이 랩에서 배운 기술을 사용하여 창 관리, 그래픽 인터페이스, 시스템 서비스 또는 네트워킹 기능을 위한 Windows 관련 API 를 탐색하는 것이 포함될 수 있습니다.