#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "test.h" // ============================================================================ // Global definitions // ============================================================================ #define S_IRWUSER (S_IRUSR | S_IWUSR) #define TEST_GET_SHMID_BY_KEY 0 #define TEST_PROCESS_COMMU 1 #define TEST_OPERATE_DESTOYED 2 #define TEST_GET_SHMID_BY_KEY_ARGC 5 #define TEST_PROCESS_COMMU_ARGC 4 #define TEST_OPERATE_DESTOYED_ARGC 5 #define ARG_BUF_SZ 64 #define PAGE_SIZE (0x1000) #define SUCCESS 1 #define FAIL (-1) const char prog_name[] = "/bin/shm"; // ============================================================================ // Helper macro and function // ============================================================================ #define INFO(fmt, ...) do { \ printf("\t\t[file: %s, line: %d, func: %s] " fmt, \ __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ } while (0) // Spawn child, and check the return value from child // Return `SUCCESS` if the execution succeeds, // return `FAIL` if fails static int execute_in_child(char **const child_argv) { int ret; pid_t child_pid; int child_status; ret = posix_spawn(&child_pid, prog_name, NULL, NULL, (char **const)child_argv, NULL); if (ret < 0) { THROW_ERROR("Failed to spawn a child process"); } ret = waitpid(child_pid, &child_status, 0); if (ret < 0) { THROW_ERROR("Failed to waitpid() for child process"); } if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0) { INFO("The test in child failed\n"); return FAIL; } return SUCCESS; } // ============================================================================ // Test cases for shm // ============================================================================ static int test_shmget_shmid_from_key(void) { int ret, shmid; key_t key; size_t shm_size = PAGE_SIZE; char *child_argv[TEST_GET_SHMID_BY_KEY_ARGC + 1]; srand(time(NULL)); key = random(); // Get a non-existent shm segment, should get the return value with ENOENT ret = syscall(SYS_shmget, key, shm_size, S_IRWUSER); if (ret != -1 || errno != ENOENT) { INFO("shmget() should return ENOENT because the segment does not exist, ret: %d errno: %d\n", ret, errno); return FAIL; } // Create a new shm segment shmid = syscall(SYS_shmget, key, shm_size, IPC_CREAT | IPC_EXCL | S_IRWUSER); if (shmid < 0) { THROW_ERROR("shmget() cannot create the shm"); } // Get the shmid by key in the same process ret = syscall(SYS_shmget, key, shm_size, S_IRWUSER); if (ret < 0) { THROW_ERROR("shmget() cannot get the shm"); } if (ret != shmid) { INFO("shmid mismatches, correct: %d actual: %d\n", shmid, ret); return FAIL; } // Create a shm segment whose key is already attached to existed segment, // should get the return value with EEXIST ret = syscall(SYS_shmget, key, shm_size, IPC_CREAT | IPC_EXCL | S_IRWUSER); if (ret != -1 || errno != EEXIST) { INFO("shmget() should return EEXIST because the segment already exists, ret: %d errno: %d\n", ret, errno); return FAIL; } // Spawn a new process for (int i = 0; i < TEST_GET_SHMID_BY_KEY_ARGC; i++) { child_argv[i] = (char *)malloc(ARG_BUF_SZ); } snprintf(child_argv[0], ARG_BUF_SZ, "%s", prog_name); snprintf(child_argv[1], ARG_BUF_SZ, "%d", TEST_GET_SHMID_BY_KEY); snprintf(child_argv[2], ARG_BUF_SZ, "%d", key); snprintf(child_argv[3], ARG_BUF_SZ, "%d", shmid); snprintf(child_argv[4], ARG_BUF_SZ, "%ld", shm_size); child_argv[TEST_GET_SHMID_BY_KEY_ARGC] = NULL; if ((ret = execute_in_child(child_argv)) != SUCCESS) { return FAIL; } for (int i = 0; i < TEST_GET_SHMID_BY_KEY_ARGC; i++) { free(child_argv[i]); } ret = syscall(SYS_shmctl, shmid, IPC_RMID, NULL); if (ret < 0) { THROW_ERROR("Cannot remove the segment"); } return SUCCESS; } static int test_process_communication() { int shmid, ret; size_t shm_size = PAGE_SIZE; long random_num, *shm_addr; char *child_argv[TEST_PROCESS_COMMU_ARGC + 1]; shmid = syscall(SYS_shmget, IPC_PRIVATE, shm_size, IPC_CREAT | IPC_EXCL | S_IRWUSER); if (shmid < 0) { THROW_ERROR("shmget() cannot get the shm"); } shm_addr = (long *)syscall(SYS_shmat, shmid, NULL, 0); if (shm_addr == (long *) -1) { THROW_ERROR("shmat() cannot attach the shm"); } srandom(time(NULL)); random_num = random(); *shm_addr = random_num; // Spawn a new process for (int i = 0; i < TEST_PROCESS_COMMU_ARGC; i++) { child_argv[i] = (char *)malloc(ARG_BUF_SZ); } snprintf(child_argv[0], ARG_BUF_SZ, "%s", prog_name); snprintf(child_argv[1], ARG_BUF_SZ, "%d", TEST_PROCESS_COMMU); snprintf(child_argv[2], ARG_BUF_SZ, "%d", shmid); snprintf(child_argv[3], ARG_BUF_SZ, "%ld", random_num); child_argv[TEST_PROCESS_COMMU_ARGC] = NULL; if ((ret = execute_in_child(child_argv)) != SUCCESS) { return FAIL; } for (int i = 0; i < TEST_PROCESS_COMMU_ARGC; i++) { free(child_argv[i]); } ret = syscall(SYS_shmdt, (void *)shm_addr); if (ret != 0) { THROW_ERROR("shmdt() failed"); } ret = syscall(SYS_shmctl, shmid, IPC_RMID, NULL); if (ret < 0) { THROW_ERROR("Cannot remove the segment"); } return SUCCESS; } static int test_immediately_rmshm() { int ret, shmid; size_t shm_size = PAGE_SIZE; struct shmid_ds buf; shmid = syscall(SYS_shmget, IPC_PRIVATE, shm_size, IPC_CREAT | IPC_EXCL | S_IRWUSER); if (shmid < 0) { THROW_ERROR("shmget() cannot get the shm"); } ret = syscall(SYS_shmctl, shmid, IPC_RMID, NULL); if (ret < 0) { THROW_ERROR("Cannot remove the segment"); } ret = syscall(SYS_shmctl, shmid, IPC_STAT, NULL); if (ret != -1 || errno != EINVAL) { INFO("Should get errno with EINVAL even though the buf is empty, ret: %d errno: %d\n", ret, errno); return FAIL; } ret = syscall(SYS_shmctl, shmid, IPC_STAT, &buf); if (ret != -1 || errno != EINVAL) { INFO("The shared memory segment should be removed immediately since shm_nattach equals to 0, ret: %d errno: %d\n", ret, errno); return FAIL; } return SUCCESS; } static int test_operate_destroyed_shm() { int shmid, ret; size_t shm_size = PAGE_SIZE; void *shm_addr; key_t key; char *child_argv[TEST_OPERATE_DESTOYED_ARGC + 1]; srand(time(NULL)); key = random(); shmid = syscall(SYS_shmget, key, shm_size, IPC_CREAT | IPC_EXCL | S_IRWUSER); if (shmid < 0) { THROW_ERROR("shmget() cannot get the shm"); } shm_addr = (void *)syscall(SYS_shmat, shmid, NULL, 0); if (shm_addr == (void *) -1) { THROW_ERROR("shmat() cannot attach the shm"); } // Mark the shared memory segment to be destroyed first ret = syscall(SYS_shmctl, shmid, IPC_RMID, NULL); // Spawn a new process for (int i = 0; i < TEST_OPERATE_DESTOYED_ARGC; i++) { child_argv[i] = (char *)malloc(ARG_BUF_SZ); } snprintf(child_argv[0], ARG_BUF_SZ, "%s", prog_name); snprintf(child_argv[1], ARG_BUF_SZ, "%d", TEST_OPERATE_DESTOYED); snprintf(child_argv[2], ARG_BUF_SZ, "%d", key); snprintf(child_argv[3], ARG_BUF_SZ, "%ld", shm_size); snprintf(child_argv[4], ARG_BUF_SZ, "%d", shmid); child_argv[TEST_OPERATE_DESTOYED_ARGC] = NULL; if ((ret = execute_in_child(child_argv)) != SUCCESS) { return FAIL; } for (int i = 0; i < TEST_OPERATE_DESTOYED_ARGC; i++) { free(child_argv[i]); } ret = syscall(SYS_shmdt, shm_addr); return SUCCESS; } // test_no_rmshm() should be place as the last test case // // Occlum checks whether all the memory segment is recycled when the LibOS exits, // to detect and prevent memory leaks. // Such test checks whether all the vmas allocated by `shm mod` are recycled when the LibOS exits, // even though no IPC_RMID is invoked for the shared memory segment. static int test_no_rmshm() { int shmid; size_t shm_size = PAGE_SIZE; void *shm_addr; shmid = syscall(SYS_shmget, IPC_PRIVATE, shm_size, IPC_CREAT | IPC_EXCL | S_IRWUSER); if (shmid < 0) { THROW_ERROR("shmget() cannot get the shm"); } shm_addr = (void *)syscall(SYS_shmat, shmid, NULL, 0); if (shm_addr == (long *) -1) { THROW_ERROR("shmat() cannot attach the shm"); } return SUCCESS; } // ============================================================================ // Funtion invoked in child process for inter-process communication // ============================================================================ static int child_test_get_shmid_by_key(int argc, const char *argv[]) { key_t key; int ret, shmid; size_t shm_size; if (argc != TEST_GET_SHMID_BY_KEY_ARGC) { INFO("Invalid argument, argc: %d\n", argc); return FAIL; } key = atoi(argv[2]); shmid = atoi(argv[3]); shm_size = atol(argv[4]); // Get the shmid by the key input from parent process ret = syscall(SYS_shmget, key, shm_size, S_IRWUSER); if (ret < 0) { THROW_ERROR("shmget() cannot get the shm"); } if (ret != shmid) { INFO("shmid get in child process mismatches that in parent process, correct: %d actual: %d\n", shmid, key); return FAIL; } return SUCCESS; } static int child_test_process_communication(int argc, const char *argv[]) { int shmid, ret; long random_num, *shm_ptr; if (argc != TEST_PROCESS_COMMU_ARGC) { INFO("Invalid argument, argc: %d\n", argc); return FAIL; } shmid = atoi(argv[2]); random_num = atol(argv[3]); // Check whether the value in the shared memory segment equals to // that written in parent process shm_ptr = (long *)syscall(SYS_shmat, shmid, NULL, 0); if (shm_ptr == (long *) -1) { THROW_ERROR("shmat() cannot attach to the shm"); } if (*shm_ptr != random_num) { INFO("Data in shm mismatches, correct: %ld actual: %ld\n", random_num, *shm_ptr); return FAIL; } ret = syscall(SYS_shmdt, (void *)shm_ptr); if (ret != 0) { THROW_ERROR("shmdt() failed"); } return SUCCESS; } static int child_test_operate_destroyed_shm(int argc, const char *argv[]) { int shmid, ret; key_t key; size_t shm_size; void *shm_addr; if (argc != TEST_OPERATE_DESTOYED_ARGC) { INFO("Invalid argument, argc: %d\n", argc); return FAIL; } key = atoi(argv[2]); shm_size = atol(argv[3]); shmid = atoi(argv[4]); ret = syscall(SYS_shmget, key, shm_size, S_IRWUSER); if (ret != -1 || errno != ENOENT) { INFO("shmget() should return ENOENT because the segment is marked to be destoyed, ret: %d errno: %d\n", ret, errno); return FAIL; } shm_addr = (void *)syscall(SYS_shmat, shmid, NULL, 0); if (shm_addr == (void *) -1) { THROW_ERROR("shmat() cannot attach the shm"); } if ((ret = syscall(SYS_shmdt, shm_addr)) != 0) { THROW_ERROR("shmdt() failed"); } return SUCCESS; } // ============================================================================ // Test suite main // ============================================================================ static test_case_t test_cases[] = { TEST_CASE(test_shmget_shmid_from_key), TEST_CASE(test_process_communication), TEST_CASE(test_immediately_rmshm), TEST_CASE(test_operate_destroyed_shm), // test_no_rmshm() should be place as the last test case TEST_CASE(test_no_rmshm), }; int main(int argc, const char *argv[]) { if (argc == 1) { // Parent process will arrive here return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); } else { // Child process will arrive here int option = atoi(argv[1]), ret; switch (option) { case TEST_GET_SHMID_BY_KEY: ret = child_test_get_shmid_by_key(argc, argv); break; case TEST_PROCESS_COMMU: ret = child_test_process_communication(argc, argv); break; case TEST_OPERATE_DESTOYED: ret = child_test_operate_destroyed_shm(argc, argv); break; default: INFO("Invalid option: %d\n", option); ret = FAIL; } if (ret == SUCCESS) { return 0; } else { return -1; } } }