Search
Duplicate
🗣️

minitalk (2) 선행 지식

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C
42cursus
minitalk
태그
Scrap
8 more properties
minitalk

선행 지식

간단한 개념에 대해서만 설명하며 자세한 내용은 아래에서 추가적으로 다룬다.

프로세스와 스레드

프로그램(Program) : 실행할 수 있는 파일
프로세스(Process) : 프로그램이 실행되어 메모리에 탑재된 인스턴스 (독립 개체)
스레드(Thread) : 프로세스 내에서 실행되고 있는 여러 흐름 단위(stack을 제외한 영역 공유)

데이터 레이스(Data Race)

멀티 스레드/프로세스 환경에서 공유 자원에 동시 접근할 때 일어나는 경쟁 상황을 일컫는다.
데이터 레이스가 발생하면 의도치 않은 동작이 발생할 수 있으며 이를 방지하기 위해서는 공유 자원에 대한 접근을 제한적으로 허용하는 상호배제가 필요하다.

해결 방법1 (with philosopher)

뮤텍스 및 세마포어는 비동기 안전 함수가 아니라 핸들러에서는 사용이 불가능하다.
pthread_mutex_t lock = 0; pthread_mutex_lock(&lock); // something pthread_mutex_unlock(&lock);
C

해결 방법2 (with minitalk)

시그널 핸들러에서는 volatile sig_atoimic_t를 사용해야 한다.
volatile sig_atomic_t lock = 0; lock = 1; // something lock = 0;
C

원자성(atomicity)

어떤 객체에 대한 연산은 분리될 수 없는 인터럽트 되지 않는 과정으로 이루어져야 되다는 개념이다.
공유 변수를 읽는 도중 다른 스레드에서 이 변수를 읽으면 데이터 레이스가 발생한다.
이를 방지하기 위해 읽기 및 쓰기에 대해 원장성을 보장하는 타입이나 함수 등이 존재하나 자세한 내용은 아래에서 추가적으로 다룬다.

비원자 접근 오류 예시

아래의 코드는 0 0 0 또는 1 1 1이 출력되어야 할 것 같지만 데이터 할당 중 시그널이 호출되면 그 외의 값이 출력 될 수 있다. ‘
printf는 비동기 안전 함수가 아니기 때문에 원래는 핸들러에서 호출하면 안되지만 main에서 사용하지 않고 핸들러에서만 호출하기 때문에 이 경우에는 사용이 가능하다.
#include <stdio.h> #include <unistd.h> #include <signal.h> struct two_words { int a, b, c; } memory; void handler(int signum) { printf ("%d %d %d\n", memory.a, memory.b, memory.c); } void main (void) { static struct two_words zeros = { 0, 0, 0 }, ones = { 1, 1, 1 }; signal (SIGUSR1, handler); memory = zeros; printf("pid : %d\n", getpid()); while (1) { memory = zeros; memory = ones; } }
C
$ while true; do kill -USR1 4242; done; # 테스트
Shell

휘발성(volatility)

레지스터에 공유 메모리의 값을 버퍼링하는 GCC의 최적화를 막기 위해 공유 메모리 상의 모든 객체는 volatile 속성의 타입으로 선언되어야 한다.
일반적으로 스택에 할당하는 지역 변수는 공유하지 않으므로, 서로 공유되는 전역 변수의 경우에만 필요에 따라 volatile을 사용하면 된다.
volatile은 변수 값이 비동기적으로 변동될 수 있음을 컴파일러에게 알려 GCC 최적화를 수행하지 않고 항상 메모리에 접근하게 할 수 있다.

주로 사용되는 환경

1.
멀티 쓰레드 환경
2.
메모리 맵 입출력(MIMO; Memory-mapped I/O)
3.
인터럽트 서비스 루틴(ISR; Interrupt Service Routine)
아래의 코드가 메모리 주소에 연결된 하드웨어에 특정 명령을 전달하는 임베디드 프로그램인 경우 하드웨어 오동작을 일으킬 수 있다.
*(unsigned int *)0x8C0F = 0x8001; // 컴파일러에 의해 최적화되어 사라짐 *(unsigned int *)0x8C0F = 0x8002; // 컴파일러에 의해 최적화되어 사라짐 *(unsigned int *)0x8C0F = 0x8003; // 컴파일러에 의해 최적화되어 사라짐 *(unsigned int *)0x8C0F = 0x8004; // 컴파일러에 의해 최적화되어 사라짐 *(unsigned int *)0x8C0F = 0x8005; // 마지막 연산만 수행됨
C
*(volatile unsigned int *)0x8C0F = 0x8001; // 정상 동작 *(volatile unsigned int *)0x8C0F = 0x8002; // 정상 동작 *(volatile unsigned int *)0x8C0F = 0x8003; // 정상 동작 *(volatile unsigned int *)0x8C0F = 0x8004; // 정상 동작 *(volatile unsigned int *)0x8C0F = 0x8005; // 정상 동작
C

블록과 동기

블록킹 & 논블록킹

프로세스 유휴 상태에 대한 매커니즘 (함수 실행에 대한 제어권을 넘겨주느냐 차이)
블록킹(Blocking): 제어권을 넘긴 다른 주체가 끝날 때 까지 기다리는 방식
논블록킹(Non-Blocking): 제어권을 넘기지 않고 다른 주체와 관련없이 자신의 작업을 하는 방식

동기 & 비동기

프로세스 수행 순서에 대한 매커니즘 (작업을 완료했는지 반환 값 확인 여부)
동기(Synchronous) : 다른 주체의 반환 값을 계속 확인하는 방식
비동기(Asynchronous) : 다른 주체의 반환 값을 신경쓰지 않는 방식

동기&비동기 + 블록킹&논블록킹 조합

조합
커피 주문 예시
시그널 핸들러에서의 예시
활용 예시
Async-Blocking
호출벨이 울릴 때 까지 대기
Sync-Blocking
커피가 나올 때 까지 대기
시그널을 블록하여 핸들러가 종료될 때 까지 일반 코드는 대기한다.
커맨드 입력 대기
Async-NonBlocking
다른 일 하다가 호출벨이 울리면 받으러가기
일반 코드를 반복하다가 시그널이 오면 핸들러를 실행한다.
AJAX 요청 / JS 비동기 콜백
Sync-NonBlocking
다른 일 하면서 대기번호 계속 확인
일반 코드를 반복하면서 시그널이 왔는지 플래그를 계속 확인한다.
로딩 화면 출력

비트 통신

비트 연산

11
10
01
00
A & B
1
0
0
0
A와 B가 참인가?
A | B
1
1
1
0
A 또는 B가 참인가?
A ^ B
0
1
1
0
A 와 B의 비트가 다른가?
~A
0
1
-
-
비트 반전
A << 1
10
00
10
00
비트를 1만큼 왼쪽으로 시프트
A >> 1
01
01
00
00
비트를 1만큼 오른쪽으로 시프트
비트 설정 : A |= B
비트 제거 : A &= ~B
비트 토글 : A ^= B

LSB & MSB

LSB(Least Significant Bit) : 최하위 비트
MSB(Most Significant Bit) : 최상위 비트
LSB & MSB를 왜 알아야하는가?
임베디드 시스템 등에서 직렬 통신을 할 때 송수신 데이터의 각 비트를 단위시간에 따라서 0 또는 1을 순차적으로 보낸다.
수신받은 데이터는 우측으로 데이터를 시프트(>>) 하고 MSB에 비트를 받는다.
이 때, 송신 측에서 LSB와 MSB 중 무엇을 먼저 보내냐에 따라 데이터의 값이 달라질 수 있으므로 주의해야한다.
LSB First : 리틀 엔디안 방식과 동일
MSB First : 빅 엔디안 방식돠 동일

통신 속도

시간를 재는 단위

관련 대기 함수
s (second)
10010^{0}
sleep()
㎳ (milli second)
10310^{-3}
없음
㎲ (micro second)
10610^{-6}
usleep()
㎱ (nano second)
10910^{-9}
nanosleep()
㎰ (pico second)
101210^{-12}
없음
fs (femto second)
101510^{-15}
없음
as (atto second)
101810^{-18}
없음

Bit Rate / bps(bit per second)

초당 보낼 수 있는 비트(0 또는 1)의 수.
ex) 2400bit/second(bps) 라면 초당 2400개의 비트 정보를 전달할 수 있다는 뜻이고,
이는 비트 정보를 보내기 위해서 416.6 ㎲ 의 시간(1s/2400bit)이 필요하다는 뜻이다.

Baud Rate

초당 보낼 수 있는 심볼(의미있는 데이터 묶음)의 수
baud는 신호의 크기에 따라 변경된다.
하나의 심볼이 1bit로 구성되어 있는 경우라면 bps = baud 이다.
하나의 심볼이 2bit로 구성되어 있는 경우라면 bps = baud * 2 이다.
하나의 심볼이 8bit로 구성되어 있는 경우라면 bps = baud * 8 이다.
ex) 8bit 심볼의 경우 800bps 는 100baud를 의미하고 1초에 100개의 심볼(ASCII)를 전송할 수 있다.
ex) 10bit 심볼의 경우 800bps는 80baud를 의미하고 1초에 80개의 심볼을 전송할 수 있다.통시

공용체 Union 활용

구조체와 동일하지만 공용체의 경우는 가장 큰 자료형의 공간을 공유한다.
union u_test { int num2; short num1; char c1; }; union u_test test; test.num2 = 0xFFFFFFFF; printf("%x\n", test.num2); // ffffffff test.num1 = 0; printf("%x\n", test.num2); // ffff0000
C
[참고] 네트워크 패킷 예시
[참고] 최상위, 최하위 접근 예시
[참고] 비트 필드를 통한 비트 접근 예시

패리티 비트(Parity Bit)

직렬 통신에서 데이터의 오류를 검출하기 위한 방법으로, 전송하고자 하는 데이터의 각 문자에 1비트를 더하여 전송한다.
두 개의 비트가 손실 된 경우는 알아차릴 수 없으나, 하나의 비트에 대한 오류는 검출할 수 있다.
짝수 패리티 : 프레임에 포함된 1의 개수가 짝수가 되도록 비트 구성
홀수 패리티 : 프레임에 포함된 1의 개수가 홀수가 되도록 비트 구성

해밍 코드(Hamming code)

오류 검출만 가능한 패리티 비트 대신 오류 정정까지 가능한 해밍코드를 사용해보는 것도 좋다.
2Bit 의 오류를 검출할 수 있고, 1Bit 의 오류를 교정할 수 있지만, 오류 검출 및 교정을 위한 잉여 비트가 많이 필요하다.
해밍코드 생성 규칙
1,2,4, …, 2n2^n 번째 자릿수를 패리티 비트로 둔다.
이 숫자로부터 시작하는 3개의 패리티 비트가 짝수인지, 홀수인지 기준으로 판별한다.
n 번째 패리티 비트는 n 비트에서 시작하여, n 비트 만큼을 포함하고, n 비트 씩 건너뛴 비트 들을 대상으로, 패리티 비트를 결정한다.
추가할 패리티 비트 개수는 아래 공식을 만족해야 한다.
2^p ≥ d + p + 1 (ex, 2^3 ≥ 4 + 3 + 1 )
d = 데이터 비트 수
p = 패리티 비트 수

[참고] 해밍코드 이해를 돕기 위한 예제

1 0 1 1 0 0 1 1의 해밍 코드를 받은 경우 원본 데이터는 2n2^n번째를 제외한 1 0 0 1 이다.
1 0 1 1를 홀수 해밍 코드로 변환한다고 가정하는 경우
패리티 비트는 2^n 에 위치해야므로, ? ? 1 ? 0 1 1이다.
패리티 규칙이 홀수로 정해졌으므로, 각 자리의 패리티 비트를 결정할 수 있다.
1(2^0) 번째는, 1번째부터 1개 포함 1개 스킵인 1,3,5,7번째 값인 ? 1 0 1대상이므로 1이 된다
2(2^1) 번째는, 2번째부터 2개 포함 2개 스킵인 2,3,6,7번째 값인 ? 1 1 1대상이므로 0이 된다.
4(2^2) 번째는, 4번째부터 4개 포함 4개 스킵인 4,5,6,7번째 값인 ? 0 0 1대상이므로 1이 된다
따라서, 변환된 해밍 코드는 '1 0 1 1 0 1 1' 이다.
짝수 패리티 비트를 적용한 해밍 코드가 0 0 1 1 0 1 1 일 때, 오류 수정
1, 3, 5, 7 번째 비트 확인
'0 1 0 1' 합이 짝수이며, 짝수 패리티 비트와 일치하므로 '0'
2, 3, 6, 7 번째 비트 확인
'0 1 1 1' 합이 홀수이며, 짝수 패리티 비트와 일치하지 않으므로 '1'
4, 5, 6, 7 번째 비트 확인
'1 0 1 1' 합이 홀수이며, 짝수 패리티 비트와 일치하지 않으므로 '1'
이후, 역순으로 패리티비트 '110' 을 도출할 수 있다.
이제, 2진법 '110' 을 10진법 '6' 으로 바꾼 뒤, 6 번째 비트를 수정하면 된다.
따라서, 정답은 0 0 1 1 0 '0' 1 이 된다.

유니코드

전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다.

유니코드 구조

첫번째 바이트의 시작 비트를 통해 몇 바이트인지 확인 가능하다. (하지만 과제할 때는 몰라도 되는 정보다…)
바이트 수
비트 구조
1 바이트
0xxxxxxx
2 바이트
110xxxxx 10xxxxxx
3 바이트
1110xxxx 10xxxxxx 10xxxxxx
4 바이트
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

유니코드 출력

write() 할 때 터미널에서 알아서 표시하므로 1바이트 씩 출력해도 문자열이 깨지지 않고 출력 된다.
#include <stdio.h> int main(int argc, char **argv) { for (int i = 0; argv[1][i]; i++) printf("%c\n", argv[1][i]); printf("return : "); for (int i = 0; argv[1][i]; i++) printf("%c", argv[1][i]); printf("\n"); return (0); }
C
$ cc test.c; ./a.out 안녕 � # '안'의 1번째 바이트 # '안'의 2번째 바이트 # '안'의 3번째 바이트 # '녕'의 1번째 바이트 # '녕'의 2번째 바이트 # '녕'의 3번째 바이트 return : 안녕
Shell

참고

Prev