VEDA 8주차 공부기록 (25.12.08~25.12.12)

gichang·2025년 12월 6일

2526VEDA

목록 보기
6/13
post-thumbnail

[ 25.12.08 (월) - (Linux 시스템/쉘/네트워크) ]

✅ 마이크로소프트 스토어 - ubuntu 20.04.6 LTS에 "vs 코드 프로그램" 설치 : 디렉토리 만들고 .code 실행! ✅

01. make (빌드 자동화 도구)

Embedded -> C/C++
a.c b.c c.c -> automation -> Makefile / make

1. make와 Makefile이란?

  • make
    : 파일 간의 의존성 관계를 파악하여, 변경된 파일만 선택적으로 다시 컴파일(빌드)해주는 명령어입니다. 대규모 프로젝트에서 빌드 시간을 획기적으로 줄여줍니다.

  • Makefile
    : make 명령어가 실행될 때 참조하는 설계도입니다. "무엇을(Target), 무엇으로(Dependency), 어떻게(Recipe) 만들지"가 적혀 있습니다.

    target: dependencies
    [TAB] recipe
    -> make와 직통으로 연결된다!!

  • vi 편집기에서 의존관계 활용 (구조 중요)

  • vi 편집기에서 주석 활용하기


2. 핵심 개념 3가지 (Rule의 구조)

Makefile의 가장 기본적인 문법 구조는 다음과 같습니다. 이 구조 전체를 하나의 Rule(규칙)이라고 부릅니다.

target: dependencies
[TAB] recipe

① Target (목표물)

  • 무엇을 만들 것인가? 입니다.

보통은 결과물 파일명(예: app, main.o)이 오지만, 실행할 행동의 이름(예: clean)이 올 수도 있습니다.

② Dependencies (Prerequisites / 의존성)

  • 재료가 무엇인가? 입니다.

Target을 만들기 위해 필요한 파일들의 목록입니다.

핵심 원리: 여기에 적힌 파일 중 하나라도 Target보다 '수정 시간(Timestamp)'이 최신이라면, make는 "어? 재료가 바뀌었네? 다시 만들어야겠다!"라고 판단하여 Recipe를 실행합니다.

③ Recipe (레시피 / 명령)

  • 어떻게 만들 것인가? 입니다.

실제로 실행할 쉘 명령어(예: gcc -c main.c, rm *.o)입니다.

⚠️ 주의: Recipe 줄의 맨 앞은 반드시 TAB 키로 들여쓰기 해야 합니다. (스페이스바 4칸이 아닙니다. 이것 때문에 에러가 많이 납니다.)

02. 심화 : 변수와 패턴 규칙 (Automation)

: 파일이 100개라면 모든 코드를 일일이 적을 수 없습니다. 자동 변수와 패턴 규칙(%)을 사용해 코드를 줄여봅시다.

- Makefile 수정 작성

  • 👉 실습 포인트
    : 각각 코딩했을 때와 동일하게 동작하는지 확인하세요.
    : %.o: %.c 규칙 덕분에 새로운 C 파일이 추가되어도 컴파일 규칙을 따로 적을 필요가 없어졌습니다.
    (단, OBJS 목록에는 추가해야 함)

Question) "make: 'my_calc' is up to date." 메시지?

: 그 메시지는 에러가 아니라, make가 아주 정상적으로(그리고 똑똑하게) 작동했다는 증거입니다.

  • 1. 왜 "up to date"라고 뜰까요?

make의 핵심 원리는 "재료(소스코드)가 변경되지 않았다면, 결과물(실행파일)을 다시 만들지 않는다"입니다.

  • 사용자님의 상황은 아마 이럴 것입니다

1단계 실습에서 이미 my_calc를 만들었습니다. (이때 파일이 생성됨)

그 후, 소스 코드(main.c 등)는 수정하지 않고, Makefile만 2단계 내용으로 바꿨습니다.

make 명령을 내렸습니다.

make가 확인해보니: "어? main.c보다 my_calc가 더 최신이네? 소스 안 고쳤으니까 다시 빌드 안 해도 되겠다!" 라고 판단한 것입니다.

  • 2. 해결 방법 (실습을 강제로 진행하기)

새로 작성한 2단계 Makefile이 잘 작동하는지 확인하려면, 기존에 만들어진 파일들을 지워야 합니다.

터미널에 다음 명령어를 입력하세요.
$ make clean
이 명령을 실행하면 Makefile 맨 아래에 적어둔 clean: 규칙이 실행되어 .o 파일들과 my_calc 파일이 삭제됩니다.

  • 3. 2단계 내용 상세 분석 (이게 왜 중요할까?)

방금 작성하신 2단계 코드가 왜 "심화" 과정인지, 암호 같은 기호들을 하나씩 뜯어서 이해해 봅시다. 이 기호들(자동 변수)은 임베디드 리눅스 개발자라면 숨 쉬듯이 사용하게 됩니다.

  • 작성하신 코드의 핵심 부분입니다:

03. 2차 타겟과 Suffixes

: 이번에 다룰 .SECONDARY.SUFFIXES는 make가 파일을 처리하는 '내부 동작 방식'을 제어하는 고급 기능입니다.
: 특히 빌드 최적화복잡한 빌드 체인을 설계할 때 매우 중요합니다.

1. .SECONDARY (2차 타겟 / 중간 파일 보존)

  • 개념 설명
    Make는 빌드 과정에서 생성된 중간 파일(Intermediate File)을 자동으로 삭제하려는 습성이 있습니다. 예를 들어 A -> B -> C 순서로 파일이 만들어질 때, 최종 목표가 C라면, Make는 B를 잠시 쓰고 버리는 임시 파일로 간주하여 빌드 후 지워버립니다.

  • 하지만, 디버깅을 위해 이 "중간 파일(B)을 남겨두고 싶을 때 사용하는 것"이 바로 .SECONDARY입니다.

  • 실습 : 중간 파일이 지워지는 현상 확인

  • 시나리오 : .c 파일을 전처리하여 .i 파일을 만들고, 그것을 컴파일하여 .o를 만드는 3단계를 구성해 봅시다.

01) 기존 파일 정리

02) Makefile 작성 (버전 1: 자동 삭제) - 전처리 과정을 흉내 내기 위해 cpp 대신 cp를 썼습니다.

03) 실행($ make) 및 결과 확인

결과 : 마지막에 rm main.i calc.i 같은 메시지가 뜨거나, ls를 해보면 .i 파일들이 사라져 있을 겁니다.
Make가 "이건 중간 단계니까 필요 없지?" 하고 지운 것입니다.

실습 : .SECONDARY로 파일 지키기

01) Makefile 수정 (버전 2) 맨 윗부분이나 적당한 곳에 아래 한 줄을 추가하세요.

(팁: 모든 .i 파일을 보존하고 싶다면 그냥 .SECONDARY: 라고 적고 뒤를 비워두거나, 패턴 규칙을 사용할 수 없으므로 보통 필요한 파일을 명시합니다. GNU Make 최신 버전에서는 .SECONDARY: 만 적으면 모든 중간 파일을 보존합니다.)

02) 다시 실행

결과: 이제 빌드가 끝나도 main.i와 calc.i 파일이 삭제되지 않고 폴더에 남아 있습니다. 디버깅할 때 아주 유용합니다.


