본문 바로가기
기술 공부

프로세스와 스레드, 그리고 Thread Safety - PKCS#11을 공부하며 배운 것들

by soy-ul 2025. 11. 3.
반응형

안녕하세요! 😊


최근 PKCS#11 기반 암호화 솔루션의 기술 문서를 분석하다가 정리하면 좋을 것 같아 글을 포스트합니다.

"멀티스레드 모드에서 세션 사용 시 스레드 간 안전성 확보를 위한 별도 처리 권장"이라는 문구였는데요. 이 한 줄을 제대로 이해하려니 프로세스, 스레드, thread safety 에 대한 개념 정리가 필요했습니다. 그래서 이 참에 제대로 공부해보기로 했습니다!


오늘은 PKCS#11 표준 API를 공부하면서 배운 프로세스, 스레드, 그리고 thread safety에 대한 내용을 정리해보려 합니다.

🔹 프로세스(Process)와 스레드(Thread)

프로세스란?

프로세스는 실행 중인 프로그램의 인스턴스입니다. Java나 C로 작성된 애플리케이션을 실행하면 운영체제가 프로세스를 생성하고, 이 프로세스가 실제 작업을 수행하게 됩니다.

 

프로세스의 특징:

  • 독립적인 메모리 공간: 운영체제로부터 반드시 독립적인 메모리 영역을 할당받습니다
  • 격리된 실행 환경: 다른 프로세스와 완전히 분리되어 실행됩니다
  • 메모리 구조: 코드, 데이터, 힙, 스택 영역을 각각 가집니다
  • 프로세스 간 통신: 다른 프로세스와 데이터를 공유하려면 IPC(Inter-Process Communication) 메커니즘이 필요합니다

애플리케이션이 변수나 함수를 처리하려면 프로세스가 반드시 운영체제로부터 메모리를 할당받아야 합니다.

 

스레드란?

스레드는 프로세스 내에서 실행되는 작업의 단위입니다. 하나의 프로세스는 여러 개의 스레드를 가질 수 있으며, 이를 통해 여러 작업을 동시에 처리할 수 있습니다.

스레드의 특징:

  • 메모리 공유: 같은 프로세스 내의 스레드들은 프로세스의 메모리 공간을 공유합니다
  • 독립적인 실행 흐름: 각 스레드는 독립적으로 실행되지만 메모리는 공유합니다
  • 가벼움: 프로세스 생성보다 스레드 생성이 훨씬 가볍습니다
  • 효율성: 컨텍스트 스위칭 비용이 프로세스보다 낮습니다

각 스레드는 독립적인 스택을 가지지만, 전역 변수나 힙 메모리는 모든 스레드가 공유한다는 점이 중요합니다. 

 

 

병렬성(Parallelism) vs 동시성(Concurrency)

스레드를 공부하다 헷갈렸던 개념이 바로 병렬성과 동시성이었습니다. 비슷해 보이지만 명확히 다릅니다.

병렬성(Parallelism):

  • 실제로 여러 작업이 물리적으로 동시에 실행됨
  • 멀티코어 CPU에서 각 코어가 실제로 동시에 작업을 처리
  • 예: 4코어 CPU에서 4개의 스레드가 각각 다른 코어에서 실제로 동시 실행

동시성(Concurrency):

  • 여러 작업이 논리적으로 동시에 진행되는 것처럼 보임
  • 싱글코어에서도 가능 (시분할, context switching)
  • CPU가 매우 빠르게 여러 작업을 번갈아가며 처리
  • 예: 싱글코어에서 10개의 스레드가 번갈아가며 실행 (실제로는 순차적이지만 동시처럼 보임)
동시성 (1코어): 작업A→작업B→작업A→작업C→작업B... (빠르게 전환)
병렬성 (4코어): 작업A | 작업B | 작업C | 작업D (실제로 동시)

참고로, 스레드는 병렬성과 동시성 둘 다 구현할 수 있습니다. 

 

 

