OS Kernel
µC/FS can be used with or without an RTOS. Either way, an OS port must be included in your project. The port includes one code/header file pair:
fs_os.c
fs_os.h
µC/FS manages devices and data structures that may not be accessed by severally tasks simultaneously. An OS kernel port leverages the kernel’s mutual exclusion services (mutexes) for that purpose.
These files are generally placed in a directory named according to the following rubric:
\Micrium\Software\uC-FS\OS\<os_name>
Four sets of files are included with the µC/FS distribution:
\Micrium\Software\uC-FS\OS\Template | Template |
\Micrium\Software\uC-FS\OS\None | No OS kernel port |
\Micrium\Software\uC-FS\OS\uCOS-II | µC/OS-II port |
\Micrium\Software\uC-FS\OS\uCOS-III | µC/OS-III port |
If you don’t use any OS, you should include the port for no OS in your project. You must also make sure that you manage interrupts correctly.
If you are using µC/OS-II or µC/OS-III, you should include the appropriate ports in your project. If you use another OS, you should create your own port based on the template. The functions that need to be written in this port are described here.
FS_OS_Init(), FS_OS_Lock() and FS_OS_Unlock()
The core data structures are protected by a single mutex. FS_OS_Init()
creates this semaphore. FS_OS_Lock()
and FS_OS_Unlock()
acquire and release the resource. Lock operations are never nested.
FS_OS_DevInit(), FS_OS_DevLock() and FS_OS_DevUnlock()
File system device, generally, do not tolerate multiple simultaneous accesses. A different mutex controls access to each device and information about it in RAM. FS_OS_DevInit()
creates one mutex for each possible device. FS_OS_DevLock()
and FS_OS_DevUnlock()
acquire and release access to a specific device. Lock operations for the same device are never nested.
FS_OS_FileInit(), FS_OS_FileAccept(), FS_OS_FileLock() and FS_OS_FileUnlock()
Multiple calls to file access functions may be required for a file operation that must be guaranteed atomic. For example, a file may be a conduit of data from one task to several. If a data entry cannot be read in a single file read, some lock is necessary to prevent preemption by another consumer. File locks, represented by API functions like FSFile_LockGet()
and flockfile()
, provide a solution. Four functions implement the actual lock in the OS port. FS_OS_FileInit()
creates one mutex for each possible file. FS_OS_FileLock()
/ FS_OS_FileAccept()
and FS_OS_FileUnlock()
acquire and release access to a specific file. Lock operations for the same file MAY be nested, so the implementations must be able to determine whether the active task owns the mutex. If it does, then an associated lock count should be incremented; otherwise, it should try to acquire the resource as normal.
FS_OS_WorkingDirGet() and FS_OS_WorkingDirSet()
File and directory paths are typically interpreted absolutely; they must start at the root directory, specifying every intermediate path component. If much work will be accomplished upon files in a certain directory or a task requires a root directory as part of its context, working directories are valuable. Once a working directory is set for a task, subsequent non-absolute paths will be interpreted relative to the set directory.
#if (FS_CFG_WORKING_DIR_EN == DEF_ENABLED) CPU_CHAR *FS_OS_WorkingDirGet (void) (1) { OS_ERR os_err; CPU_INT32U reg_val; CPU_CHAR *p_working_dir; reg_val = OSTaskRegGet((OS_TCB *) 0, FS_OS_REG_ID_WORKING_DIR, &os_err); if (os_err != OS_ERR_NONE) { reg_val = 0u; } p_working_dir = (CPU_CHAR *)reg_val; return (p_working_dir); } #endif #if (FS_CFG_WORKING_DIR_EN == DEF_ENABLED) void FS_OS_WorkingDirSet (CPU_CHAR *p_working_dir, FS_ERR *p_err) { OS_ERR os_err; CPU_INT32U reg_val; reg_val = (CPU_INT32U)p_working_dir; OSTaskRegSet((OS_TCB *) 0, FS_OS_RegIdWorkingDir, (OS_REG) reg_val, &os_err); if(os_err != OS_ERR_NONE) { *p_err = FS_ERR_OS; return; } *p_err = FS_ERR_NONE; } #endif
(1) FS_OS_WorkingDirGet()
gets the pointer to the working directory associated with the active task. In µC/OS-III, the pointer is stored in one of the task registers, a set of software data that is part of the task context (just like hardware register values). The implantation casts the integral register value to a pointer to a character. If no working directory has been assigned, the return value must be a pointer to NULL. In the case of µC/OS-III, that will be done because the register values are cleared upon task creation.
(2) FS_OS_WorkingDirSet()
associates a working directory with the active task. The pointer is cast to the integral register data type and stored in a task register.
The application calls either of the core file system functions FS_WorkingDirSet()
or fs_chdir()
to set the working directory. The core function forms the full path of the working directory and “saves” it with the OS port function FS_OS_WorkingDirSet()
. The port function should associate it with the task in some manner so that it can be retrieved with FS_OS_WorkingDirGet()
even after many context switches have occurred.
#if (FS_CFG_WORKING_DIR_EN == DEF_ENABLED) void FS_OS_WorkingDirFree (OS_TCB *p_tcb) { OS_ERR os_err; CPU_INT32U reg_val; CPU_CHAR *path_buf; reg_val = OSTaskRegGet( p_tcb, FS_OS_REG_ID_WORKING_DIR, &os_err); if (os_err != OS_ERR_NONE) { return; } if (reg_val == 0u) { (1) return; } path_buf = (CPU_CHAR *)reg_val; FS_WorkingDirObjFree(path_buf); (2) } #endif
(1) If the register value is zero, no working directory has been assigned and no action need be taken.
(2) FS_WorkingDirObjFree()
frees the working directory object to the working directory pool. If this were not done, the unfreed object would constitute a memory leak that could deplete the heap memory eventually.
The character string for the working directory is allocated from the µC/LIB heap. If a task is deleted, it must be freed (made available for future allocation) to avert a crippling memory leak. The internal file system function FS_WorkingDirObjFree()
releases the string to an object pool. In the port for µC/OS-III, that function is called by FS_OS_WorkingDirFree()
which must be called by the assigned task delete hook.
FS_OS_Dly_ms()
Device drivers and example device driver ports delay task execution FS_OS_Dly_ms()
. Common functions allow BSP developers to optimize implementation easily. A millisecond delay may be accomplished with an OS kernel service, if available. The trivial implementation of a delay (particularly a sub-millisecond delay) is a while loop; better performance may be achieved with hardware timers with semaphores for wait and asynchronous notification. The best solution will vary from one platform to another, since the additional context switches may prove burdensome. No matter which strategy is selected, the function must delay for at least the specified time amount; otherwise, sporadic errors can occur. Ideally, the actual time delay will equal the specified time amount to avoid wasting processor cycles.
void FS_BSP_Dly_ms (CPU_INT16U ms) { /* $$$$ Insert code to delay for specified number of millieconds. */ }
FS_OS_Sem####()
The four generic OS semaphore functions provide a complete abstraction of a basic OS kernel service. FS_OS_SemCreate()
creates a semaphore which may later be deleted with FS_OS_SemDel()
. FS_OS_SemPost()
signals the semaphore (with or without timeout) and FS_OS_SemPend()
waits until the semaphore is signaled. On systems without an OS kernel, the trivial implementations in FS_OS_SemCreate()/Del() and FS_OS_SemPend()/Post() are recommended.
CPU_BOOLEAN FS_OS_SemCreate (FS_BSP_SEM *p_sem, (1) CPU_INT16U cnt) { *p_sem = cnt; /* $$$$ Create semaphore with initial count 'cnt'. */ return (DEF_OK); } CPU_BOOLEAN FS_OS_SemDel (FS_BSP_SEM *p_sem) (2) { *p_sem = 0u; /* $$$$ Delete semaphore. */ return (DEF_OK); }
(1) FS_OS_SemCreate()
creates a semaphore in the variable p_sem
. For this trivial implementation, FS_BSP_SEM
is a integer type which stores the current count, i.e., the number of objects available.
(2) FS_OS_SemDel()
deletes a semaphore created by FS_OS_SemCreate()
.
CPU_BOOLEAN FS_OS_SemPend (FS_BSP_SEM *p_sem, (1) CPU_INT32U timeout) { CPU_INT32U timeout_cnts; CPU_INT16U sem_val; CPU_SR_ALLOC(); if (timeout == 0u) { sem_val = 0u; while (sem_val == 0u) { CPU_CRITICAL_ENTER(); sem_val = *p_sem; /* $$$$ If semaphore available ... */ if (sem_val > 0u) { *p_sem = sem_val - 1u; /* ... decrement semaphore count. */ } CPU_CRITICAL_EXIT(); } } else { timeout_cnts = timeout * FS_BSP_CNTS_PER_MS; sem_val = 0; while ((timeout_cnts > 0u) && (sem_val == 0u)) { CPU_CRITICAL_ENTER(); sem_val = *p_sem; /* $$$$ If semaphore available ... */ if (sem_val > 0) { *p_sem = sem_val - 1u; /* ... decrement semaphore count. */ } CPU_CRITICAL_EXIT(); timeout_cnts--; } } if (sem_val == 0u) { return (DEF_FAIL); } else { return (DEF_OK); } } CPU_BOOLEAN FS_OS_SemPost (FS_BSP_SEM *p_sem) (2) { CPU_INT16U sem_val; CPU_SR_ALLOC(); CPU_CRITICAL_ENTER(); sem_val = *p_sem; /* $$$$ Increment semaphore value. */ sem_val++; *p_sem = sem_val; CPU_CRITICAL_EXIT(); return (DEF_OK); }
(1) FS_OS_SemPend()
waits until a semaphore is signaled. If a zero timeout is given, the wait is possibly infinite (it never times out).
(2) FS_OS_SemPost()
signals a semaphore.