2. .SUFFIXES (확장자 목록 관리)

개념 설명
: .SUFFIXES는 Make가 "내가 알아먹을 수 있는 파일 확장자는 이거야"라고 리스트를 관리하는 특수 타겟입니다.
= make에게 "이 확장자(Suffix)들을 가진 파일들은 특별한 관계가 있어!" 라고 알려주는 등록 명부입니다.

  • 과거(Legacy): 예전에는 .c.o: 처럼 접미사 규칙(Suffix Rule)을 썼는데, 이때 .c와 .o가 .SUFFIXES 목록에 있어야만 동작했습니다.

  • 현재: 요즘은 %.o: %.c 같은 패턴 규칙(Pattern Rule)을 쓰기 때문에 중요도가 낮아졌습니다.

  • 실무 활용: 오히려 기본 내장 규칙을 끄기 위해 사용합니다. Make는 기본적으로 수많은 확장자(.f, .p, .r 등)를 알고 있어서 시작할 때 이걸 로딩하느라 아주 미세하게 느릴 수 있고, 꼬일 수 있습니다. 이를 초기화할 때 씁니다.

실습 : SUFFIXES 초기화

01) 기본 목록 확인

: 터미널에 아래 명령어를 쳐보세요. Make가 기본적으로 알고 있는 규칙들을 보여줍니다.

02) Makefile에 적용

: 우리는 .c와 .o만 쓰는데, 굳이 Make가 포트란(.f)이나 파스칼(.p) 규칙을 알 필요가 없죠. 깔끔하게 비워봅시다.

이렇게 하면 Make가 엉뚱한 확장자 파일을 보고 빌드를 시도하는 실수를 방지하고, 아주 미세하지만 성능이 향상됩니다.

[ 25.12.09 (화) - (Linux 시스템/쉘/네트워크) + 교과평가 (2) ]

01. cmake

우분투 리눅스 환경에서 CMake는 C++ 프로젝트(임베디드, Qt 포함)의 표준 빌드 시스템입니다.

단순히 컴파일만 하는 것이 아니라, 복잡한 의존성을 관리하고 다양한 플랫폼에 맞는 빌드 파일(Makefile 등)을 생성해 주는 "**메타 빌드 시스템**"

02. git / github

03. docker


[ 25.12.10 (수) - (Linux 시스템/쉘/네트워크) ]

01. 교과평가(2) review

01 : (o) RAM - 마이크로 컴퓨터의 절차를 실행하지만 영구적이진 않음.
02 : (o) (메모리) 임베디드 시스템에서 주변 장치 접근시. 레지스터 제어를 위해 사용하는 "메모리 맵" (I/O 맵)
03 : (x) (레지스터) CPU의 구성요소 중, 일시적인 저장장치 단순히 ALU라고 할 수 없다.
04 : (o) 프로세서 버스 3개 : Address, data, control .... bandwidth는 아님!
05 : (x) volatile unsigned int 0x1234 - ROM
06 : (o) subs r0, r0, r0 // zero flag 켜짐.
07 : (o) Pipeline 가장 먼저 처리되는 과정 : Fetch.
08 : (x) cp, ln, ln -s 구분! 링크와 카피는 다름.
09 : (x) PID(내 ID)와 PPID(엄마 ID). 삼촌 관계...
10 : (o) while ( -gt ) -> until ( -gt )
11 : (o) 리눅스 : 트리 구조(계층적). 새로운 파일시스템을 구성해도, 루트(/) 디렉토리는 1개이다. 주요 설정 파일은 /etc에 있다.
12 : (o) sleep 프로세스 죽이기 : $ kill %2? %1?
13 : (o) chmod 644(664 X) sample : u, g, o.
14 : (x) grep ::::::::: ps -e | grep sshd.
15 : (o) tar 명령어 백업하는 것(=압축) -cvzf -cvjf
16 : (o) man make : 1_GNUmakefile 2_makefile 3_Makefile (GNU-스몰-라지)
17 : (o) 리눅스 환경에서 프로그램 컴파일 도구 : gcc
18 : (o) CMake 사용하는 용도 : 빌드 시스템 자동 생성.
19 : (o) docker pull
20 : (x) git init (처음 시작)

02. LSP (Linux System Programming)

LSP라고 하면 주로 system programming을 말한다.
OS가 제공하는 시스템 프로그래밍은 앱 만드는 것.

03. R-pi를 활용한 리눅스

임베디드 리눅스board 활용(부트로더, 커널, 디바이스 트리, 루트 파일 시스템)
마운트의 개념
루트 꼭대기에서 디렉토리에 파일시스템 붙음.
고수준 vs 저수준 I/O 방식 드라이버와 직접 통신?
File Operation, Select, OPEN READ WRITE CLOSE.
다중 입출력 방식

select { poll,epoll }
readfds { 0, .... , 1023 }
fd = {open(...)} // fd3 = 3 , 4,5,6....

[ 25.12.11 (목) - Linux 시스템/쉘/네트워크: R-pi]

01. 파일의 권한과 모드

리눅스는 다중 사용자 운영체제 : 접근권한 부여(rwx)
리눅스는 모든 것이 파일로 표현된다. (유닉스 철학)
파일의 종류와 권한, 모드 알아내기

  • stat 구조체 : 리눅스에서 "파일의 내용"과 "파일의 정보(메타데이터)"는 분리되어 저장됩니다. 이 메타데이터를 가져오는 핵심 시스템 콜이 stat(2)입니다.

하드 링크 (Hard Link)심볼릭 링크 (Symbolic Link)는 리눅스 파일 시스템의 구조와 파일 관리 방식에 대한 이해를 깊게 하는 중요한 내용입니다.

1. 📂 Inode (Index Node)의 역할

리눅스 파일 시스템에서 파일은 두 부분으로 나뉘어 관리됩니다.

  • 데이터 블록: 실제 파일 내용이 저장되는 공간.
  • Inode: 파일의 메타데이터(정보)가 저장되는 구조체입니다.

하나의 Inode에는 파일의 종류, 크기, 권한, 소유자, 타임스탬프, 그리고 데이터 블록의 위치 정보가 담겨 있습니다. 디렉토리는 파일 이름을 Inode 번호에 매핑하여 파일을 찾습니다.

핵심: Inode 번호는 파일 시스템 내에서 파일 자체를 식별하는 고유 ID입니다.

하드 링크는 원본 파일과 Inode를 공유하는 또 다른 파일 이름입니다.

핵심 특징

  • Inode 공유: 하드 링크는 원본 파일과 동일한 Inode 번호를 가리킵니다.

  • 파일 형식: 파일 시스템 커널 레벨에서는 이들이 별개의 파일이 아니라 하나의 파일에 대한 여러 개의 이름으로 간주됩니다.

  • st_nlink 증가: stat 구조체의 st_nlink (링크 수) 필드가 1 증가합니다.

  • 삭제: 원본 파일이나 하드 링크 중 하나를 삭제해도, st_nlink가 0이 될 때까지 파일의 데이터는 보존됩니다.

  • 제약:
    디렉토리에는 생성 불가 (순환 참조 문제 방지).
    다른 파일 시스템(파티션)을 넘어서 생성 불가 (Inode 번호는 파일 시스템 내에서만 고유).

심볼릭 링크는 원본 파일의 경로명(pathname)을 담고 있는 새로운 별도의 파일입니다. 윈도우의 '바로 가기'와 가장 유사한 개념입니다.