🔹 Thread Safety란?

Thread Safety의 개념

Thread Safety(스레드 안전성)는 여러 스레드가 동시에 접근해도 데이터의 무결성이 보장되는 상태를 말합니다.

실생활 비유: 은행 계좌 문제

은행 계좌에 10,000원이 있다고 가정해봅시다. 그런데 ATM과 은행 창구에서 동시에 출금을 시도한다면 어떻게 될까요? 은행 계좌를 예로 들어보겠습니다.

 

❌ Thread-Safe 하지 않은 경우:

1. 스레드 A (ATM): "계좌 잔액 확인" → 10,000원 확인
2. 스레드 B (창구): "계좌 잔액 확인" → 10,000원 확인 (아직 A가 출금 안 함)
3. 스레드 A: "5,000원 출금 처리" → 잔액을 5,000원으로 기록하려고 준비
4. 스레드 B: "5,000원 출금 처리" → 잔액을 5,000원으로 기록
5. 스레드 A: 잔액을 5,000원으로 기록

결과: 10,000원에서 10,000원을 출금했는데 잔액이 5,000원!
(실제로는 0원이어야 하는데 한 번의 출금이 무시됨)
 

이것이 바로 Race Condition(경쟁 상태)입니다! 😱

Race Condition이란, 두 개의 스레드가 하나의 자원을 놓고 서로 사용하려고 경쟁하는 상황이다. 
두 개 이상의 프로세스가 공통 자원을 병행적으로(concurrently) 읽거나 쓰는 동작을 할 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 같지 않고 달라지는 상황이 발생한다. 

 

✅ Thread-Safe 한 경우:

은행에서 번호표 시스템(Lock)을 도입했다고 가정해봅시다.

1. 스레드 A (ATM): 번호표 받음 🎫 "내 차례!"
2. 스레드 B (창구): 대기... ⏳
3. 스레드 A: 잔액 확인(10,000) → 5,000 출금 → 잔액 5,000 기록 → 번호표 반납
4. 스레드 B: 번호표 받음 🎫 "이제 내 차례!"
5. 스레드 B: 잔액 확인(5,000) → 5,000 출금 → 잔액 0 기록

결과: 정확히 0원! ✅

 

Thread Safety 확보 방법

Thread Safety를 확보하는 방법은 크게 두 가지가 있습니다. :

 

방법 1: Lock/Unlock (동기화)

  • 개발자가 직접 잠금 메커니즘을 구현
  • 한 스레드가 작업 중일 때 다른 스레드는 대기

방법 2: Thread-Safe 라이브러리 사용

  • 라이브러리가 내부적으로 동기화 처리
  • 개발자는 신경 쓸 필요 없음

🔹 PKCS#11에서의 스레드 관리

PKCS#11이란?

PKCS#11은 암호화 토큰과 애플리케이션 간의 표준 인터페이스입니다. HSM(Hardware Security Module)이나 스마트카드 같은 암호화 장치를 사용할 때 PKCS#11 API를 통해 키 생성, 암호화, 복호화, 서명 등의 작업을 수행합니다.

핵심 개념: 세션(Session)

PKCS#11에서 세션은 애플리케이션과 암호화 토큰 간의 논리적 연결입니다. 마치 데이터베이스 커넥션처럼, 암호화 작업을 수행하기 위한 통로라고 생각하시면 됩니다!

 

세션 사용 흐름:

1. C_OpenSession - 세션 열기 (문을 연다)
2. C_Login - 로그인 (신분 확인)
3. C_Encrypt/C_Decrypt - 암호화/복호화 작업 수행
4. C_Logout - 로그아웃
5. C_CloseSession - 세션 닫기 (문을 닫는다)

 

PKCS#11 표준의 멀티스레드 규칙

PKCS#11 표준에서는 멀티스레드 환경에 대해 명확한 규칙을 제시하고 있습니다.

 

