/// @file sem.c /// @brief /// @copyright (c) 2014-2023 This file is distributed under the MIT License. /// See LICENSE.md for details. /// @details /// # 03/04/2023 /// At the moment we have various functions with their description (see /// comments). The first time that the function semget is called, we are going /// to generate the list and the first semaphore set with the assumption that we /// are not given a IPC_PRIVATE key (temporary ofc). /// We are able to create new semaphores set and generate unique keys /// (IPC_PRIVATE) with a counter that searches for the first possible key to /// assign. /// Temporary idea: /// - IPC_CREAT flag, it should be working properly, it creates a semaphore set /// with the given key. /// - IPC_EXCL flag, it should be working properly, it returns -1 and sets the /// errno if the key is already used. /// /// # 11/04/2023 /// Right now we have a first version of working semaphores in MentOS. /// We have completed the semctl function and we have implemented the first /// version of the semop function both user and kernel side. /// The way it works is pretty straightforward, the user tries to perform an /// operation and based on the value of the semaphore the kernel returns certain /// values. If the operation cannot be performed then the user will stay in a /// while loop. The cycle ends with a positive return value (the operation has /// been taken care of) or in case of errors. /// For testing purposes -> you can try the t_semget and the t_sem1 tests. They /// both use semaphores and blocking / non blocking operations. t_sem1 is also /// an exercise that was assingned by Professor Drago in the OS course. // ============================================================================ // Setup the logging for this file (do this before any other include). #include "sys/kernel_levels.h" // Include kernel log levels. #define __DEBUG_HEADER__ "[IPCsem]" ///< Change header. #define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level. #include "io/debug.h" // Include debugging functions. // ============================================================================ #include "ipc/ipc.h" #include "sys/sem.h" #include "assert.h" #include "fcntl.h" #include "klib/list.h" #include "process/process.h" #include "process/scheduler.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #include "sys/errno.h" ///@brief A value to compute the semid value. int __sem_id = 0; /// @brief Semaphore management structure. typedef struct { /// @brief Semaphore ID associated to the semaphore set. int id; /// @brief The semaphore data strcutre. struct semid_ds semid; /// @brief List of all the semaphores. struct sem *sem_base; /// Reference inside the list of semaphore management structures. list_head list; } sem_info_t; /// @brief List of all current active semaphores. list_head semaphores_list; // ============================================================================ // MEMORY MANAGEMENT (Private) // ============================================================================ /// @brief Allocates the memory for semaphore management structure. /// @param key IPC_KEY associated with the set of semaphores. /// @param nsems number of semaphores to initialize. /// @param semflg flags used to create the set of semaphores. /// @return a pointer to the allocated semaphore management structure. static inline sem_info_t *__sem_info_alloc(key_t key, int nsems, int semflg) { // Allocate the memory. sem_info_t *sem_info = (sem_info_t *)kmalloc(sizeof(sem_info_t)); // Check the allocated memory. assert(sem_info && "Failed to allocate memory for a semaphore management structure."); // Clean the memory. memset(sem_info, 0, sizeof(sem_info_t)); // Allocate the memory for semaphores. sem_info->sem_base = (struct sem *)kmalloc(sizeof(struct sem) * nsems); // Check the allocated memory. assert(sem_info->sem_base && "Failed to allocate memory for a set of semaphores."); // Clean the memory. memset(sem_info->sem_base, 0, sizeof(struct sem) * nsems); // Initialize its values. sem_info->id = ++__sem_id; sem_info->semid.sem_perm = register_ipc(key, semflg & 0x1FF); sem_info->semid.sem_otime = 0; sem_info->semid.sem_ctime = 0; sem_info->semid.sem_nsems = nsems; for (int i = 0; i < nsems; i++) { sem_info->sem_base[i].sem_pid = sys_getpid(); sem_info->sem_base[i].sem_val = 0; sem_info->sem_base[i].sem_ncnt = 0; sem_info->sem_base[i].sem_zcnt = 0; } // Return the semaphore management structure. return sem_info; } /// @brief Frees the memory of a semaphore management structure. /// @param sem_info pointer to the semaphore management structure. static inline void __sem_info_dealloc(sem_info_t *sem_info) { assert(sem_info && "Received a NULL pointer."); // Deallocate the array of semaphores. kfree(sem_info->sem_base); // Deallocate the semid memory. kfree(sem_info); } // ============================================================================ // LIST MANAGEMENT/SEARCH FUNCTIONS (Private) // ============================================================================ /// @brief Searches for the semaphore with the given id. /// @param semid the id we are searching. /// @return the semaphore with the given id. static inline sem_info_t *__list_find_sem_info_by_id(int semid) { sem_info_t *sem_info; // Iterate through the list of semaphore set. list_for_each_decl(it, &semaphores_list) { // Get the current entry. sem_info = list_entry(it, sem_info_t, list); // If semaphore set is valid, check the id. if (sem_info && (sem_info->id == semid)) { return sem_info; } } return NULL; } /// @brief Searches for the semaphore with the given key. /// @param key the key we are searching. /// @return the semaphore with the given key. static inline sem_info_t *__list_find_sem_info_by_key(key_t key) { sem_info_t *sem_info; // Iterate through the list of semaphore set. list_for_each_decl(it, &semaphores_list) { // Get the current entry. sem_info = list_entry(it, sem_info_t, list); // If semaphore set is valid, check the id. if (sem_info && (sem_info->semid.sem_perm.key == key)) { return sem_info; } } return NULL; } static inline void __list_add_sem_info(sem_info_t *sem_info) { assert(sem_info && "Received a NULL pointer."); // Add the new sem_info at the end. list_head_insert_before(&sem_info->list, &semaphores_list); } static inline void __list_remove_sem_info(sem_info_t *sem_info) { assert(sem_info && "Received a NULL pointer."); // Delete the sem_info from the list. list_head_remove(&sem_info->list); } // ============================================================================ // SYSTEM FUNCTIONS // ============================================================================ int sem_init() { list_head_init(&semaphores_list); return 0; } long sys_semget(key_t key, int nsems, int semflg) { sem_info_t *sem_info = NULL; // Check if nsems is less than 0 or greater than the maximum number of // semaphores per semaphore set. if ((nsems < 0) || (nsems > SEM_SET_MAX)) { pr_err("Wrong number of semaphores for semaphore set.\n"); return -EINVAL; } // Need to find a unique key. if (key == IPC_PRIVATE) { // Exit when i find a unique key. do { key = (int)-rand(); } while (__list_find_sem_info_by_key(key)); // We have a unique key, create the semaphore set. sem_info = __sem_info_alloc(key, nsems, semflg); // Add the semaphore set to the list. __list_add_sem_info(sem_info); } else { // Get the semaphore set if it exists. sem_info = __list_find_sem_info_by_key(key); // Check if a semaphore set with the given key already exists, but nsems is // larger than the number of semaphores in that set. if (sem_info && (nsems > sem_info->semid.sem_nsems)) { pr_err("Wrong number of semaphores for and existing semaphore set.\n"); return -EINVAL; } // Check if no semaphore set exists for the given key and semflg did not // specify IPC_CREAT. if (!sem_info && !(semflg & IPC_CREAT)) { pr_err("No semaphore set exists for the given key and semflg did not specify IPC_CREAT.\n"); return -ENOENT; } // Check if IPC_CREAT and IPC_EXCL were specified in semflg, but a semaphore // set already exists for key. if (sem_info && (semflg & IPC_CREAT) && (semflg & IPC_EXCL)) { pr_err("IPC_CREAT and IPC_EXCL were specified in semflg, but a semaphore set already exists for key.\n"); return -EEXIST; } // Check if the semaphore set exists for the given key, but the calling // process does not have permission to access the set. if (sem_info && !ipc_valid_permissions(semflg, &sem_info->semid.sem_perm)) { pr_err("The semaphore set exists for the given key, but the calling process does not have permission to access the set.\n"); return -EACCES; } // If the semaphore set does not exist we need to create a new one. if (sem_info == NULL) { // Create the semaphore set. sem_info = __sem_info_alloc(key, nsems, semflg); // Add the semaphore set to the list. __list_add_sem_info(sem_info); } } // Return the id of the semaphore set. return sem_info->id; } long sys_semop(int semid, struct sembuf *sops, unsigned nsops) { sem_info_t *sem_info = NULL; // The semid is less than zero. if (semid < 0) { pr_err("The semid is less than zero.\n"); return -EINVAL; } // The pointer to the operation is NULL. if (!sops) { pr_err("The pointer to the operation is NULL.\n"); return -EINVAL; } // The value of nsops is negative. if (nsops <= 0) { pr_err("The value of nsops is negative.\n"); return -EINVAL; } // Search for the semaphore. sem_info = __list_find_sem_info_by_id(semid); // The semaphore set doesn't exist. if (!sem_info) { pr_err("The semaphore set doesn't exist.\n"); return -EINVAL; } // The value of sem_num is less than 0 or greater than or equal to the number of semaphores in the set. if ((sops->sem_num < 0) || (sops->sem_num >= sem_info->semid.sem_nsems)) { pr_err("The value of sem_num is less than 0 or greater than or equal to the number of semaphores in the set.\n"); return -EFBIG; } // Check if the semaphore set exists for the given key, but the calling // process does not have permission to access the set. if (sem_info && !ipc_valid_permissions(O_RDWR, &sem_info->semid.sem_perm)) { pr_err("The semaphore set exists for the given key, but the calling process does not have permission to access the set.\n"); return -EACCES; } // Update semop time. sem_info->semid.sem_otime = sys_time(NULL); // If the operation is negative then we need to check for possible blocking // operation. If the value of the sem were to become negative then we return // a special value. if (((int)sem_info->sem_base[sops->sem_num].sem_val + (int)sops->sem_op) < 0) { // The value would become negative, we cannot perform the operation. return -EAGAIN; } // Update the semaphore value. sem_info->sem_base[sops->sem_num].sem_val += sops->sem_op; // Update the pid of the process that did last op. sem_info->sem_base[sops->sem_num].sem_pid = sys_getpid(); // Update the time. sem_info->semid.sem_ctime = sys_time(NULL); return 0; } long sys_semctl(int semid, int semnum, int cmd, union semun *arg) { sem_info_t *sem_info = NULL; task_struct *task = NULL; // Search for the semaphore. sem_info = __list_find_sem_info_by_id(semid); // The semaphore set doesn't exist. if (!sem_info) { pr_err("The semaphore set doesn't exist.\n"); return -EINVAL; } // Get the calling task. task = scheduler_get_current_process(); assert(task && "Failed to get the current running process."); if (cmd == IPC_RMID) { // Remove the semaphore set; any processes blocked is awakened (errno set to // EIDRM); no argument required. if ((sem_info->semid.sem_perm.uid != task->uid) && (sem_info->semid.sem_perm.cuid != task->uid)) { pr_err("The calling process is not the creator or the owner of the semaphore set.\n"); return -EPERM; } // Remove the set from the list. __list_remove_sem_info(sem_info); // Delete the set. __sem_info_dealloc(sem_info); } else if (cmd == SETVAL) { // The value of the semnum-th semaphore in the set is initialized to the // value specified in arg.val. // Check if the index is valid. if ((semnum < 0) || (semnum >= (sem_info->semid.sem_nsems))) { pr_err("Semaphore number out of bound (%d not in [%d, %d])\n", semnum, 0, sem_info->semid.sem_nsems); return -EINVAL; } // Check if the argument is a null pointer. if (!arg) { pr_err("The argument is NULL.\n"); return -EINVAL; } // Checking if the value is valid. if (arg->val < 0) { pr_err("The value to set is not valid %d.\n", arg->val); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_WRONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } // Setting the value. sem_info->sem_base[semnum].sem_val = arg->val; // Update the last change time. sem_info->semid.sem_ctime = sys_time(NULL); } else if (cmd == SETALL) { // Initialize all semaphore in the set referred to by semid, using the // values supplied in the array pointed to by arg.array. // Check if the argument is a null pointer. if (!arg) { pr_err("The argument is NULL.\n"); return -EINVAL; } // Check if the array is valid. if (!arg->array) { pr_err("The array is NULL.\n"); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_WRONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } // Setting the values. for (unsigned i = 0; i < sem_info->semid.sem_nsems; ++i) { sem_info->sem_base[i].sem_val = arg->array[i]; } // Update the last change time. sem_info->semid.sem_ctime = sys_time(NULL); } else if (cmd == IPC_STAT) { // Place a copy of the semid_ds data structure in the buffer pointed to by // arg.buf. // Check if the argument is a null pointer. if (!arg) { pr_err("The argument is NULL.\n"); return -EINVAL; } // Check if the buffer is a null pointer. if (!arg->buf) { pr_err("The buffer is NULL.\n"); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_RDONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } // Copying all the data. memcpy(arg->buf, &sem_info->semid, sizeof(struct semid_ds)); } else if (cmd == GETALL) { // Retrieve the values of all of the semaphores in the set referred to by // semid, placing them in the array pointed to by arg.array. // Check if the argument is a null pointer. if (!arg) { pr_err("The argument is NULL.\n"); return -EINVAL; } // Check if the array is valid. if (!arg->array) { pr_err("The array is NULL.\n"); return -EINVAL; } for (unsigned i = 0; i < sem_info->semid.sem_nsems; ++i) { arg->array[i] = sem_info->sem_base[i].sem_val; } } else if (cmd == GETVAL) { // Returns the value of the semnum-th semaphore in the set specified by // semid; no argument required. // Check if the index is valid. if ((semnum < 0) || (semnum >= (sem_info->semid.sem_nsems))) { pr_err("Semaphore number out of bound (%d not in [%d, %d])\n", semnum, 0, sem_info->semid.sem_nsems); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_RDONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } return sem_info->sem_base[semnum].sem_val; } else if (cmd == GETPID) { // Return the process ID of the last process to perform a semop on the // semnum-th semaphore. // Check if the index is valid. if ((semnum < 0) || (semnum >= (sem_info->semid.sem_nsems))) { pr_err("Semaphore number out of bound (%d not in [%d, %d])\n", semnum, 0, sem_info->semid.sem_nsems); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_RDONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } return sem_info->sem_base[semnum].sem_pid; } else if (cmd == GETNCNT) { // Return the number of processes currently waiting for the resources to // become available. // Check if the index is valid. if ((semnum < 0) || (semnum >= (sem_info->semid.sem_nsems))) { pr_err("Semaphore number out of bound (%d not in [%d, %d])\n", semnum, 0, sem_info->semid.sem_nsems); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_RDONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } return sem_info->sem_base[semnum].sem_ncnt; } else if (cmd == GETZCNT) { // Return the number of processes currently waiting for the value of the // semnum-th semaphore to become 0. // Check if the index is valid. if ((semnum < 0) || (semnum >= (sem_info->semid.sem_nsems))) { pr_err("Semaphore number out of bound (%d not in [%d, %d])\n", semnum, 0, sem_info->semid.sem_nsems); return -EINVAL; } // Check permissions. if (!ipc_valid_permissions(O_RDONLY, &sem_info->semid.sem_perm)) { pr_err("The calling process does not have read permission to access the set.\n"); return -EACCES; } return sem_info->sem_base[semnum].sem_zcnt; } else if (cmd == SEM_STAT) { pr_err("Not implemented.\n"); return -ENOSYS; } else if (cmd == SEM_INFO) { pr_err("Not implemented.\n"); return -ENOSYS; } else { return -EINVAL; } return 0; } // ============================================================================ // PROCFS FUNCTIONS // ============================================================================ ssize_t procipc_sem_read(vfs_file_t *file, char *buf, off_t offset, size_t nbyte) { if (!file) { pr_err("Received a NULL file.\n"); return -ENOENT; } size_t buffer_len = 0, read_pos = 0, ret = 0; ssize_t write_count = 0; sem_info_t *sem_info = NULL; char buffer[BUFSIZ]; // Prepare a buffer. memset(buffer, 0, BUFSIZ); // Prepare the header. ret = sprintf(buffer, "key semid perms nsems uid gid cuid cgid otime ctime\n"); // Iterate through the list of semaphore set. list_for_each_decl(it, &semaphores_list) { // Get the current entry. sem_info = list_entry(it, sem_info_t, list); // Add the line. ret += sprintf( buffer + ret, "%8d %5d %10d %7d %5d %4d %5d %9d %10d %d\n", abs(sem_info->semid.sem_perm.key), sem_info->id, sem_info->semid.sem_perm.mode, sem_info->semid.sem_nsems, sem_info->semid.sem_perm.uid, sem_info->semid.sem_perm.gid, sem_info->semid.sem_perm.cuid, sem_info->semid.sem_perm.cgid, sem_info->semid.sem_otime, sem_info->semid.sem_ctime); } sprintf(buffer + ret, "\n"); // Perform read. buffer_len = strlen(buffer); read_pos = offset; if (read_pos < buffer_len) { while ((write_count < nbyte) && (read_pos < buffer_len)) { buf[write_count] = buffer[read_pos]; // Move the pointers. ++read_pos, ++write_count; } } return write_count; }