NAME

attributes - POSIX 안전성 개념

DESCRIPTION

참고: 이 맨 페이지는 GNU C 라이브러리 매뉴얼의 "POSIX Safety Concepts" 절에서 가져온 내용을 바탕으로 한 것이다. 여기서 설명하는 주제들에 대한 더 자세한 내용을 그 매뉴얼에서 찾아 볼 수 있다.

많은 함수 매뉴얼 페이지에는 ATTRIBUTES 절이 있어서 여러 맥락에서 그 함수 호출의 안전성을 기술한다. 그 절에서 함수에 다음과 같은 안전성 표시를 붙인다.

MT-Safe

MT-Safe, 즉 스레드 안전 함수는 다른 스레드가 존재할 때에도 안전하게 호출할 수 있다. MT-Safe에서 MT는 다중 스레드(Multi Thread)를 나타낸다.

MT-Safe라고 해서 함수가 원자적인 것은 아니며 POSIX에서 사용자에게 드러내는 어떤 메모리 동기화 메커니즘을 사용한다는 의미도 아니다. 게다가 MT-Safe 함수들의 조합을 차례로 호출하는 것이 MT-Safe가 아닐 수도 있다. 예를 들어 한 스레드에서 MT-Safe 함수 두 개를 잇달아 호출하는 것이 두 함수 조합을 원자적으로 실행하는 것과 동등한 동작을 보장하지 않는다. 다른 스레드에서 동시에 이뤄지는 호출이 파괴적 방식으로 간섭할 수 있기 때문이다.

라이브러리 인터페이스를 넘어서 함수를 인라인 처리할 수도 있는 프로그램 단위 최적화로 인해 안전하지 않은 순서 바뀜이 발생할 수도 있으며, 그래서 GNU C 라이브러리 인터페이스를 넘어서 인라인 처리를 하는 걸 권장하지 않는다. 프로그램 단위 최적화 하에서는 적혀 있는 MT 안전성이 보장되지 않는다. 하지만 사용자에게 보이는 헤더에 정의돼 있는 함수들은 인라인 처리에 안전하도록 설계돼 있다.

MT-Unsafe
MT-Unsafe 함수는 다중 스레드 프로그램에서 호출하기에 안전하지 않다.

안전성 표시에 등장하는 다른 키워드들을 이어지는 절에서 정의한다.

조건부로 안전한 기능

특정 맥락에서 함수 호출을 안전하지 않게 만드는 일부 특성들에 대해선 함수 호출 자체를 삼가는 것 말고도 안전성 문제를 피할 수 있는 알려진 방법들이 있다. 다음에 나오는 키워드들이 그런 특성들을 가리키는데, 각 정의에는 키워드가 나타내는 안전성 문제를 없애기 위해 전체 프로그램에 어떤 제약이 가해져야 하는지 나와 있다. 함수를 안전하지 않게 하는 이유들을 모두 확인해서 적혀 있는 제약을 적용해 해결할 때만 함수가 그 맥락에서 호출하기에 안전해진다.

init

MT-Unsafe 특성으로 init 표시가 된 함수들은 첫 호출 때 MT-Unsafe인 초기화를 수행한다.

그런 함수를 단일 스레드 모드에서 적어도 한 번 호출하면 그 함수가 MT-Unsafe이게 하는 그 특정 원인이 없어진다. 그 함수에 대해 다른 이유가 남아 있지 않다면 다른 스레드 시작 후에 그 함수를 안전하게 호출할 수 있다.

race
MT 안전성과 관련해 race 표시가 된 함수들은 동시 실행 중에 데이터 경쟁이나 유사한 형태의 파괴적 간섭을 일으키는 방식으로 객체들에 동작한다. 일부 경우에서는 사용자가 객체를 함수로 전달하고, 다른 경우에서는 함수가 객체를 이용해 사용자에게 값을 반환하고, 또 다른 경우에서는 객체가 사용자에게 전혀 드러나지 않는다.
const

MT 안전성과 관련해 const 표시가 된 함수들은 GNU C 라이브러리의 상당 부분에서 동기화 없이 접근하기 때문에 상수로 여기는 게 나은 내부 객체를 원자적이지 않은 방식으로 변경한다. 내부 객체에 읽기를 하는 쪽과 쓰기를 하는 쪽 모두를 MT-Unsafe이게 하는 race와 달리 이 표시는 쓰는 쪽에만 적용된다. 쓰는 쪽을 호출하는 건 여전히 MT-Unsafe지만 객체 변경 후 그 상수성이 돌아오므로 읽는 쪽을 (안전하지 않게 하는 다른 이유가 남아 있지 않다면) MT-Safe로 여길 수 있게 된다. 객체가 실질적으로 상수일 때는 동기화가 없는 게 문제가 되지 않기 때문이다.