규칙 1: C_Login은 토큰 레벨에서 전역적으로 작동

  • 한 세션에서 C_Login을 수행하면, 같은 토큰을 사용하는 모든 세션이 자동으로 로그인 상태가 됩니다
  • 즉, C_Login은 한 번만 수행하면 됩니다!

규칙 2: 각 스레드는 독립적인 세션을 가져야 함

  • 여러 스레드가 하나의 세션을 공유해서는 안 됩니다
  • 각 스레드는 자신만의 세션을 열어야 합니다

규칙 3: Thread-Safe 모드 초기화

  • C_Initialize 호출 시 CKF_OS_LOCKING_OK 플래그를 사용하여 멀티스레드 지원을 활성화해야 합니다

 

✅ 올바른 멀티스레드 사용 패턴

애플리케이션 시작

C_Initialize(CKF_OS_LOCKING_OK) // Thread-Safe 모드로 초기화

스레드1:                                       스레드2:                                                     스레드3:
OpenSession                         OpenSession                                           OpenSession
C_Login ✓                           (이미 로그인됨 ✓)                                       (이미 로그인됨 ✓)
암호화 작업                                복호화 작업                                                    서명 작업
CloseSession                        CloseSession                                             CloseSession
 
 

핵심 정리:

  • ✅ C_Login: 한 번만 (토큰 레벨 전역 상태)
  • ✅ 세션: 각 스레드마다 별도로 생성
  • ✅ 라이브러리 초기화: Thread-Safe 모드 활성화

❌ 잘못된 사용 패턴 (표준 위반)

애플리케이션 시작

스레드1에서: OpenSession(session1)
C_Login
↓ 스레드1, 2, 3이 모두 session1 공유 ❌

결과: Race Condition 발생! 데이터 충돌!

 

이 방식은 PKCS#11 표준을 위반하며, 예측 불가능한 오류를 발생시킵니다.


특징:

  • 각 스레드가 독립적인 세션을 가짐
  • C_Login은 여전히 한 번만 수행 (토큰 레벨 전역)
  • 세션은 각각 별도로 오픈
  • 표준 준수 및 안전성 보장

비교 정리

세션 사용 여러 스레드가 하나의 세션 공유 각 스레드가 별도 세션 각 스레드 별도 세션
C_Login 한 번 한 번 (토큰 레벨 전역) 한 번 (토큰 레벨)
표준 준수 ❌ 위반 (허용) ✅ 준수 (강제) -
Thread-Safe ❌ 개발자가 직접 관리 ✅ 라이브러리가 처리 -

 

🔹 마치며

PKCS#11 표준 API를 공부하면서 프로세스, 스레드, thread safety에 대해 깊이 이해하게 되었습니다.

처음에는 단순히 "멀티스레드 환경에서 세션을 공유하면 안 된다"는 규칙만 알았다면, 이제는 그 이유와 동작 원리를 명확히 알게 되었습니다.

 

핵심 정리:

  1. 프로세스: 독립적인 메모리 공간을 가진 실행 단위
  2. 스레드: 프로세스 내에서 메모리를 공유하는 실행 단위
  3. Thread Safety: 여러 스레드가 동시에 접근해도 데이터 무결성 보장
  4. PKCS#11 규칙:
    • C_Login은 한 번만 (토큰 레벨 전역)
    • 각 스레드는 독립적인 세션 사용
    • Thread-Safe 모드로 초기화 필수

엔지니어로서 개발자가 아니더라도 이런 기술적 개념을 이해하면 시스템 분석이나 문제 해결을 할 때 도움을 많이 받는것 같습니다.

특히 멀티스레드 환경에서 발생하는 문제는 재현하기도 어렵고 원인 파악도 힘든데, 기본 개념을 정리하며 훨씬 수월하게 대응할 수 있을것 같습니다.

 

마지막으로, 이 글이 프로세스와 스레드, 그리고 thread safety에 대해 궁금해하시는 분들에게 도움이 되었으면 좋겠습니다. 😊

 

감사합니다!

반응형