핵심 특징

  • 별도의 Inode: 심볼릭 링크는 새로운 고유의 Inode 번호를 가집니다.

  • 파일 내용: 링크 파일의 데이터 블록에는 원본 파일의 절대 경로 또는 상대 경로 문자열이 저장됩니다.

  • 파일 종류: ls -l로 보면 파일 종류가 l (link)로 표시됩니다.

  • 유연성:
    디렉토리에도 생성 가능.
    다른 파일 시스템을 넘어서도 생성 가능.
    단점 (Dangling Link): 원본 파일을 삭제하면, 링크 파일이 가리킬 대상이 사라져 접근할 수 없게 됩니다 (Broken Link 또는 Dangling Link).

03. 리눅스 파일 관리

표준입력/출력/에러
Redirection
fd2 = dup(fdt)
입출력 버퍼 (I/O Buffer)

04. 리눅스 파일의 권한변경 : umask, chmod, chown

파일의 존재(stat)와 연결 방식(link)을 이해했으니, 이제 "누가 이 파일에 접근할 수 있는가?"를 결정하는 권한 관리(Permission Management)의 핵심인 umask프로세스 권한 개념을 파헤쳐 봅시다.

1. 핵심 개념: umask (User Mask)

리눅스에서 파일이나 디렉토리를 생성할 때, 시스템은 모든 권한(Full Permission)을 기본적으로 주지 않습니다. 보안을 위해 "기본적으로 제외할 권한"을 설정해 두는데, 이것이 바로 umask입니다.

1-1. 동작 원리 (Bitwise Operation)

umask는 파일 생성 시 요청한 모드(mode)에서 특정 비트를 끄는(masking) 역할을 합니다. 최종 권한은 다음 공식으로 결정됩니다.

즉, umask에 설정된 비트는 최종 권한에서 사라집니다.

  • 파일의 최대 권한: 0666 (rw-rw-rw-) (실행 권한은 보안상 기본적으로 제외됨)
  • 디렉토리의 최대 권한: 0777 (rwxrwxrwx)

1-2. 계산 예시 (8진수)

umask가 022일 때, 파일 생성 시 0666을 요청하면 어떻게 될까요?

결과적으로 Group과 Other의 w(쓰기) 권한이 제거되었습니다.


2. 시스템 콜: umask(2)

: C 프로그램 내부에서 프로세스의 umask를 변경할 때 사용합니다.

  • 인자: 새로 설정할 umask 값 (예: 022, 077).

  • 반환값: 이전(변경 전)의 umask 값을 반환합니다.

  • 특징: 이 설정은 해당 프로세스(및 자식 프로세스)에만 영향을 미치며, 쉘의 기본 umask를 영구적으로 바꾸지는 않습니다.


3. 또 하나의 개념: 프로세스 권한 (RUID vs EUID)

파일에 접근할 때 커널은 "이 프로세스가 파일의 주인인가?"를 확인합니다. 이때 사용하는 ID가 두 가지 있습니다.

  • RUID (Real User ID): 프로세스를 실제로 실행한 사용자의 ID. (누가 켰니?)

  • EUID (Effective User ID): 현재 권한 검사에 사용되는 ID. (누구 행세를 하니?)

보통은 RUID == EUID이지만, Set-UID 비트가 설정된 실행 파일(예: passwd)을 실행하면, 프로세스 실행 중에는 EUID가 파일 소유자(root)로 바뀝니다.

05. 디렉토리(directory)

06. 리눅스에서 시간 다루기 : locale, tm 구조체

리눅스 시스템 프로그래밍에서 시간을 다루는 것은 로그 기록, 스케줄링, 성능 측정 등에서 필수적입니다.

  • 핵심은 "기계가 이해하는 시간(정수)""사람이 이해하는 시간(구조체/문자열)"로 변환하는 흐름을 이해하는 것입니다.

cf. 여기에 Locale(로케일) 개념을 더하면, 사용자의 언어와 지역에 맞는 날짜 형식을 출력할 수 있습니다.

1. 시간의 흐름과 핵심 자료형

리눅스 시간 처리의 전체적인 흐름은 다음과 같습니다.

1-1. Unix Timestamp (time_t)

  • 정의: 1970년 1월 1일 00:00:00 (UTC)부터 현재까지 흐른 초(Seconds)를 나타내는 정수입니다.

  • 특징: 시스템 내부적으로 시간을 저장하거나 연산할 때 가장 효율적입니다.

  • 함수: time(time_t *t)

1-2. Broken-down Time (struct tm)

  • 정의: time_t를 년, 월, 일, 시, 분, 초 단위로 쪼개서 담은 구조체입니다.

  • 주의사항 (함정):
    tm_year: 1900년 이후의 연도 (예: 2023년은 123)
    tm_mon: 0부터 시작하는 월 (0 = 1월, 11 = 12월)

  • 변환 함수:
    localtime(): 시스템의 Timezone 설정에 맞춰 변환 (한국이면 KST).
    gmtime(): UTC(협정 세계시) 기준으로 변환.

1-3. 문자열 포맷팅 (strftime)

  • printf처럼 포맷 지정자를 사용하여 struct tm을 원하는 형태의 문자열로 만듭니다.

  • Locale의 영향을 받습니다. (예: %p가 "AM/PM"이 될 수도, "오전/오후"가 될 수도 있음)

2. Locale (로케일)의 이해

: Locale은 언어, 국가, 문자 인코딩 등의 지역 설정을 정의합니다. 날짜 포맷뿐만 아니라 화폐 단위, 숫자 표기법 등에도 영향을 줍니다.

  • 환경 변수: LANG, LC_ALL, LC_TIME 등으로 설정.
  • C 함수: <locale.h>의 setlocale()을 사용하여 프로그램 내에서 로케일을 설정합니다.
  • setlocale(LC_ALL, "C"): 기본 POSIX 모드 (영어).
  • setlocale(LC_ALL, ""): 현재 시스템의 환경 변수를 따름 (권장).

07. R-pi 온/습도 센서 활용

(C언어) GPIO2 핀과 wiringPi 라이브러리를 활용해서 실습을 진행해보기.
(참고 : 네이버 블로그 "베어팹")

#include <wiringPi.h>         // wiringPi 라이브러리 사용
#include <stdio.h>            // 표준입출력용 라이브러리  
#include <stdlib.h>           // 표준 유틸리티용 라이브러리
#include <stdint.h>           // 정수 자료형 라이브러리

#define MAX_TIMINGS	85        // 최대 신호 추출 개수
#define DHT_PIN		2	      // GPIO로 사용할 핀 번호

int data[5] = { 0, 0, 0, 0, 0 };       // 온습도 및 checksum 데이터 저장용 변수 배열

