1. Trang chủ
  2. » Luận Văn - Báo Cáo

Porting Applications

36 141 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 36
Dung lượng 760,35 KB

Nội dung

165 Chapter 6 Porting Applications A developer faces a challenging task when porting applications from a tradi- tional RTOS such as VxWorks, pSoS, Nucleus, and so on, to embedded Linux. The difficulty arises because of the entirely different programming model of Linux as compared to other RTOSs. This chapter discusses a roadmap for porting applications from a traditional RTOS to embedded Linux. It also discusses various techniques that are generally employed to facilitate porting. 6.1 Architectural Comparison In this section we compare the architecture of a traditional RTOS with embedded Linux. A traditional RTOS is generally based on a flat memory model. All the applications along with the kernel are part of a single image that is then loaded into the target. Kernel services such as schedulers, memory management, timers, and the like, run in the same physical address space as user applications. Applications request any kernel service using a simple function call interface. User applications also share common address space among themselves. Figure 6.1 shows a flat memory model of a traditional RTOS. The major drawback of such an RTOS is that it is based on a flat memory model. MMU is not utilized for memory protection. Consequently any user application can corrupt kernel code or data. It can also corrupt data structures of some other application. Linux on the other hand utilizes MMU to provide separate virtual address space to each process. The virtual address space is protected; that is, a process cannot access any data structure belonging to some other process. Kernel code and data structures are also protected. Access to kernel services by user applications is provided through a well-defined system call an interface. Figure 6.2 shows an MMU-based memory model of Linux. 166 Embedded Linux System Design and Development From now on any reference to RTOS refers to a traditional RTOS with a flat memory model and no memory protection unless specified. The porting issues that are evident from the above comparison are Ⅲ Applications that “share” single address space in an RTOS should be ported to the protected virtual address space model of Linux. Ⅲ An RTOS generally provides its own native set of APIs for various services such as task creation, IPC, timers, and so on. Thus a mapping of each such native API to an equivalent Linux API must be defined. Ⅲ A kernel interface in Linux is not a simple function call interface. So user applications cannot make any direct driver or kernel calls. Figure 6.1 RTOS flat memory model. Device Drivers Timers IPC Networking Scheduler Memory Management OS Services User Task 1 User Task 2 User Task 3 Physical Memory Start of Physical Memory End of Physical Memory Porting Applications 167 6.2 Application Porting Roadmap In this section we discuss a generic application porting roadmap from an RTOS to embedded Linux. The following sections cover the porting roadmap in detail. 6.2.1 Decide Porting Strategy Divide all your RTOS tasks into two broad categories: user-space tasks and kernel tasks. For example, any UI task is a user-space task and any hardware initialization task is a kernel task. You should also identify a list of user-space and kernel functions. For example, any function that manipulates device registers is a kernel function and any function that reads some data from a file is a user-space function. Two porting strategies could be adopted. Note that in both approaches kernel tasks migrate as Linux kernel threads. The following discussion applies to user space tasks only. One-Process Model In this approach user-space RTOS tasks migrate as separate threads in a single Linux process as shown in Figure 6.3. The advantage of this approach is the reduced porting effort as it requires fewer modifications in the existing code Figure 6.2 Linux memory model. Device Drivers Timers IPC Networking Scheduler Memory Mangement User Process 1 User Process 3 User Process 2 Start of Virtual Memory End of Virtual Memory User Space Kernel OS Services 168 Embedded Linux System Design and Development base. The biggest disadvantage is no memory protection between threads inside the process. However kernel services, drivers, and so on are fully protected. Multiprocess Model Categorize tasks as unrelated, related, and key tasks. Ⅲ Unrelated tasks: Loosely coupled tasks that use IPC mechanisms offered by the RTOS to communicate with other tasks or stand-alone tasks 1 that are not related to other tasks could be migrated as separate Linux processes. Ⅲ Related tasks: Tasks that share global variables and function callbacks fall under this category. They could be migrated as separate threads in one Linux process. Ⅲ Key tasks: Tasks that perform key activities such as system watchdog tasks should be migrated as separate Linux processes. This ensures that key tasks are protected from memory corruption of other tasks. Figure 6.4 shows this approach. The advantages of this model are Ⅲ Per-process memory protection is achieved. A task cannot corrupt address space belonging to some other process. Ⅲ It’s extensible. New features can be added keeping this model in mind. Ⅲ Applications can fully exploit the benefits of the Linux programming model. The biggest disadvantage of this approach is that migration to Linux using this model is a time-consuming process. You may need to redesign most of the applications. One such time-consuming activity is porting user-space Figure 6.3 Migration in one-process model. User Task 1 User Task 2 Kernel Task 1 Kernel Task 2 read 1 read 2 Kernel read 1 Kernel read 2 Linux Process Kernel User Space RTOS Linux Porting Applications 169 libraries. The trouble comes when the library maintains some global variables that are manipulated by multiple tasks. Assume you decide to port a library as a shared library in Linux. Thus you get the advantage of text sharing across multiple processes. Now what about the global data in the library? In a shared library only text is shared and data is per-process private. Thus all the global variables in the library now become per-process globals. You cannot modify them in one process and expect changes to become visible in another. Thus to support this, you need to redesign applications using the library to use proper IPC mechanisms among themselves. You may also be tempted to put such tasks under the related tasks category but you lose the benefits of the multiprocess model in that case. 6.2.2 Write an Operating System Porting Layer (OSPL) This layer emulates RTOS APIs using Linux APIs as shown in Figure 6.5. A well-written OSPL minimizes changes to your existing code base. To achieve this, mapping between RTOS APIs and Linux APIs must be defined. The mapping falls under the following two categories. Ⅲ One-to-one mapping: Every RTOS API can be emulated using a single Linux API. The arguments or return value of the equivalent Linux API may differ but the expected function behavior is the same. Ⅲ One-to-many mapping: More than one Linux API is necessary to emulate an RTOS API. For many RTOS APIs you also need to define the mapping with Linux kernel APIs as these APIs may be used by kernel tasks. You can either have Figure 6.4 Migration in multiprocess model. User Task 1 User Task 2 Kernel Task 1 read 1 read 2 Kernel read 1 Linux Process 1 Kernel User Space RTOS Linux User Task 3 User Task 4 read 1 read 2 Linux Process 2 IPC 170 Embedded Linux System Design and Development a separate kernel and user OSPL or have a single library that links in both user and kernel. An OSPL API for the latter case looks like the following. void rtosAPI(void){ #ifndef __KERNEL__ /* Equivalent user space Linux API */ #else /* Equivalent Linux kernel API */ #endif } Note that when defining mapping of RTOS APIs to Linux APIs you may come across some RTOS APIs that cannot be emulated using Linux APIs without avoiding any changes to the existing code base. In such cases you may need to rewrite some portion of your existing code. 6.2.3 Write a Kernel API Driver Sometimes you face a difficulty when making a decision of porting a task to user or kernel space as it calls both user and kernel functions. The same problem occurs with the function that calls both user-space and kernel functions. For example, consider function func calling function func1 and func2 . func1 is a user-space function and func2 is a kernel function. void func(){ func1(); <-- User-space function func2(); <-- Kernel function } Now where should the function func be ported? In user space or kernel space? You need to write a kernel API driver to support such cases. In the Figure 6.5 Operating system porting layer. Applications RTOS APIs OSPL Linux APIs Porting Applications 171 kernel API driver model, function func is ported in user space by providing an interface for function func2 in user space. The kernel API driver is discussed in detail in Section 6.5. In this section we discussed an application porting roadmap from an RTOS to Linux. The rest of the chapter is divided into three parts. Ⅲ In the first part we discuss pthreads (POSIX threads) in brief. Pthreads is a Linux threading model. The section covers all the pthreads operations that one should understand before starting the porting process. Ⅲ In the second part we write a small OSPL supporting only task creation, task destruction, and mutex APIs. Ⅲ Finally we discuss the kernel API driver. 6.3 Programming with Pthreads To discuss various pthreads operations we have taken a very simple MP3 player located in file player.c . There are two main components of the player. Ⅲ Initialization: This includes audio subsystem initialization in a separate thread. It’s used for demonstrating thread creation and exit routines. Ⅲ Decoding: This is the core of the application. Two threads of execution are involved. The main thread reads MP3 data from a file and adds it in a queue. The decoder thread dequeues the data, decodes it, and plays it out. The queue is a shared data structure between the main and decoder threads. Figure 6.6 shows the various entities that are involved during the decoding phase. The idea here is to demonstrate various pthread synchro- nization primitives in a greater detail. Please note that this section is not a complete pthreads reference manual. Our aim is give you sufficient details to kickstart your development with pthreads. Also in our player example we have intentionally omitted player- specific details regarding decoding and playback. This is done to give more emphasis to pthreads operations in the player. Figure 6.6 Simple audio player. Main read Decoder read Input from File Ouput Shared Resource Queue 172 Embedded Linux System Design and Development 6.3.1 Thread Creation and Exit A new thread of execution is created by calling the pthread_create function. The prototype of the function is int pthread_create (pthread_t * thread_id, pthread_attr_t *thread_attributes, void * (*start_routine)(void *), void * arg); The function returns zero on success and the identifier of the created thread is stored in the first argument thread_id . The new thread starts its execution from the start_routine function. arg is an argument to start_routine . thread_attributes represents various thread attributes such as scheduling policy, priority, stacksize, and the like. The function returns a nonzero value on failure. Let’s take our MP3 player in player.c . The player start-up calls system_init function for various subsystem initializations. system_init function runs in the context of the main thread. int system_init(){ pthread_t audio_tid; int sample = 1; void * audio_init_status; /* Initialize audio subsystem in a separate thread */ if (pthread_create(&audio_tid, NULL, audio_init, (void *)sample) != 0){ printf("Audio thread creation failed.\n"); return FAIL; } /* * Initialize rest of application, data structures etc */ } system_init calls pthread_create to perform audio subsystem initial- ization in a new thread. On success, the thread id of the created thread is stored in audio_tid . The new thread executes the audio_init function. audio_init takes an integer argument sample. As the second argument to pthread_create is NULL , the audio_tid thread starts with a default set of attributes. (For example, scheduling policy and priority of the thread is inherited from the caller.) The new thread initializes the decoder and audio output subsystem. If requested it also plays a sample sound for two seconds to verify if the initialization is successful. Porting Applications 173 void* audio_init(void *sample){ int init_status = SUCCESS; printf("Audio init thread created with ID %d\n", pthread_self()); /* * Initialize MP3 decoder subsystem. * set init_status = FAIL for failure. */ /* * Initialize Audio output subsystem. * set init_status = FAIL for failure. */ if ((int)sample){ /* * Play sample output for 2 seconds. * Set init_status = FAIL if play fails */ } printf("Audio subsystem initialized\n"); pthread_exit((void *)init_status); } Two questions arise. Ⅲ How can the audio_init thread send its exit status to the main thread? Ⅲ Is it possible for the system_init function to wait for termination of the audio_init thread before quitting? How can it fetch the exit status of audio_init thread? A thread sets its exit status using the pthread_exit function. This function also terminates execution of the calling thread. void pthread_exit(void *return_val); audio_init calls pthread_exit to terminate its execution and also to set its exit status. pthread_exit is analogous to the exit system call. From the application developer’s point of view there is only one difference: exit terminates the complete process and pthread_exit terminates the calling thread only. A thread can get the exit status of another thread by calling the pthread_join function. int pthread_join(pthread_t tid, void **thread_return_val); 174 Embedded Linux System Design and Development pthread_join suspends execution of the calling thread until thread tid exits. When pthread_join returns, the exit status of thread tid is stored in the thread_return_val argument. pthread_join is analogous to the wait4 system call. wait4 suspends the execution of a parent process until the child specified in its argument terminates. Similarly pthread_join also suspends the execution of the calling thread until the thread specified in its argument exits. As you can see, system_init calls pthread_join to wait for the audio_init thread to exit before returning. It also prints an error message if audio_init fails. int system_init(){ . void * audio_init_status; . . /* Wait for audio_init thread to complete */ pthread_join(audio_tid, &audio_init_status); /* If audio init failed then return error */ if ((int)audio_init_status == FAIL){ printf("Audio init failed.\n"); return FAIL; } return SUCCESS; } Note that a thread created using pthread_create with a default set of attributes (the second argument to pthread_create is NULL ) is a joinable thread. Resources allocated to a joinable thread are not released until some other thread calls pthread_join on the thread. It becomes a zombie. 6.3.2 Thread Synchronization Pthreads provides thread synchronization in the form of mutex and condition variables. A mutex is a binary semaphore that provides exclusive access to a shared data structure. It supports two basic operations: lock and unlock. A thread should lock the mutex before entering the critical section and unlock it when it is done. A thread blocks if it tries to lock an already locked mutex. It is awakened when the mutex is unlocked. Mutex lock operation is atomic. If two threads try to acquire the mutex at the same time, it’s assured that one operation will complete or block before the other starts. A nonblocking version of the lock operation, trylock, is also supported. Trylock returns success if the mutex is acquired; it returns failure if the mutex is already locked. A general sequence to protect a shared data structure using mutex is lock the mutex operate on shared data unlock the mutex [...]... System Design and Development system calls The protected kernel address space significantly increases the application porting effort from a traditional RTOS that has a flat memory model to embedded Linux Let’s take an example to understand the difficulty a developer faces when porting applications from an RTOS to Linux A target running an RTOS has an RTC A function rtc_set is available to set the RTC... when porting the above application to Linux The function rtc_set directly modifies RTC registers so it should go in the kernel On the other hand, the function rtc_get_from_user reads the user input so it should go in the user space In Linux, the function rtc_ get_from_user cannot call the function rtc_set as the latter is a kernel function The following solutions are available when porting such applications. .. for porting such applications to Linux We call it the kernel API driver (kapi) In this approach, a user-space stub is written for every kernel function that should be exported to user space The stub when called traps into the kernel API driver that then calls the actual function in the kernel The kernel API driver (or kapi driver) is implemented as a character driver /dev/kapi It provides an ioctl Porting. .. the cancellation request can ignore it, honor it immediately, or defer the request Two functions are provided that determine the action taken whenever a cancellation request is received by a thread Porting Applications 181 int pthread_setcancelstate(int state, int *oldstate); int pthread_setcanceltype(int type, int *oldtype); pthread_setcancelstate is called to ignore or accept the cancellation request... Unlock mutex that was acquired previously by calling rtosMutexLock Ⅲ rtosError_t rtosMutexTrylock(rtosMutex_t *mutex): Acquire mutex if it is unlocked Return RTOS_AGAIN if the mutex is already locked Porting Applications 183 Note that all the above APIs return one of the values in enum rtosError_t in file rtosTypes.h This RTOS file is included in ospl.h typedef enum { RTOS_OK, RTOS_AGAIN, RTOS_UNSUPPORTED,... rtosError_t rtosMutexTrylock(rtosMutex_t *mutex){ int err; #ifndef KERNEL err = pthread_mutex_trylock(mutex); if (err == 0) return RTOS_OK; if (errno == EBUSY) return RTOS_AGAIN; return RTOS_ERROR; Porting Applications Table 6.1 185 RTOS and Linux Mutex APIs Linux RTOS User Space Kernel Space Mutex init pthread_mutex_init init_MUTEX Mutex lock pthread_mutex_lock down, down_interruptible Mutex unlock... uarg.priority = priority; uarg.arg1 = arg1; uarg.arg2 = arg2; uarg.arg3 = arg3; err = pthread_create (&tHandle->thread_id, NULL, wrapper_routine, (void *)&uarg); return (err) ? RTOS_ERROR : RTOS_OK; #else } Porting Applications 187 We define a new structure uarg_t that holds all the RTOS task entry routine arguments, the pointer to the entry routine, and the priority of the task typedef struct _uarg_t { rtosEntry_t... routine, void * arg, int priority, int stackSize, rtosTask_t *tHandle){ #ifndef KERNEL #else struct completion *complete_ptr = (struct completion *)kmalloc(sizeof(structcompletion), GFP_KERNEL); Porting Applications 189 karg_t *karg = (karg_t *)kmalloc(sizeof(karg_t), GFP_KERNEL); strcpy(tHandle->name, name); init_completion(complete_ptr); < Initialize a completion variable tHandle->exit = complete_ptr;... (current)) { flush_signals(current); break; } } /* signal and complete */ complete_and_exit (exit, 0); } To summarize, mappings between RTOS mutex APIs and Linux mutex APIs are listed in Table 6.2 Porting Applications Table 6.2 191 RTOS and Linux Task APIs Linux RTOS User Space Kernel Space Task create pthread_create kernel_thread Task delete pthread_cancel kill_proc Table 6.3 RTOS, Linux Timers, and... functions can be implemented using POSIX.1b real-time extensions We discuss more about POSIX.1b support in Linux in Chapter 7 6.5 Kernel API Driver One of the major challenges a developer faces when porting applications to embedded Linux is the kernel-space/user-space mode of programming in Linux In Linux, because of the protected kernel address space, an application cannot directly call any kernel function . 165 Chapter 6 Porting Applications A developer faces a challenging task when porting applications from a tradi- tional RTOS such. End of Physical Memory Porting Applications 167 6.2 Application Porting Roadmap In this section we discuss a generic application porting roadmap from an

Ngày đăng: 06/10/2013, 23:20

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN