[질문 예제들]
Q1. File io에 관하여 코드 실행 결과를 작성하시오
C언어로 되어 있으므로 코드 흐름 따라 결과 작성하기
Q2. offset을 설정해 읽을 수 있는 read 방법 1가지
시스템 프로그래밍에서 파일을 읽을 때, pread 함수를 사용하여 offset을 설정할 수 있습니다.
pread 함수는 파일 디스크립터, 읽을 데이터를 저장할 버퍼, 읽을 바이트 수, 및 파일 내에서의 오프셋을 인자로 받습니다.
이 함수를 사용하면 파일을 읽을 때 현재의 파일 오프셋을 변경하지 않고 특정 오프셋에서 읽을 수 있습니다.
lseek: 파일의 오프셋을 이동시키는 시스템 콜입니다. 주로 파일 내에서 읽거나 쓸 위치를 지정하는 데 사용됩니다.
pread: 파일에서 읽기를 수행하되, 파일의 오프셋을 변경하지 않고 특정 위치에서 읽을 때 사용됩니다 = lseek + read 함수
pwrite: 파일에 쓰기를 수행하되, 파일의 오프셋을 변경하지 않고 특정 위치에 쓸 때 사용됩니다.
Q3. 파일 자체 정보를 저장하는 구조체 이름
리눅스 시스템 프로그래밍에서 파일의 자체 정보를 저장하는 구조체는 struct stat입니다.
이 구조체는 <sys/stat.h> 헤더 파일에서 정의되어 있습니다.
struct stat은 파일의 다양한 속성 및 정보를 저장하는 데 사용됩니다. 몇 가지 중요한 멤버들로는 파일의 크기, 소유자 정보, 권한, 변경 시간 등이 있습니다.
다음은 struct stat의 일부 멤버들입니다:
struct stat {
dev_t st_dev; /* 파일이 위치한 장치의 ID */
ino_t st_ino; /* 파일의 inode 번호 */
mode_t st_mode; /* 파일의 종류 및 접근 권한 */
nlink_t st_nlink; /* 하드 링크의 개수 */
uid_t st_uid; /* 파일의 소유자 ID */
gid_t st_gid; /* 파일의 그룹 ID */
off_t st_size; /* 파일 크기 (바이트 단위) */
time_t st_atime; /* 마지막 접근 시간 */
time_t st_mtime; /* 마지막 수정 시간 */
time_t st_ctime; /* 마지막 변경 시간 */
// ... 기타 멤버들 ...
};
Q4. 예시를 들고 다음 중 정확하게 sleep의 시간을 측정할 수 없는 posix clock은? 있다면 왜 그런가요?
POSIX clock 중에서 정확한 sleep 시간을 측정하기 어려운 것은 일반적으로 CLOCK_REALTIME입니다.
CLOCK_REALTIME은 시스템의 실제 시간을 기반으로 하며, 외부 요소에 의해 시간이 조정될 수 있습니다.
이러한 시간의 조정은 예를 들어 사용자에 의해 수동으로 시스템 시간을 변경하거나, 네트워크를 통해 동기화된 시간 서버로부터 시간을 받아오는 것 등이 해당됩니다.
따라서, sleep 함수를 사용하여 일정 시간 동안 프로세스를 중지시키는 경우, CLOCK_REALTIME을 사용할 경우 외부적인 시간의 변화로 인해 sleep 시간이 정확하게 유지되지 않을 수 있습니다. 이러한 이유로 정확한 sleep 시간을 보장하기 위해서는 CLOCK_MONOTONIC을 사용하는 것이 더 바람직합니다.
CLOCK_REALTIME
CLOCK_MONOTONIC
CLOCK_PROCESS_CPUTIME_ID
CLOCK_THREAD_CPUTIME_ID
Q5. Thread에 signal을 보내는 함수 이름은?
리눅스 시스템 프로그래밍에서는 POSIX 스레드(pthread)를 사용하여 쓰레드 간 통신이 이루어집니다.
스레드에 시그널을 보내려면 pthread_kill 함수를 사용할 수 있습니다.
이 함수는 다음과 같이 정의되어 있습니다:
#include <signal.h>
#include <pthread.h>
int pthread_kill(pthread_t thread, int sig);
// thread: 시그널을 보낼 대상 스레드의 식별자
// sig: 보낼 시그널의 번호
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
void *thread_function(void *arg) {
while (1) {
// 스레드의 작업 수행
printf("Thread is running...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t thread;
// 새로운 스레드 생성
pthread_create(&thread, NULL, thread_function, NULL);
// 몇 초 후에 시그널을 보내기 위해 대기
sleep(3);
// 생성된 스레드에 시그널을 보냄
pthread_kill(thread, SIGUSR1);
// 메인 스레드 종료
pthread_join(thread, NULL);
return 0;
}
이 예제에서는 pthread_create로 스레드를 생성하고, pthread_kill로 생성된 스레드에 시그널을 보냅니다. 보낸 시그널은 스레드 내에서 적절하게 처리해야 합니다.
Q6. process에 signal을 보낸 경우 어느 thread가 수신을 하는가
#include <signal.h>
#include <pthread.h>
void *thread_function(void *arg) {
// 스레드의 작업 내용
return NULL;
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, thread_function, NULL);
// 특정 스레드에게 시그널 보내기
pthread_kill(tid, SIGUSR1);
pthread_join(tid, NULL);
return 0;
}
이 예시에서는 pthread_kill 함수를 사용하여 특정 스레드에게 SIGUSR1 시그널을 보내고 있습니다.
이렇게 하면 해당 스레드에서 시그널을 처리하도록 유도할 수 있습니다.
즉 pthread_kill을 사용한 쓰레드 tid가 수신을 하는 것이다
[간단 개념 정리들]
- Program / Process / Thread
Program : 실행 가능한 코드
Process : 실행 중인 프로그램
Thread : 프로세스 내의 실행 단위
프로그램(Program):
- 프로그램은 실행 가능한 파일 또는 소프트웨어 응용 프로그램을 나타냅니다.
- 예를 들어, 워드 프로세서, 웹 브라우저, 미디어 플레이어 등은 모두 프로그램의 예입니다.
- 프로그램은 디스크에 저장된 실행 코드와 데이터로 이루어져 있습니다.
프로세스(Process):
- 프로세스는 실행 중인 프로그램의 인스턴스를 나타냅니다.
- 각 프로세스는 독립적인 메모리 공간, 실행 환경 및 시스템 자원을 가지고 있습니다.
- 여러 프로세스는 서로 독립적으로 실행되며, 하나의 프로세스가 다른 프로세스에 영향을 미치지 않습니다.
스레드(Thread):
- 스레드는 프로세스 내에서 실행되는 작은 실행 단위입니다.
- 하나의 프로세스는 여러 개의 스레드를 가질 수 있습니다.
- 스레드는 같은 프로세스 내의 다른 스레드와 메모리를 공유하며, 이로 인해 효율적인 자원 사용이 가능합니다.
- 스레드는 경량화된 프로세스로 생각할 수 있으며, 프로세스와 스레드 간에는 일반적으로 통신이 더 쉽습니다.
게임에 비유
프로그램(Program):
비유: 게임의 설치 파일
설명: 게임의 설치 파일은 게임을 실행하기 위한 모든 데이터와 명령어가 포함된 것으로 생각할 수 있습니다. 이 파일은 아직 실행되지 않았지만 게임을 시작할 수 있는 모든 정보를 가지고 있습니다.
프로세스(Process):
비유: 게임을 실행한 상태
설명: 게임을 실행하면 컴퓨터에서는 해당 게임에 대한 프로세스가 시작됩니다. 이 프로세스는 게임이 현재 진행 중인 상태를 나타내며, 게임을 실행하면서 필요한 모든 자원과 메모리를 할당받습니다.
스레드(Thread):
비유: 게임 내에서의 병렬 작업
설명: 게임 내에서 여러 가지 작업이 동시에 일어날 때, 각 작업은 하나의 스레드로 볼 수 있습니다. 예를 들어, 게임에서는 화면 업데이트, 소리 재생, 사용자 입력 처리 등이 병렬로 처리될 수 있습니다. 이때 각각의 작업은 하나의 스레드로 동작하며, 이들이 함께 게임을 원활하게 동작시키게 됩니다.
- 메세지 큐
- create, send, receieve
- sys V 메세지 큐와 POSIX 메세지 큐로 나뉨
리눅스 시스템 프로그래밍에서 System V 메시지 큐와 POSIX 메시지 큐는 두 가지 다른 IPC(Inter-Process Communication) 메커니즘입니다. 각각의 메시지 큐 시스템은 다른 API와 동작 방식을 가지고 있습니다.
1. **System V 메시지 큐:**
- **Key System Calls:** `msgget()`, `msgsnd()`, `msgrcv()`, `msgctl()`
- **식별자(ID) 사용:** System V 메시지 큐는 메시지 큐를 식별하는 데에 사용되는 특별한 키(key)를 사용합니다.
- **메시지 큐 구조체:** 메시지 큐는 시스템 전역에서 고유한 키를 가진 메시지 큐 식별자로 식별되며, 메시지는 구조체로 구성되어 전달됩니다.
- **세마포어 제어 구조체 사용:** System V IPC(Inter-Process Communication) 메커니즘에 사용되는 세마포어 제어 구조체를 활용합니다.
2. **POSIX 메시지 큐:**
- **Key System Calls:** `mq_open()`, `mq_send()`, `mq_receive()`, `mq_close()`, `mq_unlink()`
- **파일 경로 사용:** POSIX 메시지 큐는 파일 경로를 사용하여 메시지 큐를 식별합니다.
- **메시지 큐 속성 객체:** 메시지 큐를 생성할 때 메시지 큐의 속성을 지정할 수 있는 `mq_attr` 구조체를 사용합니다.
- **표준 파일 디스크립터 사용:** POSIX 메시지 큐는 파일과 유사하게 파일 디스크립터를 사용하여 메시지 큐를 열고 읽기/쓰기 작업을 합니다.
### 주요 차이점:
- **API 및 함수 호출:** 두 메시지 큐 시스템은 서로 다른 API와 함수 호출을 사용합니다.
- **식별 방법:** System V는 특별한 키를 사용하여 메시지 큐를 식별하고, POSIX는 파일 경로를 사용합니다.
- **메시지 큐 속성 및 구조체:** POSIX 메시지 큐는 속성을 설정하는 구조체를 사용하며, System V는 메시지 큐 구조체 및 세마포어 구조체를 사용합니다.
- **표준화:** POSIX 메시지 큐는 POSIX 표준에 따라 설계되어 표준 라이브러리 및 툴킷과 호환성이 높습니다.
어떤 메시지 큐를 선택할지는 사용하려는 시스템, 요구 사항 및 개발자의 선호도에 따라 다를 수 있습니다. 최근 시스템에서는 POSIX 메시지 큐가 더 유연하고 표준화된 API를 제공하기 때문에 많은 경우에 선호되는 선택지가 됩니다.
- 쓰레드
- fork : 프로세스 복제
- exec : 프로그램 실행
- exit : 프로세스 종료
싱글 쓰레드, 멀티 쓰레드 차이 :
- 멀티 쓰레드로 쓰레드가 3개 되면 code data files는 그대로 유지되지만 register, stack, thread가 3개가 된다
- 멀티 쓰레드는 프로그램의 성능을 향상시킬 수 있지만, 쓰레드 간의 데이터 공유와 동기화에 신경 써야 합니다. 이것은 복잡성을 증가시킬 수 있습니다. 싱글 쓰레드는 간단하지만, 한 번에 하나의 작업만 처리할 수 있어서 성능 면에서 제약이 있을 수 있습니다. 선택은 프로그램의 요구사항과 목적에 따라 다릅니다.
create, pthread_join, detach, 동기화, mutex 사용
멀티 프로세스는 각각이 독립된 프로그램으로, 메모리를 분리하고 IPC를 사용하여 통신합니다. 오류가 한 프로세스에 영향을 미치지 않지만, 자원 사용이 많고 컨텍스트 스위칭 오버헤드가 높습니다.
멀티 쓰레드는 같은 프로그램의 일부로, 공유 메모리를 통해 데이터를 공유하며 쓰레드 간 통신이 간편합니다. 안정성에 주의해야 하지만 자원 효율성이 높고 컨텍스트 스위칭이 더 빠릅니다. 선택은 프로그램 목적에 따라 달라집니다.
- Race Condition, Lock, Mutex, 그리고 Deadlock
은 다중 스레드 환경에서 발생할 수 있는 문제와 이를 해결하기 위한 동기화 메커니즘들을 설명하는 중요한 개념들입니다.
Race Condition (경쟁 상태):
여러 스레드가 동시에 공유된 자원에 접근할 때, 순서가 보장되지 않아 예측할 수 없는 결과가 발생하는 문제입니다.
동시에 자원을 수정하려는 경우, 올바른 순서로 접근하지 않으면 Race Condition이 발생할 수 있습니다.
Lock (락) 및 Mutex (뮤텍스):
Lock과 Mutex는 Race Condition을 방지하기 위한 동기화 메커니즘으로 사용됩니다.
락은 임계 영역에 들어갈 때 잠금(lock)을 설정하고, 작업이 끝나면 잠금을 해제(unlock)합니다.
Mutex는 상호 배제를 통해 한 번에 하나의 스레드만이 자원에 접근할 수 있도록 합니다.
Deadlock (데드락):
데드락은 둘 이상의 스레드나 프로세스가 서로의 작업이 끝나기를 기다리며 무한히 대기하는 상태를 말합니다.
데드락은 여러 조건이 동시에 충족될 때 발생하며, 이 중요한 조건 중에는 상호 배제, 보유 및 대기, 비선점, 순환 대기가 있습니다.
이러한 문제를 해결하기 위해서는 Lock 또는 Mutex를 사용하여 Race Condition을 방지하고, 데드락을 예방하거나 탐지하기 위한 적절한 전략을 수립해야 합니다. 즉, 동시에 자원에 접근하는 상황을 통제하고, 데드락의 가능성을 최소화하기 위해 적절한 동기화 기술과 알고리즘을 선택하고 구현해야 합니다.
- File Offset
리눅스 시스템 프로그래밍에서 File Offset(파일 오프셋)은 파일 내에서 데이터를 읽거나 쓸 위치를 가리키는 값입니다. 파일 오프셋은 파일 포인터(File Pointer) 또는 파일 디스크립터(File Descriptor)와 관련이 있습니다. 파일 오프셋은 파일 내의 현재 위치를 가리키며, 이를 조작하여 파일에서 원하는 위치에서 읽기 또는 쓰기 작업을 수행할 수 있습니다.
대표적으로 사용되는 파일 오프셋 관련 함수들은 다음과 같습니다.
1. **`lseek` 함수:**
- `lseek` 함수는 파일 오프셋을 이동시키는 함수로, 파일 디스크립터, 이동할 오프셋 값, 그리고 이동의 기준을 나타내는 SEEK_SET, SEEK_CUR, SEEK_END 중 하나를 인자로 받습니다.
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- `fd`: 파일 디스크립터
- `offset`: 이동할 오프셋 값
- `whence`: SEEK_SET(파일의 시작을 기준으로), SEEK_CUR(현재 위치를 기준으로), SEEK_END(파일의 끝을 기준으로)
2. **`pread`와 `pwrite` 함수:**
- `pread` 함수는 파일에서 읽을 때 사용되며, `pwrite` 함수는 파일에 쓸 때 사용됩니다. 이 두 함수는 파일 오프셋을 변경하지 않고 주어진 오프셋에서 읽거나 쓸 수 있습니다.
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
- `fd`: 파일 디스크립터
- `buf`: 데이터를 읽거나 쓸 버퍼
- `count`: 읽거나 쓸 데이터의 크기
- `offset`: 파일에서의 오프셋 값
파일 오프셋을 올바르게 조작함으로써 여러 프로세스 간의 파일 접근을 동기화하거나, 파일의 특정 부분에 대한 작업을 수행할 수 있습니다. 파일 오프셋은 파일 입출력 및 관리에서 중요한 역할을 합니다.
- Time
리눅스 시스템 프로그래밍에서 시간과 관련된 작업을 수행하는 데에는 여러 가지 방법이 있습니다. 여기에서는 주요한 세 가지 개념인 `time`, `sleeping`, `timer`에 대해 간략하게 설명하겠습니다.
1. **시간 (Time):**
- 리눅스 시스템에서는 시간은 주로 Epoch time 또는 Unix time으로 표현됩니다. Epoch time은 1970년 1월 1일 00:00:00 UTC부터 경과한 초로 표현된 시간입니다.
- C 언어에서는 `time_t` 데이터 타입을 사용하여 시간을 나타내며, `<time.h>` 헤더 파일에 여러 시간 관련 함수들이 정의되어 있습니다.
- 예를 들어, `time()` 함수는 현재 시간을 초 단위로 반환합니다.
#include <stdio.h>
#include <time.h>
int main() {
time_t currentTime = time(NULL);
printf("Current time: %ld\n", currentTime);
return 0;
2. **Sleeping (쉬기):**
- `sleep()` 함수는 프로그램을 지정된 시간 동안 일시적으로 중지시킵니다. 이 함수는 초 단위로 대기하도록 설정됩니다.
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Sleeping for 3 seconds...\n");
sleep(3);
printf("Awake!\n");
return 0;
}
3. **Timer (타이머):**
- 리눅스에서는 타이머를 사용하여 특정 시간 간격 동안 작업을 수행하거나 특정 시간에 작업을 예약할 수 있습니다.
- `timer_create()`, `timer_settime()`과 같은 함수들을 사용하여 타이머를 설정하고, 시그널 핸들러를 등록하여 타이머 이벤트를 처리할 수 있습니다.
#include <stdio.h>
#include <signal.h>
#include <time.h>
timer_t timerid;
void timer_handler(int signo) {
if (signo == SIGALRM)
printf("Timer expired!\n");
}
int main() {
struct sigevent sev;
struct itimerspec its;
signal(SIGALRM, timer_handler);
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGALRM;
timer_create(CLOCK_REALTIME, &sev, &timerid);
its.it_value.tv_sec = 2; // 타이머 시작까지의 시간 (초)
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0; // 타이머 간격 (0이면 한 번만 발생)
its.it_interval.tv_nsec = 0;
timer_settime(timerid, 0, &its, NULL);
while (1) {
// 프로그램이 종료되지 않도록 루프를 유지
}
return 0;
}
이러한 기능들은 리눅스 시스템 프로그래밍에서 시간 관리와 작업 스케줄링에 사용됩니다.
'시스템 프로그래밍 > 대학교 강의 정리' 카테고리의 다른 글
[시스템 프로그래밍] 중간 족보 (0) | 2023.10.15 |
---|---|
[시스템 프로그래밍] 어셈블리어 (0) | 2023.10.15 |
시스템 프로그래밍 (0) | 2023.09.03 |
오리엔테이션 (0) | 2023.08.28 |