void read_dht_data()                    // dht데이터 읽기 함수
{
	uint8_t laststate	= HIGH;          // DHT핀의 상태 저장용 변수(현재 신호가 HIGH인지 LOW인지 확인하기 위한 용도)
	uint8_t counter		= 0;             // 신호의 길이를 측정하기 위한 카운터 변수
	uint8_t j			= 0, i;          // 40개의 신호 인덱스 용 변수

	data[0] = data[1] = data[2] = data[3] = data[4] = 0;    //초기 데이터 값은 0으로 지정

	/* DHT11센서와의 통신을 개시하기 위해 DATA핀을 18ms동안 LOW로 출력 */
	pinMode( DHT_PIN, OUTPUT );
	digitalWrite( DHT_PIN, LOW );
	delay( 18 );

	/* 핀을 입력모드로 설정해서 DHT11로 부터 응답을 기다림 */
	pinMode( DHT_PIN, INPUT );

	/* DHT11에서 오는 신호 검출 및 데이터비트 추출 */
	for ( i = 0; i < MAX_TIMINGS; i++ )       // 총 85번 동안 신호를 확인
	{
		counter = 0;                           // 초기 카운터는 0
		while ( digitalRead( DHT_PIN ) == laststate ) //DHT핀의 신호를 읽어서 현재 지정한 DATA핀 신호와 같은 동안==즉 신호의 변환이 없는 동안
		{
			counter++;                              // 카운터 변수 1 증가
			delayMicroseconds( 1 );                 // 1uS(마이크로초) 동안 대기
			if ( counter == 255 )                  // 카운터가 255까지 도달하면, 즉 너무 오래 동안 대기하면==오류가 생겼다는 의미 임
			{
				break;                              // 카운터 세기 중지
			}
		}
		laststate = digitalRead( DHT_PIN );       // 현재 핀 상태 저장

		if ( counter == 255 )                     // 카운터가 255이상 도달했다면, 데이터비트 수신 중지== for문 밖으로 나가서 처음부터 새로 받겠다는 의미임
			break;

		/* 첫번째 3개의 신호는 무시하고 짝수번째에 해당하는 신호길이를 읽어 0인지 1인지에 따라 온습도 변수에 저장
           첫번째 3개의 신호는 DHT11의 응답 용 신호로 실제 데이터 길이를 통해 정보를 수신하는 값이 아니므로 무시함.
           짝수만 추출하는 이유는 짝수 번째의 신호 길이에 따라 해당 신호가 0을 의미하는지 1을 의미하는지를 나타냄. 
         */     
		if ( (i >= 4) && (i % 2 == 0) )   // 4번째 이후의 신호이면서 짝수번째 신호라면 
		{
			/* 가각의 데이터 비트를 온도 및 습도 변수에 하나씩 넣어줌 */
			data[j / 8] <<= 1;                    // 이진수의 자리수를 하나씩 옆으로 이동시킴
			if ( counter > 16 )                    // 카운터의 값이 16보다 크다면, 즉 신호의 길이가 길어서 비트 1로 인식된다면
				data[j / 8] |= 1;                  // 해당 비트는 1을 넣어줌
			j++;                                 // 다음 데이터를 위해 인덱스 값을 하나 증가 시킴
		}
	}

	/*
	 * 40비트를 다 확인했다면 (8비트 x 5 ) 체크섬 데이터와 오류체크를 해서
	 * 오류가 없으면 데이터를 출력함.
     */
	if ( (j >= 40) && (data[4] == ( (data[0] + data[1] + data[2] + data[3]) & 0xFF) ) )
	{        //에러가 없으면 습도 및 온도 출력
		printf( "Humidity = %d.%d %% Temperature = %d.%d C\n", data[0], data[1], data[2], data[3]);
	}else  {
		printf( "Data not good, skip\n" );      //에러 발생시 Data not good 메시지 출력
	}
}

int main( void )
{
	printf( "Raspberry Pi DHT11 temperature/humidity test\n" );     

	if ( wiringPiSetupGpio() == -1 )    //라즈베리파이의 BCM GPIO 핀번호를 사용하겠다고 선언
		exit( 1 );

	while ( 1 )
	{
		read_dht_data();              // 온도 및 습도 데이터 획득 및 출력
		delay( 2000 );                // 다음 읽기까지 2초 대기
	}

	return(0);
}
  • 코드 설명 -

위 코드는 DHT11과 데이터 통신을 하는 방법을 어느정도 알고 있어야 이해할 수 있습니다. 아래 사진과 같이 DHT는 총 40쌍의 데이터 정보를 LOW-HIGH 순으로 보내고 이때 보내지는 HIGH의 길이로 비트가 0인지, 1인지 구분을 합니다. 따라서 총 신호의 개수는 80개이고 사전 준비신호, 응답신호, 종료신호까지 포함해서 85개의 신호를 받아서 그중 데이터를 가려내는 코드입니다.

  • 파일을 원하는 이름으로 저장하고 아래 명령어를 통해 컴파일해 줍니다. 저는 dht11.c라는 이름으로 지정했습니다.
    gcc -o dht11 dht11.c -lwiringPi

  • 컴파일 옵션에 반드시 -lwiringPi 를 포함시켜야 합니다. 이 옵션은 라이브러리를 지정해 줄때 사용합니다. 우리는 wiringPi 라이브러리를 사용하기 때문에 이 옵션을 지정하지 않으면 오류가 납니다.

  • 이제 컴파일된 실행파일을 아래 명령어로 실행해 줍니다.
    ./dht11

그런데 위의 결과화면을 보시면 온도와 습도 정보가 2~3번에 한번꼴로 오류가 나는 것을 확인 할 수 있습니다. 위에서도 이야기 했듯이 라즈베리파이는 여러 프로세스가 실행되는 운영체제 환경이기 때문에 실시간으로 하나의 프로그램에 상주하며 정확한 제어가 안됩니다. 따라서 읽어오는 값이 오류가 꽤 발생 할 수 있습니다. (위 파이썬 예제에서도 15번을 시도해서 그중에 오류가 안나는 경우 출력해주는 프로세스 였습니다.)

참고로 위 코드 중 counter변수의 비교문에 들어간 16이라는 숫자는 HIGH신호와 LOW신호의 길이를 구분하는 기준값인데요. 라즈베리파이의 성능이나 현재 사용하고 있는 프로세스나 CPU의 부하 정도에 따라서 값이 조금씩 달라지는 것 같습니다. 라즈베리파이 포럼에서 반복해서 오류가 날 때 이 숫자를 50으로 바꾸니까 잘 동작한다는 내용도 있었습니다. 자신의 환경에 따라서 조금씩 수정해 보시면 될 것 같네요^^


✅ 1. 이어서 학습해야 할 핵심 개념 (C언어 + 리눅스 기반)

1) Linux에서 GPIO 제어 방식

  • 우분투/라즈비안 최신 커널에서는 다음 두 방식이 중요합니다.

✔ A) sysfs 방식 (옛 방식 → 여전히 많은 자료가 존재)

  • 경로: /sys/class/gpio/
  • export/unexport
  • direction
  • value

✔ B) libgpiod 방식 (최신 Linux 표준)

  • 커널 4.8 이후 공식 방식
    우분투에서 기본 제공 패키지 있음.

2) GPIO 전기적 특성

  • 3.3V 로직
  • 입출력 구분
  • 풀업/풀다운

3) DHT11 센서 동작 원리

DHT11은 단일 GPIO 핀을 이용한 비동기 프로토콜 사용.
통신 방식 요약:
1. MCU가 핀을 LOW 18ms 유지(센서 깨우기)
2. MCU가 HIGH → LOW 전환
3. DHT11이 자체 응답 신호를 보냄
4. 이후:

  • 50µs LOW (비트 시작)
  • HIGH 지속시간:
  • 약 26–28µs → 0
  • 약 70µs → 1