const 표시 뒤에 오는 식별자가 읽는 쪽에는 단독으로 안전성 표시로 나오게 된다. 쓰는 쪽을 호출하면서 이 안전성 문제를 피하고 싶은 프로그램에서는 그 식별자에 연계된 비재귀 읽기-쓰기 락을 사용할 수 있다. 식별자 붙은 const로 표시된 함수들의 호출을 모두 쓰기 락으로 보호하고 식별자만 표시된 함수들의 호출을 모두 읽기 락으로 보호하면 된다.

sig

MT 안전성과 관련해 sig 표시가 된 함수들에서는 내부 용도로 임시 시그널 핸들러를 설치할 수 있으며, 그래서 콜론 뒤에 표시된 시그널을 이용하는 경우 간섭이 있을 수 있다.

호출 중에 그 시그널을 다른 곳에서 쓰지 않도록 하여 이 안전성 문제를 피할 수 있다. 같은 임시 시그널을 쓰는 모든 함수들의 호출 동안 비재귀 뮤텍스를 잡고, 호출 전에 그 시그널을 막았다가 호출 후 핸들러를 재설정하기를 권장한다.

term

MT 안전성과 관련해 term 표시가 된 함수들에서는 권장하는 방식으로, 즉 tcgetattr(3)을 호출한 다음 어떤 플래그를 변경해서 tcsetattr(3)을 호출하는 방식으로 터미널 설정을 바꿀 수 있는데, 그러면 다른 스레드의 변경 사항이 유실될 수 있는 가능성이 생긴다. 따라서 term으로 표시된 함수들은 MT-Unsafe다.

그러므로 터미널을 쓰는 응용에서는 동시적이고 재진입 가능한 방식으로 터미널과 상호작용하는 걸 피하는 게 바람직하다. 시그널 핸들러에서 그 함수들을 쓰지 않거나 쓰게 될 수도 있는 시그널을 막고, 그 함수들을 호출하는 동안 및 터미널과 상호작용 하는 동안 락을 잡고 있으면 된다. 그 락을 race:tcattr(fd) 표시가 된 함수와의 상호 배제에도 쓰는 게 좋은데, 여기서 fd는 터미널 제어를 위한 파일 디스크립터다. 단순함을 위해 호출자에서 뮤텍스 한 개만 쓸 수도 있고, (여러 파일 디스크립터가 참조하더라도) 터미널별로 뮤텍스 하나씩을 쓸 수도 있다.

기타 안전성 표시

함수 호출을 안전하지 않게 만드는 건 아니지만 특정 프로그램 유형에서 고려가 필요할 수도 있는 특성들을 나타내기 위한 키워드가 함수에 추가로 붙을 수 있다.

locale

MT 안전성과 관련해 locale 표시가 된 함수들은 어떤 형태의 동기화도 없이 로캘 객체의 데이터를 읽는다. 로캘 변경과 동시에 locale 표시 함수를 호출하면 실행 중 활성인 어느 로캘에도 해당하지 않으면서 그것들이 예측 불가능하게 섞인 방식으로 동작할 수 있다.

하지만 이 함수들을 MT-Unsafe라고 표시하지는 않는다. 로캘 객체를 변경하는 함수들에 const:locale 표시를 하고 안전하지 않은 것으로 여기기 때문이다. 안전하지 않은 그 함수들은 여러 스레드가 돌거나 비동기 시그널이 켜져 있을 때는 호출하지 않게 되어 있고, 따라서 그 맥락에서 로캘이 실질적으로 상수라고 볼 수 있으며, 그래서 이 함수들이 안전해진다.

env

MT 안전성과 관련해 env 표시가 된 함수들은 동시 변경이 있을 때의 안전성을 보장하기 위한 어떤 안전 장치도 없이 getenv(3)나 비슷한 함수로 환경에 접근한다.

