05-数据管理(内存管理、文件锁定/记录锁/字节范围锁)

数据管理

内存管理

#include <stdlib.h>

void *malloc(size_t size);
void *calloc(size_t number_of_elements, size_t element_size); // 为一个数组分配内存,该空间的每个bite都初始化为0
void *realloc(void *existing_memory, size_t new_size); // 推荐使用malloc申请一块新空间,再用memcpy复制内容
// realloc会在原空间上多申请扩充空间,如果空间充足则无需移动原内容并返回原指针;空间不足时新申请一块空间并复制内容返回新指针。
// 注意不要让任何指针指向realloc操作的空间

void free(void *ptr_to_memory);

替代选项:

  1. TCMalloc
  2. alloca 在栈帧上申请一块空间,函数返回自动释放,某些系统不支持函数被调用后修改栈帧长度故也不支持alloca函数
  3. libmalloc
  4. vnlloc
  5. quick-fit
  6. jemalloc

文件锁定

当一个程序在对文件写操作时,文件进入暂时状态,这个状态下若另一个程序尝试读这个文件,它会停下来等待这个状态的结束。两种方式实现文件锁定:1. 以原子操作的方式创建锁文件 2. 允许程序锁定文件的一部分,从而独享对这一部分内容的访问

应用程序只需要针对某个资源创建一个锁文件即可,然后其他程序可以通过检查这个文件来判断它们是否被允许访问这个资源。锁文件只是建议锁,不是强制锁。

锁的隐含继承和释放

  1. 锁与进程和文件相关联:
    1. 当一个进程终止时,它所建立的锁全部释放
    2. 无论一个描述符何时关闭,该进程通过这一描述符引用的文件上的锁全部释放
  2. fork产生的子进程不继承父进程的锁。子进程能继承父进程的文件描述符,如果还能继承锁那么就达不到阻止多个进程读写同一文件的效果
  3. 执行exec后,新程序可以继承原执行程序的锁。但是,如果文件描述符设置了执行时关闭,那么执行exec期间关闭文件描述符的同时也关闭了锁

创建锁

使用open系统调用,带上O_CREAT和O_EXCL标志,能以原子操作同时完成两项工作:确定文件不存在,然后创建它。

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

int main(int argc, char const *argv[])
{
    int file_desc;
    int save_errno;

    file_desc = open("/tmp/LCK.test", O_RDWR | O_CREAT | O_EXCL, 0444);
    if (file_desc != -1) {
        save_errno = errno;
        printf("Open failed with error %d\n", save_errno);
    } else {
        printf("Open succeeded\n");
    }

    return 0;
}
/*
第一次运行时 “Open succeeded”
第二次 “Open failed with error 17”
用带有O_CREAT和O_EXCL的open函数创建文件时,文件已存在则返回错误号17。定义在errno.h头文件中,表示EEXIST
*/

当短时间占用某个资源时——临界区。进入前用open函数创建锁文件,退出时用unlink删除该锁文件。

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

const char *lock_file = "/tmp/LCK.test2";

int main(int argc, char const *argv[])
{
    int file_desc;
    int tries = 10;

    while (tries--) {
        file_desc = open(lock_file, O_RDWR | O_CREAT | O_EXCL, 0444);
        if (file_desc == -1) {
            printf("%d - Lock alread present\n", getpid());
            sleep(3);
        } else {
            // 进入临界区
            printf("%d - I have exclusive access\n", getpid());
            sleep(1);
            (void)close(file_desc);
            (void)unlink(lock_file);
            sleep(2);
            // 退出临界区
        }
    }

    return 0;
}

区域锁定

当有一个大型文件,它由一个程序写入数据,由许多不同程序同时进行更新。当记录程序不停的运行时,处理文件无法对文件进行更新。这时需要一些协调方法提供文件的并发访问。可以通过文件区域锁定解决这个问题。有两种方法:fcntl和lockf。fcntl更为常用,两者使用不同的底层实现,无法同时工作。

#include <fcntl.h>

int fcntl(int fildes, int command, ...);
/*
command 有三个选项
F_GETLK
	用于获取 fildes 参数打开的文件的锁信息,不会尝试去锁定文件。使用 flock 类型表明需要查看的锁类型与文件区域。如果查看成功返回非-1,flock结构中的 l_pid 被设置为拥有锁的pid,通过检查 flock 结构得知;失败返回-1,flock结构保持不变。
F_SETLK
	加锁,通过flock结构定义。成功返回非-1,失败返回-1。
F_SETLKW
	与前者类似,但在无法获取锁时,这个调用等待到获取锁或收到一个信号。
*/