✅ 1. DHT11 통신 구조 이해 (통신 이미지 해석)

아래 이미지는 DHT11의 전체 통신 시퀀스를 시간축으로 표현한 것이다.

📌 통신 흐름은 크게 3단계:

  • 통신 개시 신호 (Start Signal)
  • DHT11의 응답 신호 (Response Signal)
  • 온도/습도 40비트 데이터 송신

✅ 2. WiringPi로 구현할 때의 관점에서 이미지 해석

▶ 1) MCU(라즈베리 파이 → wiringPi 코드)에서 "통신 개시 알림"

✔ 핵심 해석

  • DHT11은 스스로 데이터를 보내지 않고, MCU의 요청을 기다린다.
  • MCU가 18ms 동안 LOW를 넣으면 “데이터 좀 줘!”라고 명령하는 것.
  • 그리고 HIGH 상태를 40µs 주면 신호 마무리.
  • 그 뒤 INPUT 모드로 바꿔야 DHT11의 응답을 읽을 수 있다.

▶ 2) DHT11의 응답 신호 (Response Signal)

✔ 핵심 해석

  • DHT11은 MCU의 요청을 확인하면 먼저 80µs LOW 신호를 보낸다.
  • 이어서 80µs HIGH 신호를 보낸다.
  • 이것이 "응답했다"는 의미.
  • 이 상태가 정상적으로 감지되지 않으면 checksum 오류 or 센서 미연결 에러.

▶ 3) 온도/습도 데이터 40비트 송신 구간

✔ 핵심 해석
비트 구분 원리

  • 50µs LOW는 항상 일정
  • 그 뒤 HIGH 시간을 보고 0/1 판단
  • 하지만 HIGH의 정확한 시간을 코드에서 측정하는 것은 어렵다

그래서 보통 30µs 지점을 샘플링한다.

  • HIGH면 → 1
  • LOW면 → 0

이 방식은 wiringPi + DHT11 코드에서 가장 일반적인 구현 방식이다.

▶ 4) DHT11 데이터 40비트 구조 해석

08. Rpi 실습평가(2)_ p.132 - 라즈베리 파이와 GPIO : 하드웨어 제어

3.2 LED 제어 프로그래밍

# 라즈베리 파이 windows에서 원격으로 연결

# 라즈베리파이 고정 IP 설정

# 라즈베리파이 절전모드 해제

09. PWM (Pulse Width Modulation) 제어

PWM 시그널로 할 수 있는 것들?

1. PWM 핵심 이론

: "디지털은 0과 1밖에 없는데, 어떻게 LED 밝기를 조절할까?" 이 질문이 PWM의 시작입니다.

  • 라즈베리파이 CPU(SoC)는 디지털 장치라서 3.3V(ON) 아니면 0V(OFF)만 출력할 수 있습니다. 1.5V 같은 중간 전압을 못 냅니다.

그래서 "엄청나게 빨리 껐다 켰다"를 반복해서 평균 전압을 조절하는 꼼수를 쓰는데, 이게 바로 PWM입니다.

# 핵심 용어 2가지 (이건 꼭 외워야 합니다)

01) 주기 (Period) / 주파수 (Frequency)

  • 스위치를 껐다 켜는 한 세트의 시간입니다.

  • 이게 너무 느리면 우리 눈에 깜빡거림(Flickering)이 보입니다. 보통 LED는 60Hz~100Hz 이상이면 사람 눈에는 계속 켜져 있는 것처럼 보입니다.

02) 듀티 사이클 (Duty Cycle):

  • "한 주기 안에서 전기가 들어와 있는 시간의 비율"입니다.

  • 50% Duty: 절반은 켜고 절반은 끔 → 3.3V의 절반인 약 1.65V 효과 (밝기 50%)

  • 25% Duty: 1/4만 켜둠 → 약 0.8V 효과 (밝기 25%)

요약 : 디지털 신호의 폭(Width)을 조절(Modulation)해서 아날로그 신호처럼 흉내 내는 기술.

PWM(Pulse Width Modulation, 펄스 폭 변조)은 디지털 신호로 아날로그 같은 효과(LED 밝기 조절, 모터 속도 제어)를 내는 기술로, 임베디드 리눅스 제어의 핵심입니다.

임베디드 리눅스의 매력은 "모든 하드웨어를 파일처럼 다룬다"는 점입니다.

코딩(C++) 전에, 터미널에서 리눅스 명령어로 직접 하드웨어 칩을 건드려 보겠습니다. (이 과정이 진짜 임베디드 리눅스 공부입니다.)

터미널을 열고 아래 명령어들을 한 줄씩 입력하며 LED 반응을 보세요.

💡 준비물

  • 하드웨어: 라즈베리파이, 브레드보드, LED 1개, 저항(220Ω~330Ω) 1개

  • 연결: LED의 긴 다리(+)를 GPIO 18번(물리 핀 12번)에, 짧은 다리(-)를 GND에 연결하세요. (GPIO 18번이 하드웨어 PWM을 지원하는 대표적인 핀입니다.)

2. 임베디드 리눅스 관점

"모든 것은 파일이다"
이 부분이 리눅스 시스템 프로그래밍의 핵심입니다.

  • 윈도우와 다르게, 리눅스 커널은 하드웨어 제어권을 파일 시스템으로 노출시킵니다.

우리가 C++ 코드를 짜든 파이썬을 쓰든, 결국 내부적으로는 리눅스 커널의 특정 파일에 숫자를 쓰는 행위가 일어납니다.

  • 경로 : /sys/class/pwm/pwmchip0/

  • 동작 원리 :
    01) export 파일에 0을 쓰면 → 커널이 "아, 0번 PWM 채널을 쓰겠구나" 하고 메모리를 할당합니다.
    02) period 파일에 10000000(ns)을 쓰면 → 하드웨어 타이머가 10ms 주기로 세팅됩니다.
    03) duty_cycle 파일에 5000000(ns)을 쓰면 → 5ms 동안만 전기를 내보냅니다.

이론 학습 포인트: "코딩은 결국 이 파일들에 값을 적는 과정을 자동화하는 것뿐이다"라는 점을 이해하는 것이 중요합니다.


3. 코드 분석 (WiringPi 라이브러리)

하드웨어 없이 코드를 눈으로 읽으며 로직을 이해해 봅시다. 이 코드는 위의 복잡한 파일 제어 과정을 함수로 포장해 둔 것입니다.

#include <wiringPi.h>

// BCM GPIO 18번은 하드웨어 PWM을 지원하는 핀입니다.
// WiringPi 라이브러리에서는 이 핀을 '1번'으로 매핑합니다.
#define LED_PIN 1 

int main() {
    wiringPiSetup(); 

    // [핵심 1] 핀 모드 설정
    // 일반적인 디지털 출력(OUTPUT)이 아니라, PWM 신호를 쏘겠다고 설정합니다.
    // 이때부터 이 핀은 CPU가 0/1을 직접 제어하지 않고, 별도의 PWM 컨트롤러가 담당합니다.
    pinMode(LED_PIN, PWM_OUTPUT); 

    // [핵심 2] 값 쓰기 (Range: 0 ~ 1024)
    // WiringPi는 기본적으로 0(0%) ~ 1024(100%) 범위를 사용합니다.
    
    // 밝기 50% (Duty Cycle 50%)
    pwmWrite(LED_PIN, 512); 
    
    // 밝기 100% (Duty Cycle 100% - 계속 켜짐)
    pwmWrite(LED_PIN, 1024);
    
    // 밝기 0% (Duty Cycle 0% - 꺼짐)
    pwmWrite(LED_PIN, 0);

    return 0;
}

