본문 바로가기
Computer Science/UNIX & Linux

[UNIX/Linux] ep7++) 레코드 락킹(advisory locking)

by 클레어몬트 2024. 11. 6.

https://claremont.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-ep4-1-Concurrency-Mutual-Exclusion-and-Synchronization

 

[운영체제] ep4-1) Concurrency: Mutual Exclusion and Synchronization

ㅁ동시성(Concurrency): 여러 작업이 짧은 시간 간격으로 번갈아 가며 수행됨으로써 동시에 처리되는 것처럼 보이게 만드는 것현대 OS는 멀티 프로그래밍, 멀티 프로세싱 등의 기법으로 동시성을 사

claremont.tistory.com

https://claremont.tistory.com/entry/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-ep5-Concurrency-Deadlock-and-Starvation

 

[운영체제] ep5) Concurrency: Deadlock and Starvation

ㅁDeadlock의 발생 조건 4가지① Mutual Exclusion(상호배제)② Hold-and-Wait③ No preemption(비선점)④ Circular Wait: 여러 프로세스가 자원을 할당받은 후, 다른 프로세스가 점유하고 있는 자원을 추가로 요청

claremont.tistory.com

뮤텍스 락과 비슷한 개념이다 

critical section 을 피하기 위해 사용하는 기법이다

 

 

 

ㅁ레코드 락킹(advisory locking): 여러 프로세스가 동일한 파일을 동시에 다룰 때 충돌을 방지하기 위해 사용하는 방법 (critical section avoidance)

 

multiple reader, single writer

1 .읽기 락: 여러 프로세스가 동시에 읽을 수 있도록 허용하지만, 쓰기 락은 적용되지 않게 함

2. 쓰기 락: 특정 프로세스가 쓰기 작업을 하는 동안 다른 프로세스가 해당 구역을 읽거나 쓰지 못하게 막음. 한 구역에 하나의 쓰기 록만 존재 가능

 

 

fcntl 함수는 파일 기술자(fd)를 통해 파일의 여러 속성을 제어하는 다목적 함수이다

ㅇ int fcntl (int filedes, int cmd, struct flock* ldata): 파일에 락을 적용하는 함수

- filedes : 유효한 개방된 파일기술자. 읽기 락인 경우 O_RDONLY나 O_RDWR로 개방. 쓰기 락을 위해서는 O_WRONLY나 O_RDWR로 개방

- cmd : 파일에 대한 특정 작업을 설정하는 명령어

 

<cmd에 들어가는 기능>

• F_GETLK : ldata를 통해 전달된 현재 설정 락 정보를 획득

• F_SETLK : 파일에 락을 설정하고, 불가능하면 즉시 -1을 반환

• F_SETLKW : 파일에 락을 설정하고, 만약 다른 프로세스가 점유중이면 대기(sleep)

 

(레코드 락킹에 필요한 struct flock 구조체)

struct flock {
    short l_type;    /* 락의 유형: F_RDLCK(읽기 락), F_WRLCK(쓰기 락), F_UNLCK(락 해제) */
    short l_whence;  /* lseek와 동일하다, 기준 위치: SEEK_SET, SEEK_CUR, SEEK_END */
    off_t l_start;   /* 시작 오프셋 (바이트 단위) */
    off_t l_len;     /* 락의 길이인 세그먼트 크기 (바이트 단위), 0이면 파일 끝까지 */
    pid_t l_pid;     /* 락을 설정한 PID, 명령에 의해 설정 */
};

K부터 O까지 총 5개 구간에 락이 걸려 있는 상황

 

 

(예시 코드)

struct flock my_lock;

my_lock.l_type = F_WRLCK;     // 쓰기 락 설정
my_lock.l_whence = SEEK_CUR;  // 현재 파일 위치를 기준으로
my_lock.l_start = 0;          // 락 시작 위치를 현재 위치로부터 0바이트
my_lock.l_len = 512;          // 512바이트 길이로 잠금