하지만 이 함수들을 MT-Unsafe라고 표시하지는 않는다. 환경을 변경하는 함수들에 모두 const:env 표시를 하고 안전하지 않은 것으로 여기기 때문이다. 안전하지 않은 그 함수들은 여러 스레드가 돌거나 비동기 시그널이 켜져 있을 때는 호출하지 않게 되어 있고, 따라서 그 맥락에서 환경이 실질적으로 상수라고 볼 수 있으며, 그래서 이 함수들이 안전해진다.

hostid
MT 안전성과 관련해 hostid 표시가 된 함수들은 머신의 "호스트 ID"를 담은 시스템 전역 자료 구조들을 읽는다. 이 자료 구조들은 일반적으로 원자적으로 변경할 수 없다. "호스트 ID"가 보통은 바뀌지 않을 것이므로 그걸 읽는 함수(gethostid(3))는 안전한 것으로 여기고 변경하는 함수(sethostid(3))에 const:hostid 표시를 해서 호출 시 특별한 주의가 필요하다고 표시한다. 그 경우에서 특별한 주의란 (프로세스 내부 정도가 아니라) 시스템 전체에서의 조율을 뜻한다.
sigintr

MT 안전성과 관련해 sigintr 표시가 된 함수들은 동시 변경이 있을 때의 안전성을 보장하기 위한 어떤 안전 장치도 없이 GNU C 라이브러리의 _sigintr라는 내부 자료 구조에 접근한다.

하지만 이 함수들을 MT-Unsafe라고 표시하지는 않는다. 그 자료 구조를 변경하는 함수들에 모두 const:sigintr 표시를 하고 안전하지 않은 것으로 여기기 때문이다. 안전하지 않은 그 함수들은 여러 스레드가 돌거나 비동기 시그널이 켜져 있을 때는 호출하지 않게 되어 있고, 따라서 그 맥락에서 그 자료 구조가 실질적으로 상수라고 볼 수 있으며, 그래서 이 함수들이 안전해진다.

cwd

MT 안전성과 관련해 cwd 표시가 된 함수들은 실행 중에 일시적으로 현재 작업 디렉터리를 바꿀 수 있으며, 그래서 다른 스레드나 비동기 시그널 핸들러 내지 취소 핸들러에서 상대 경로명이 예상치 못한 방식으로 해석될 수 있다.

이 때문에 그런 함수들에 MT-Unsafe 표시를 붙일 필요까지는 없겠지만, 그런 동작 방식이 선택적일 때는 (가령 nftw(3)FTW_CHDIR) 그 방식을 피하는 게 전체 경로명을 쓰거나 파일 디스크립터 기준 시스템 호출(가령 openat(2))을 쓰는 것보다 좋은 대안일 수 있다.

:식별자

때로는 표시 뒤에 식별자가 올 수 있다. 예를 들어 raceconst처럼 안전하지 않은 방식으로 자료 구조에 접근하는 여러 함수들을 한데 묶거나, sig로 표시한 함수에 시그널 이름을 적는 것처럼 구체적 정보를 제공하기 위해서이다. 향후에는 lockcorrupt에도 적용될 수 있으리라 생각해 본다.

대부분의 경우에서 식별자는 함수군의 이름이 되겠지만 전역 객체나 함수 인자, 또는 그에 연계된 식별 가능한 속성이나 논리적 요소의 이름이 될 수도 있다. 예를 들어 :buf(arg) 표기는 인자 arg에 연계된 버퍼를 나타내며 :tcattr(fd)는 파일 디스크립터 fd의 터미널 속성들을 나타낸다.

식별자를 쓰는 가장 흔한 경우는 해당 맥락에서 안전한 동작을 보장하기 위해 같은 동기화 요소로 보호할 필요가 있는 함수와 인자들을 논리적으로 묶는 것이다.

/조건

어떤 안전성 표시는 조건부이다. 즉 인자나 전역 변수, 심지어 기반 커널까지 포함되는 어떤 불리언 식이 참으로 평가될 때만 적용된다. 예를 들어 /!ps/one_per_line은 인자 ps가 NULL일 때에만, 그리고 전역 변수 one_per_line이 0이 아닐 때만 앞의 표시가 적용됨을 나타낸다.

어떤 함수를 안전하지 않게 만드는 모든 표시들에 그런 조건이 붙어 있고 그 조건들 중 어느 것도 성립하지 않을 때는 그 함수를 안전한 것으로 여길 수 있다.

SEE ALSO

pthreads(7), signal-safety(7)


2021-03-22