4. Velog 추가 작성 가이드 (시간 없을 때)

: 지금 작성 중인 블로그에 하드웨어 사진 대신, 이 이론을 정리해서 올리면 아주 훌륭한 기술 포스팅이 됩니다.

  • 제목: [Linux/System] PWM의 원리와 리눅스 커널 인터페이스

  • 내용 1 (개념): 디지털 신호로 아날로그를 제어하는 원리 (Duty Cycle 그림 설명)

  • 내용 2 (커널): /sys/class/pwm 디렉토리를 통해 하드웨어를 파일처럼 제어한다는 사실 (이게 면접관들이 좋아하는 포인트입니다).

  • 내용 3 (코드): pinMode와 pwmWrite가 내부적으로 어떤 의미인지 해석.

하드웨어 연결은 나중에 시간 날 때 하고, 오늘은 이 "소프트웨어가 하드웨어를 제어하는 메커니즘"을 이해하는 것으로 마무리하셔도 충분합니다!

[ 25.12.12 (금) - (Linux 시스템/쉘/네트워크) ]

: LED 제어와 택트 스위치 입력은 가장 기초적이지만, GPIO(General Purpose Input/Output)의 동작 원리와 리눅스 시스템 프로그래밍(LSP)의 기초인 "파일로서의 장치" 개념을 익히기에 가장 좋은 주제.

01. R-pi 택트 스위치

# 빵판(Bread Board) 사용법.

  • 전원(Power/VCC,VDD)은 기기에 에너지를 공급하는 '살아있는 선'(전압선, 중성선 포함), 일반적으로 양극(+) 전압을 가지며, 회로 내의 모든 부품에 전력을 제공

  • 접지(Ground/GND)는 안전을 위해 기기 외함이나 회로 기준점을 '땅(대지)'에 연결해 누설 전류를 흘려보내 감전을 막고 기기를 보호하는 '안전망', 배터리의 음극(-) 단자가 GND 역할. 회로 내 모든 전압을 측정하는 공통 기준점(0V)입니다. 또한 전류가 전원 소스로 돌아가는 복귀 경로(return path) 역할

( cf. 전원은 전류가 흐르며 전기를 사용하게 하지만, 접지는 평상시엔 전류가 거의 흐르지 않다가 문제가 생겼을 때만 작동해 전류를 땅으로 빼돌리는 역할 )

# 핵심 개념 정리 (이것만은 꼭!)

이 실습을 통해 이해해야 할 핵심 개념 3가지입니다.

  • GPIO (General Purpose Input/Output)
    : 핀을 입력(Input)으로 쓸지 출력(Output)으로 쓸지 프로그래머가 '방향(Direction)'을 정해야 합니다. (스위치는 Input, LED는 Output)

  • Sysfs 인터페이스 (/sys/class/gpio)
    : 리눅스 커널은 하드웨어 제어권을 파일 시스템 형태로 노출합니다. 우리가 파일을 쓰고 읽는 행위가 실제로는 커널을 통해 하드웨어의 레지스터를 조작하는 과정이 됩니다.

  • 플로팅(Floating) 상태풀업/풀다운
    : 스위치를 눌렀을 때는 0V(GND)나 3.3V가 연결되지만, 떼었을 때는 연결이 끊어진 상태가 됩니다. 이때 전압이 0도 아니고 1도 아닌 상태로 둥둥 떠다니는 것'플로팅'이라고 합니다. 이를 방지하기 위해 풀업(Pull-up) 또는 풀다운(Pull-down) 저항을 사용하여 기본 상태를 잡아줘야 합니다.

02. 스피커의 사용

  • 라즈베리파이에 피에조 부저를 연결하려면, 부저의 양극(+)을 라즈베리파이의 GPIO 핀 (예: 12번, 18번 등)에, 음극(-)을 GND 핀에 연결하고, 파이썬으로 PWM 신호를 보내 주파수를 제어하여 다양한 소리(음계, 멜로디)를 만들 수 있습니다.

  • 액티브 타입은 전원만 연결하면 되고, 패시브 타입은 PWM 신호 처리가 필요하며, 부저 모듈을 사용하면 더 안정적

1. 연결 방법 (패시브 피에조 부저 기준)

  • (+) 핀 (신호선) : 라즈베리파이의 원하는 GPIO 핀 (예: GPIO 12번).
  • (-) 핀 (접지) : 라즈베리파이의 GND 핀.
  • 팁 : 저항이 포함된 부저 모듈을 사용하면 포트 보호 및 전류 공급에 유리합니다.

2. 액티브 부저 vs. 패시브 부저

  • 액티브 부저: 내부에 발진 회로가 있어 전원만 연결하면 정해진 소리(삐삐삐)가 납니다. 단순 알림에 적합합니다.
  • 패시브 부저 (피에조): 주파수 제어를 통해 다양한 음계를 만들 수 있으며, PWM 신호 처리가 필요합니다. 위 코드 예시는 패시브 부저용입니다.

3. 스피커 연결 (약한 소리)

  • 스피커 모듈/앰프 필요: 라즈베리파이의 GPIO 출력은 약해서 일반 스피커를 바로 연결하면 소리가 매우 작습니다.
  • 소리 출력을 위해서는 별도의 앰프 모듈 (예: PAM8403)과 연결하고, 스피커를 앰프 출력단에 연결해야 합니다

03. 조도 센서(Illuminance) : BH1750

: 빛의 밝기에 따라 저항값이 변하는 황화카드뮴(CdS) 센서로, 빛이 밝으면 저항이 낮아지고 어두우면 높아져 이를 GPIO 핀을 통해 읽어내어 자동 조명, 밝기 조절 등 다양한 스마트 환경 제어에 활용함.

1. 조도센서의 원리

  • CdS(황화카드뮴) 광도전 효과: 빛을 받으면 전기 저항이 감소하는 특성을 이용한 센서로, 빛이 강할수록 저항이 낮아지고 빛이 약할수록 저항이 높아집니다.
  • 저항값 변화: 밝은 곳에서 약 1kΩ, 어두운 곳에서 10MΩ 이상으로 변화하며, 빛의 세기(룩스, lx)에 따라 변합니다.

2. 라즈베리파이 연결 방법

  • 아날로그 신호 변환: 라즈베리파이의 GPIO 핀은 아날로그 신호를 직접 읽지 못하므로, ''ADC 변환''이 필요합니다.
    • ADC 칩 사용: MCP3008 같은 8채널 ADC 칩을 사용하여 여러 센서의 아날로그 값을 디지털로 변환 후 SPI 통신으로 읽어옵니다.
    • 전압 분배 회로: 조도센서와 고정 저항(예: 10kΩ)을 직렬로 연결하고, 이 중간 지점의 전압 변화를 라즈베리파이 GPIO로 읽어 빛의 변화를 감지하는 회로를 구성할 수 있습니다.
  • 극성 없음: 조도센서 자체에는 극성이 없어 방향에 상관없이 연결할 수 있습니다.