// F_SETLKW cmd를 사용하여 락을 설정
// 다른 프로세스가 해당 구역을 이미 잠가놓은 경우, 락이 해제될 때까지 대기
fcntl(fd, F_SETLKW, &my_lock);

 

 

 

(test1.c 코드)

▪ 락 정보는 fork호출에 의해 계승되지 않음

fcntl호출의 파일포인터를 변경시키지 않음

한 프로세스에 속한 모든 락은 그 프로세스가 죽을 때 자동적으로 제거됨

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd;
    struct flock my_lock;
    struct flock b_lock;

    my_lock.l_type = F_WRLCK;
    my_lock.l_whence = SEEK_SET;
    my_lock.l_start = 0;
    my_lock.l_len = 10;

    fd = open("testdata", O_RDWR);

    if (fcntl(fd, F_SETLKW, &my_lock) == -1) {
        perror("parent : locking");
        exit(1);
    }

    printf("parent : locked record\n");

    switch(fork()) {
        case -1 :
            perror("fork");
            exit(1);
        case 0 :
            my_lock.l_len = 5;
            if (fcntl(fd, F_SETLKW, &my_lock) == -1) {
                perror("child : locking");
                exit(1);
            }
            printf("child: locked\n");
            printf("child: exiting\n");
            exit(0);
    }
	
    sleep(2);

    printf("parent: unlocking\n");

    my_lock.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &my_lock) == -1) {
        printf("parent : unlocking");
        exit(1);
    }
    exit(0);

    return 0;
}

 

 

(test2.c 코드)

프로세스는 F_GETLK를 사용해 어느 프로세스가 락을 가지고 있는지 결정할 수 있음

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd;
    struct flock my_lock;
    struct flock b_lock;

    my_lock.l_type = F_WRLCK;
    my_lock.l_whence = SEEK_SET;
    my_lock.l_start = 0;
    my_lock.l_len = 10;

    fd = open("testdata", O_RDWR);

    if (fcntl(fd, F_SETLKW, &my_lock) == -1) {
        perror("parent : locking");
        exit(1);
    }

    printf("parent : locked record\n");

    switch(fork()) {
        case -1 :
            perror("fork");
            exit(1);
        case 0 :
            my_lock.l_len = 5;
            if (fcntl(fd, F_SETLKW, &my_lock) == -1) {
                perror("child : locking");
                exit(1);
            }
            printf("child: locked\n");
            printf("child: exiting\n");
            exit(0);
    }
	
    sleep(2);

    printf("parent: unlocking\n");

    my_lock.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &my_lock) == -1) {
        printf("parent : unlocking");
        exit(1);
    }
    exit(0);

    return 0;
}

 

 

(test3.c 코드)

▪ Deadlock Avoidance test

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    int fd;
    struct flock first_lock, second_lock;

    first_lock.l_type = F_WRLCK;
    first_lock.l_whence = SEEK_SET;
    first_lock.l_start = 0;
    first_lock.l_len = 10;

    second_lock.l_type = F_WRLCK;
    second_lock.l_whence = SEEK_SET;
    second_lock.l_start = 10;
    second_lock.l_len = 5;

    fd = open("testdata", O_RDWR);

    if (fcntl(fd, F_SETLKW, &first_lock) == -1) {
        perror("A");
        exit(1);
    }

    printf("A: lock succedded (proc %d)\n", getpid());

    switch(fork()) {
        case -1 :
            perror("error on fork");
            exit(1);
        case 0 :
            if (fcntl(fd, F_SETLKW, &second_lock) == -1) {
                perror("B");
                exit(1);
            }
            printf("B: lock succeeded (proc %d)\n", getpid());
            if (fcntl(fd, F_SETLKW, &first_lock) == -1) {
                perror("C");
                exit(1);
            }
            printf("C: lock succeeded (proc %d)\n", getpid());
            exit(0);
        default:
            printf("parent sleeping\n");
            sleep(5);
            if (fcntl(fd, F_SETLKW, &second_lock) == -1) {
                perror("D");
                exit(1);
            }
            printf("D: lock succeeded (proc %d)\n", getpid());
    }

    return 0;
}