// 实际使用这些命令时第三个参数必须指向flock结构的指针,故函数原型为
int fcntl(int fildes, int command, struct flock *flock_structure);
struct flock
{
    short int l_type;	/* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK.	*/
    short int l_whence;	/* Where `l_start' is relative to (like `lseek').  */
    __off_t l_start;	/* Offset where the lock begins.  */
    __off_t l_len;	/* Size of the locked area; zero means until EOF.  */
    __pid_t l_pid;	/* Process holding the lock.  */
};
/*
flock (文件锁) 属性与取值
short l_type
	F_RDLCK 读锁
	F_UNLCK 解锁
	F_WRLCK 写锁

定义文件中的一个区域,即一个连续的字节集合
short l_whence
	SEEK_SET 文件头
	SEEK_CUR 当前位置
	SEEK_END 文件尾
short l_start
	该区域的第一个字节
short l_len
	该区域的长度

记录持有锁的进程
short l_pid
*/
程序对某个文件拥有的锁都将在相应文件描述符被关闭时自动清除,在程序结束时也会自动清除。

锁定状态下读写操作

对于文件区域加锁后必须使用底层的read和write访问数据,高级的fread和fwrite会对读写数据进行缓存。

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

const char *test_file = "/tmp/test_lock";

int main(int argc, char const *argv[])
{
    int file_desc;
    int byte_count;
    char *byte_to_write = "A";
    struct flock region_1;
    struct flock region_2;
    int res;

    // 打开文件描述符
    file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
    if (!file_desc) {
        fprintf(stderr, "Unable to open %s for read/write\n", test_file);
        exit(EXIT_FAILURE);
    }
    // 给文件添加数据
    for (byte_count = 0; byte_count < 100; ++byte_count) {
        (void)write(file_desc, byte_to_write, 1);
    }

    // 文件的10~30字节设为区域1,并上共享锁
    region_1.l_type = F_RDLCK;
    region_1.l_whence = SEEK_SET;
    region_1.l_start = 10;
    region_1.l_len = 20;
    // 文件的40~50字节设为区域2,并上独占锁
    region_2.l_type = F_WRLCK;
    region_2.l_whence = SEEK_SET;
    region_2.l_start = 40;
    region_2.l_len = 10;

    // 锁定文件
    printf("Process %d locking file\n", getpid());
    res = fcntl(file_desc, F_SETLK, &region_1);
    if (res == -1) {
        fprintf(stderr, "Failed to lock region 1\n");
    }
    res = fcntl(file_desc, F_SETLK, &region_2);
    if (res == -1) {
        fprintf(stderr, "Failed to lock region 2\n");
    }

    sleep(20);
    printf("Process %d closing file\n", getpid());
    close(file_desc);
    return 0;
}

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

#define SIZE_TO_TRY 5

const char *test_file = "/tmp/test_lock";

void show_lock_info(struct flock *to_show);

int main(int argc, char const *argv[])
{
    int file_desc;
    int res;
    struct flock region_to_test;
    int start_byte;

    // 打开一个文件描述符
    file_desc = open(test_file, O_RDWR | O_CREAT, 0666);
    if (!file_desc) {
        fprintf(stderr, "Unable to open %s for read/write", test_file);
        exit(EXIT_FAILURE);
    }

    for (start_byte = 0; start_byte < 99; start_byte += SIZE_TO_TRY) {
        // 设置测试文件区域,写锁
        region_to_test.l_type = F_WRLCK;
        region_to_test.l_whence = SEEK_SET;
        region_to_test.l_start = start_byte;
        region_to_test.l_len = SIZE_TO_TRY;
        region_to_test.l_pid = -1;

        printf("Testing F_WRLCK on region from %d to %d\n", start_byte, start_byte + SIZE_TO_TRY);

        // 测试文件上的锁
        res = fcntl(file_desc, F_GETLK, &region_to_test);
        if (res == -1) {
            fprintf(stderr, "F_GETLK failed\n");
            exit(EXIT_FAILURE);
        }
        if (region_to_test.l_pid != -1) {
            printf("Lock would fail. F_GETLK returned: \n");
            show_lock_info(&region_to_test);
        } else {
            printf("F_WRLCK - Lock would succed\n");
        }

        // 读锁
        region_to_test.l_type = F_RDLCK;
        region_to_test.l_whence = SEEK_SET;
        region_to_test.l_start = start_byte;
        region_to_test.l_len = SIZE_TO_TRY;
        region_to_test.l_pid = -1;
        printf("Testing F_RDLCK on region from %d to %d\n", start_byte, start_byte + SIZE_TO_TRY);

        // 测试文件上的锁
        res = fcntl(file_desc, F_GETLK, &region_to_test);
        if (res == -1) {
            fprintf(stderr, "F_GETLK failed\n");
            exit(EXIT_FAILURE);
        }
        if (region_to_test.l_pid != -1) {
            printf("Lock would fail. F_GETLK returned: \n");
            show_lock_info(&region_to_test);
        } else {
            printf("F_RDLCK - Lock would succed\n");
        }
    }

    close(file_desc);
    return 0;
}

void show_lock_info(struct flock *to_show)
{
    printf("\tl_type %d, ", to_show->l_type);
    printf("l_whence %d, ", to_show->l_whence);
    printf("l_start %d, ", (int)to_show->l_start);
    printf("l_len %d, ", (int)to_show->l_len);
    printf("l_pid %d\n", (int)to_show->l_pid);
}
posted @ 2022-06-16 09:40  某某人8265  阅读(71)  评论(0编辑  收藏  举报