3. 주요 활용 분야

  • 자동 조명: 어두워지면 자동으로 켜지는 가로등이나 실내 조명 제어.
  • 스마트 기기: 밝기에 따라 화면 밝기가 변하는 디스플레이 제어.
  • 환경 모니터링: 농장이나 스마트팜에서 식물 생장 환경의 빛 밝기 측정.

04. (DC모터의 활용 : skip_파손위험)

05. 7447을 이용한 7세그먼트 연결

< 구분법 >

① 커먼 캐소드 (Common Cathode, 공통 음극) - "직관적"

  • 구조: 모든 LED의 -극(Cathode)이 하나로 묶여 있습니다.
  • 연결: 공통 핀(COM)을 GND(0V)에 연결합니다.
  • 제어 로직 (Active High):
    GPIO에 1 (High, 3.3V)을 주면 켜집니다.
    GPIO에 0 (Low, 0V)을 주면 꺼집니다.
  • 특징: 우리가 생각하는 "1이면 켜진다"는 논리와 같아서 입문자가 이해하기 편합니다.

② 커먼 에노드 (Common Anode, 공통 양극) - "반전 주의"

  • 구조: 모든 LED의 +극(Anode)이 하나로 묶여 있습니다.
  • 연결: 공통 핀(COM)을 3.3V(VCC)에 연결합니다.
  • 제어 로직 (Active Low):
    GPIO에 0 (Low, 0V)을 주면 전위차(3.3V - 0V)가 생겨 전류가 흐르므로 켜집니다.
    GPIO에 1 (High, 3.3V)을 주면 양쪽 전압이 같아져서 꺼집니다.
  • 특징: 논리가 반대입니다. (0을 줘야 켜짐). 실무에서는 전류 허용량(Sink Current) 때문에 에노드 방식을 선호하는 경우도 많습니다.

: 테스트장비는 "캐소드"임

  • 캐소드는 GND(검은선)를 아래에 연결함.
  • 에노스는 아직 테스트 해보질 않았는데, 5v 또는 3.3v 를 공통에 연결할듯 싶다.

7-세그먼트 LED(1.8v)는 높은 전압(라즈베리파이 3.3v)에 약하므로 저항을 꼭 연결해야 함.

[출처] RaspberryPi(라즈베리파이) 7-세그먼트(캐소드-Cathode) 숫자표기|작성자 hhgintro

06. 프로세스(Process) : 교재 p.262 (Chapter 5. 프로세스와 스레드 : 다중 처리)

# "개념 이해 -> 생성 및 관리 -> 통신(IPC) -> 경량화(스레드) -> 하드웨어 제어 적용"

5.1 프로세스와 시그널 (기초 다지기)

: 가장 먼저 '프로세스'가 무엇인지 정의하고, 이를 리눅스 쉘(Shell)에서 확인하고 제어하는 방법을 배웁니다.

nice 명령어는 5.1.2 프로세스 관련 명령어 부분에서 아주 중요하게 다뤄지는 개념입니다. 특히, 라즈베리 파이처럼 자원이 한정된 임베디드 기기에서는 시스템 효율을 위해 자주 사용됩니다.

1. nice의 개념: "얼마나 양보할 것인가?"

리눅스 스케줄러(CPU 자원 분배자)에게 "나는 착한(nice) 프로세스니까 CPU를 남들에게 좀 양보할게"라고 말하는 정도를 설정하는 명령어입니다.

  • 값이 클수록: "나는 착하다(Nice하다)." -> 우선순위가 낮음 (CPU를 덜 씀, 남에게 양보).
  • 값이 작을수록: "나는 안 착하다(Not nice)." -> 우선순위가 높음 (CPU를 독점하려 함).

2. 범위 (Range)

Nice 값(NI)은 -20에서 +19 사이의 정수를 가집니다.

💡 라즈베리 파이에서의 활용 팁 (경험담)

: 라즈베리 파이에서 코드를 빌드할 때(make 등), CPU 점유율이 100%로 치솟으면서 마우스가 버벅거리거나 원격 연결이 끊길 때가 있죠?
-> 이때 nice를 활용하면 "빌드는 계속 하되, 내 마우스 움직임(UI)이나 원격 접속을 방해하지 마라"라고 할 수 있습니다.

<주요 개념>

  • 프로세스(Process): 실행 중인 프로그램. (코드 이미지 + 메모리 + 레지스터 상태)
  • PID (Process ID): 각 프로세스에 부여되는 고유 식별 번호.
  • 시그널(Signal): 프로세스에게 비동기적인 사건을 알리는 소프트웨어 인터럽트 (예: Ctrl+C).

<학습할 핵심 내용>

  • 프로세스의 상태 전이 (Running, Sleeping, Stopped, Zombie).
  • 명령어: ps, top, htop, kill, pgrep 등을 통해 R-Pi에서 돌아가는 프로세스 목록을 확인하고 종료시키는 법.
  • 시그널 핸들링: 프로그램 코드 내에서 특정 시그널(SIGINT 등)을 받았을 때 종료하지 않고 특정 동작을 하도록 처리하는 법 (signal(), sigaction()).

5.2 멀티 프로세스와 다중 처리 프로그래밍 (생성 및 관리)

: 프로그램 코드(C언어 등)를 통해 직접 프로세스를 만들고 관리하는 시스템 호출(System Call)을 배웁니다.

<주요 개념>

  • 부모/자식 프로세스: 프로세스는 계층 구조를 가집니다.
  • init (또는 systemd): 리눅스 부팅 시 가장 먼저 실행되는 조상 프로세스 (PID 1).
  • Copy-on-Write (CoW): 프로세스 복제 시 메모리 관리 효율성 기법.

<학습할 핵심 내용>

  • 시스템 콜 3대장:
  • fork(): 현재 프로세스를 복제하여 자식 프로세스 생성.
  • exec() 계열: 현재 프로세스의 메모리를 새로운 프로그램으로 덮어씀.
  • wait() / waitpid(): 자식 프로세스가 종료될 때까지 기다림 (좀비 프로세스 방지).
  • 고아(Orphan) & 좀비(Zombie) 프로세스: 부모가 먼저 죽거나, 자식의 종료를 처리해주지 않았을 때 발생하는 문제와 해결법.

5.3 프로세스 간 통신 (IPC: Inter-Process Communication)

: 프로세스는 독립적인 메모리 공간을 가지므로 서로 변수를 공유할 수 없습니다. 데이터를 주고받기 위한 특별한 기법(IPC)들을 배웁니다.

<주요 개념>

  • IPC: 프로세스들끼리 데이터를 주고받는 메커니즘.
  • 동기화: 데이터를 주고받을 때 순서를 맞추는 것.

<학습할 핵심 내용>

  • 파이프(Pipe): 부모-자식 간의 단방향 통신 (가장 기초적).
  • FIFO (Named Pipe): 서로 관련 없는 프로세스 간의 통신 (파일 시스템 경로 사용).
  • System V vs POSIX IPC:
  • 공유 메모리(Shared Memory): 가장 빠르지만 동기화 필요.
  • 메시지 큐(Message Queue): 데이터를 패킷 단위로 전송.
  • 세마포어(Semaphore): 공유 자원 접근 제어를 위한 카운터 (동기화 도구).

5.4 POSIX 스레드와 동기화 (경량화 및 병렬 처리)

: 프로세스는 생성 비용(오버헤드)이 큽니다. 이를 해결하기 위해 메모리를 공유하는 스레드(Thread)를 배웁니다.

<주요 개념>

  • 스레드(Thread): 프로세스 내의 실행 흐름 단위 (LWP: Light Weight Process).
  • 경쟁 상태(Race Condition): 여러 스레드가 동시에 같은 자원을 수정하려 할 때 데이터가 깨지는 현상.

<학습할 핵심 내용>

  • pthread 라이브러리: pthread_create, pthread_join, pthread_exit.
  • 동기화 기법: 스레드 간 충돌 방지.
  • 뮤텍스(Mutex): 자물쇠 개념 (한 번에 하나만 접근).
  • 조건 변수(Condition Variable): 특정 조건이 만족될 때까지 대기.

5.5 다중 처리와 라즈베리 파이의 제어 (실전 응용)

: 앞서 배운 내용을 라즈베리 파이의 하드웨어(SenseHAT, GPIO 등) 제어에 적용합니다.

<주요 개념>

  • 병렬 처리: 센서 입력, 데이터 처리, 모터 제어 등을 동시에 수행.
  • I/O 블로킹 해결: 센서 값을 읽는 동안 프로그램이 멈추지 않게 하기.

<학습할 핵심 내용>

  • 메인 스레드는 사용자 입력이나 화면 출력을 담당하고,
  • 작업 스레드(Worker Thread)는 센서(SenseHAT 조이스틱) 값을 지속적으로 읽어오게 구조화하는 방법.
  • 하드웨어 제어 시 발생하는 동시성 문제 해결.

10. 응용 : R-pi 보드의 서버(Server)화

: 빵판(Breadboard)에서 LED를 켜는 것이 "하드웨어 제어의 기초"라면, 라즈베리 파이를 서버로 활용하는 것은 "시스템 프로그래밍과 네트워크의 꽃"입니다.

: 단순히 while 문을 돌리는 것이 아니라, 라즈베리 파이가 "항상 깨어 있으면서 외부(Windows)의 요청을 기다리고 처리해주는 역할"을 하게 만드는 것입니다.

1. 핵심 개념: 라즈베리 파이가 "서버"가 되기 위해 필요한 것들

개발자 관점에서 "서버"는 거창한 장비가 아니라 "요청을 기다리는 프로그램(Process)"입니다.

① 소켓 프로그래밍 (Socket Programming) - "통신의 문"

  • 개념: 윈도우(Client)와 라즈베리 파이(Server)가 데이터를 주고받기 위한 가상의 연결 통로입니다.
  • LSP 연결: 리눅스에서 소켓도 결국 파일(File Descriptor)입니다. 아까 LED를 제어할 때 write()를 썼듯이, 네트워크로 데이터를 보낼 때도 소켓 파일에 write()를 합니다.
  • 핵심 함수: socket()(생성), bind()(주소 할당), listen()(대기), accept()(수락).

② 데몬 (Daemon) & Systemd - "보이지 않는 일꾼"

  • 개념: 서버 프로그램은 터미널을 꺼도 백그라운드에서 계속 실행되어야 합니다. 이런 프로세스를 데몬이라고 합니다.
  • 실무 적용: 단순히 ./server로 실행하는 게 아니라, 리눅스의 서비스 관리자인 systemd에 등록하여 부팅 시 자동 실행되게 하고, 죽으면 다시 살려내도록 설정합니다. (systemctl start myserver 같은 명령어 사용)

③ 프로토콜 (Protocol) - "약속된 언어"

  • 개념: 윈도우에서 "1"을 보냈을 때 라즈베리 파이가 "LED ON"으로 알아듣기 위한 약속입니다.
  • 방식:
    -Raw Data: 단순히 바이트(Byte) 단위로 직접 구조체를 정의해서 보냄 (C/C++ 개발자가 선호, 빠름).
    -HTTP/REST API: 웹 브라우저나 앱에서 제어하기 쉽게 만듦 (JSON 등 사용).
    -MQTT: IoT 기기들끼리 가볍게 메시지를 주고받는 표준 통신 규약.

④ 동시성 제어 (Concurrency) - "멀티태스킹"

  • 개념: 만약 윈도우 뿐만 아니라 스마트폰에서도 동시에 접속한다면? 한 명을 처리하는 동안 다른 사람은 기다려야 할까요?
  • 해결: 멀티 스레드(Multi-thread)나 멀티 프로세스(Fork), 혹은 비동기 I/O(Epoll)를 사용하여 여러 클라이언트의 요청을 동시에 처리합니다. (Qt의 QTcpServer나 리눅스 select/poll 개념이 여기서 쓰입니다.)

2. 전체 아키텍처: Windows(Qt) ↔ R-Pi(C Server)

사용자님의 관심사(Qt, C언어, 시스템 프로그래밍)를 모두 합친 이상적인 아키텍처는 다음과 같습니다.

01) Windows (Client):

  • Qt로 만든 GUI 프로그램이 실행됩니다.
  • 사용자가 화면의 'LED ON' 버튼을 클릭합니다.
  • TCP/IP 소켓을 통해 데이터 패킷(예: CMD_LED_ON)을 라즈베리 파이의 IP주소와 포트로 전송합니다.

02) Network (LAN):

  • 공유기를 통해 데이터가 이동합니다.

03) Raspberry Pi (Server):

  • C언어로 작성된 서버 프로그램이 포트(예: 8080)를 열고 accept() 상태로 대기 중입니다.
  • 데이터가 들어오면 read()로 읽어서 명령을 해석(Parsing)합니다.
  • if (msg == CMD_LED_ON) 이면, 아까 실습한 Sysfs 파일 입출력을 통해 실제 GPIO 핀에 전기를 흘려보냅니다.
  • "LED가 켜졌습니다"라는 응답(Ack)을 다시 윈도우로 보냅니다.

3. 왜 이 방식이 중요한가요? (현업 관점)

: 단순히 선을 연결해 스위치를 켜는 것과, 네트워크를 통해 명령을 받아 하드웨어를 제어하는 것은 차원이 다릅니다.

  • 원격 제어 (Remote Control): 물리적 거리에 제약이 사라집니다. (집 밖에서 방 안의 불 끄기)
  • 데이터 수집 (Data Logging): 센서 데이터를 라즈베리 파이가 계속 수집(서버 역할)하고 있다가, PC가 요청할 때 그래프를 그릴 수 있는 데이터를 던져줍니다.
  • 중앙 집중 관리: 서버 한 대가 수십 개의 센서와 액추에이터를 관리할 수 있습니다.

4. 다음 단계 제안

: 개념이 이해되셨다면, 아까 만든 LED 제어 코드에 "네트워크 기능"을 덧붙이는 실습을 추천합니다.

추천 실습 로드맵:
01) Echo Server 만들기: 윈도우(터미널)에서 글자를 보내면 라즈베리 파이가 그대로 반송하는 간단한 C 소켓 서버 작성 (LSP 네트워크 기초).

02) 원격 LED 스위치: 1번 코드에 아까 만든 LED 제어 로직을 합쳐서, 윈도우에서 "on"을 보내면 불이 켜지게 개조.

03) Qt 클라이언트 연동: 윈도우 터미널 대신 Qt로 버튼을 만들어 제어.

0개의 댓글