From: Octavian Purdila <tavi@xxxxxxxxx> Add the documentation and templates for the kernel modules lab which focuses on: familiarizing with the basic Linux kernel API, describing memory allocation and locking mechanism. Signed-off-by: Octavian Purdila <tavi@xxxxxxxxx> Signed-off-by: Daniel Baluta <daniel.baluta@xxxxxxxxx> --- Documentation/labs/index.rst | 1 + Documentation/labs/kernel_api.rst | 788 +++++++++++++++++++++ tools/labs/templates/kernel_api/1-mem/Kbuild | 3 + tools/labs/templates/kernel_api/1-mem/mem.c | 46 ++ .../labs/templates/kernel_api/2-sched-spin/Kbuild | 3 + .../templates/kernel_api/2-sched-spin/sched-spin.c | 40 ++ tools/labs/templates/kernel_api/3-memory/Kbuild | 3 + tools/labs/templates/kernel_api/3-memory/memory.c | 71 ++ tools/labs/templates/kernel_api/4-list/Kbuild | 3 + tools/labs/templates/kernel_api/4-list/list.c | 100 +++ tools/labs/templates/kernel_api/5-list-full/Kbuild | 3 + .../templates/kernel_api/5-list-full/list-full.c | 145 ++++ tools/labs/templates/kernel_api/6-list-sync/Kbuild | 3 + .../templates/kernel_api/6-list-sync/list-sync.c | 173 +++++ tools/labs/templates/kernel_api/7-list-test/Kbuild | 3 + .../templates/kernel_api/7-list-test/list-test.c | 32 + 16 files changed, 1417 insertions(+) create mode 100644 Documentation/labs/kernel_api.rst create mode 100644 tools/labs/templates/kernel_api/1-mem/Kbuild create mode 100644 tools/labs/templates/kernel_api/1-mem/mem.c create mode 100644 tools/labs/templates/kernel_api/2-sched-spin/Kbuild create mode 100644 tools/labs/templates/kernel_api/2-sched-spin/sched-spin.c create mode 100644 tools/labs/templates/kernel_api/3-memory/Kbuild create mode 100644 tools/labs/templates/kernel_api/3-memory/memory.c create mode 100644 tools/labs/templates/kernel_api/4-list/Kbuild create mode 100644 tools/labs/templates/kernel_api/4-list/list.c create mode 100644 tools/labs/templates/kernel_api/5-list-full/Kbuild create mode 100644 tools/labs/templates/kernel_api/5-list-full/list-full.c create mode 100644 tools/labs/templates/kernel_api/6-list-sync/Kbuild create mode 100644 tools/labs/templates/kernel_api/6-list-sync/list-sync.c create mode 100644 tools/labs/templates/kernel_api/7-list-test/Kbuild create mode 100644 tools/labs/templates/kernel_api/7-list-test/list-test.c diff --git a/Documentation/labs/index.rst b/Documentation/labs/index.rst index 9af739f..222827a 100644 --- a/Documentation/labs/index.rst +++ b/Documentation/labs/index.rst @@ -32,3 +32,4 @@ then point your browser at **Documentation/output/labs/index.html**. vm.rst exercises.rst kernel_modules.rst + kernel_api.rst diff --git a/Documentation/labs/kernel_api.rst b/Documentation/labs/kernel_api.rst new file mode 100644 index 0000000..f2daaa9 --- /dev/null +++ b/Documentation/labs/kernel_api.rst @@ -0,0 +1,788 @@ +========== +Kernel API +========== + +Lab objectives +============== + + * Familiarize yourself with the basic Linux kernel API + * Description of memory allocation mechanisms + * Description of locking mechanisms + +Overview +======== + +Inside the current lab we present a set of concepts and basic functions required +for starting Linux kernel programming. It is important to note that kernel +programming differs greatly from user space programming. The kernel is a +stand-alone entity that can not use libraries in user-space (not even libc). +As a result, the usual user-space functions (printf, malloc, free, open, read, +write, memcpy, strcpy, etc.) can no longer be used. In conclusion, kernel +programming is based on a totally new and independent API that is unrelated to +the user-space API, whether we refer to POSIX or ANSI C (standard C language +library functions). + +Accessing memory +================ + +An important difference in kernel programming is how to access and allocate +memory. Due to the fact that kernel programming is very close to the physical +machine, there are important rules for memory management. First, it works with +several types of memory: + + * Physical memory + * Virtual memory from the kernel address space + * Virtual memory from a process's address space + * Resident memory - we know for sure that the accessed pages are present in + physical memory + +Virtual memory in a process's address space can not be considered resident due +to the virtual memory mechanisms implemented by the operating system: pages may +be swapped or simply may not be present in physical memory as a result of the +demand paging mechanism. The memory in the kernel address space can be resident +or not. Both the data and code segments of a module and the kernel stack of a +process are resident. Dynamic memory may or may not be a resident, depending +on how it is allocated. + +When working with resident memory, things are simple: memory can be accessed at +any time. But if working with non-resident memory, then it can only be accessed +from certain contexts. Non-resident memory can only be accessed from the +process context. Accessing non-resident memory from the context of the +interruption has unpredictable results and, therefore, when the operating +system detects such access, it will take drastic measures: blocking or +resetting the system to prevent serious corruption. + +The virtual memory of a process can not be accessed directly from the kernel. +In general, it is totally discouraged to access the address space of a process, +but there are situations where a device driver needs to do it. The typical case +is where the device driver needs to access a buffer from the user-space. In +this case, the device driver must use special features and not directly access +the buffer. This is necessary to prevent access to invalid memory areas. + +Another difference from the userpace scheduling, relative to memory, is due to +the stack, a stack whose size is fixed and limited. In the Linux 2.6.x kernel, +a stack of 4K , and a stack of 12K is used in Windows. For this reason, the +allocation of large-scale stack structures or the use of recursive calls should +be avoided. + +Contexts of execution +===================== + +In relation to kernel execution, we distinguish two contexts: process context +and interrupt context. We are in the process context when we run code as a +result of a system call or when we run in the context of a thread kernel. When +we run in a routine to handle an interrupt or a deferrable action, we run in +an interrupt context. + +Some of the kernel API calls can block the current process. Common examples are +using a semaphore or waiting for a condition. In this case, the process is +put into the WAITING state and another process is running. An interesting +situation occurs when a function that can lead to suspension of the current +process is called from an interrupt context. In this case, there is no current +process, and therefore the results are unpredictable. Whenever the operating +system detects this condition will generate an error condition that will cause +the operating system to shut down. + +Locking +======= + +One of the most important features of kernel programming is parallelism. Linux +support SMP systems with multiple processors and kernel preemptivity. This makes +kernel programming more difficult because access to global variables must be +synchronized with either spinlock primitives or blocking primitives. Although +it is recommended to use blocking primitives, they can not be used in an interrupt +context, so the only locking solution in the context of the interrupt is spinlocks. + +Spinlocks are used to achieve mutual exclusion. When it can not get access to +the critical region, it does not suspend the current process, but it use the +busy-waiting mechanism (waiting in a loop while releasing the lock). The code +that runs in the critical region protected by a spinlock is not allowed to +suspend the current process (it must adhere to the execution conditions in the +context of an interrupt). Moreover, the CPU will not be released except for +interrupts. Due to the mechanism used, it is important that a spinlock be +detained as little time as possible. + +Preemptivity +============ + +Linux uses a preemptive kernels. The notion of preemptive multitasking should not +be confused with the notion of preemptive kernel. The notion of preemptive multitasking +refers to the fact that the operating system interrupts a process by force when +it expires its quantum of time and runs in user-space to run another process. +A kernel is preemptive if a process running in kernel mode (as a result of a system call) +can be interrupted to run another process. + +Because of preemptivity, when we share resources between two portions of code +that can run from different process contexts, we need to protect ourselves with +synchronization primitives, even with the single processor. + +Linux Kernel API +================ + +Convention indicating errors +---------------------------- + +For Linux kernel programming, the convention used to call functions to indicate +success is the same as UNIX programming: 0 for success, or a value other than 0 +for failure. For failures negative values are returned as shown in the example below: + +.. code-block:: c + + if (alloc_memory() != 0) + return -ENOMEM; + + if (user_parameter_valid() != 0) + return -EINVAL; + +The exhaustive list of errors and a summary explanation can be found in +``include/asm-generic/errno-base.h`` and ``includes/asm-generic/ernno.h``. + +Strings of characters +--------------------- + +In Linux, the kernel programmer is provided with the usual routine functions: +``strcpy``, ``strncpy``, ``strlcpy``, ``strcat``, ``strncat``, ``strlcat``, +``strcmp``, ``strncmp``, ``strnicmp``, ``strnchr``, ``strrchr``, ``strrchr``, +``strstr``, ``strlen``, ``memset``, ``memmove``, ``memcmp``, etc. These functions +are declared in the ``include/linux/string.h`` header and are implemented in the +kernel in the ``lib/string.c`` file. + +printk +------ + +The printf equivalent in the kernel is printk , defined in +``include/linux/printk.h``. The printk syntax is very similar to printf. The first +parameter of printk decides the message category in which the current message falls: + +.. code-block:: c + + #define KERN_EMERG "<0>" /* system is unusable */ + #define KERN_ALERT "<1>" /* action must be taken immediately */ + #define KERN_CRIT "<2>" /* critical conditions */ + #define KERN_ERR "<3>" /* error conditions */ + #define KERN_WARNING "<4>" /* warning conditions */ + #define KERN_NOTICE "<5>" /* normal but significant condition */ + #define KERN_INFO "<6>" /* informational */ + #define KERN_DEBUG "<7>" /* debug-level messages */ + +Thus, a warning message in the kernel would be sent with: + +.. code-block:: c + + printk(KERN_WARNING "my_module input string %s\n", buff); + + +If the logging level is missing from the printk call, logging is done with the +default level at the time of the call. One thing to keep in mind is that +messages sent with printk are only visible on the console and only if their +level exceeds the default level set on the console. + +To reduce the size of lines when using printk, it is recommended to use the +following help functions instead of directly using the printk call: + +.. code-block:: c + + pr_emerg(fmt, ...); /* similar with printk(KERN_EMERG pr_fmt(fmt), ...); */ + pr_alert(fmt, ...); /* similar with printk(KERN_ALERT pr_fmt(fmt), ...); */ + pr_crit(fmt, ...); /* similar with printk(KERN_CRIT pr_fmt(fmt), ...); */ + pr_err(fmt, ...); /* similar with printk(KERN_ERR pr_fmt(fmt), ...); */ + pr_warning(fmt, ...); /* similar with printk(KERN_WARNING pr_fmt(fmt), ...); */ + pr_warn(fmt, ...); /* similar with cu printk(KERN_WARNING pr_fmt(fmt), ...); */ + pr_notice(fmt, ...); /* similar with printk(KERN_NOTICE pr_fmt(fmt), ...); */ + pr_info(fmt, ...); /* similar with printk(KERN_INFO pr_fmt(fmt), ...); */ + +A special case is pr_debug that calls the printk function only when the DEBUG +macro is defined or if dynamic debugging is used. + + +Memory allocation +----------------- + +In Linux only resident memory can be allocated, using kmalloc call. A typical kmalloc +call is presented below: + +.. code-block:: c + + #include <linux/slab.h> + + string = kmalloc (string_len + 1, GFP_KERNEL); + if (!string) { + //report error: -ENOMEM; + } + +As you can see, the first parameter indicates the size in bytes of the allocated +area. The function returns a pointer to a memory area that can be directly used +in the kernel, or NULL if memory could not be allocated. The second parameter +specifies how allocation should be done and the most commonly used values are: + + * ``GFP_KERNEL`` - using this value may cause the current process to be + suspended. Thus, can not be used in the interrupt context. + * ``GFP_ATOMIC`` - when using this value it ensures that the kmalloc function + does not suspend the current process. Can be used anytime. + +Complement to the kmalloc function is ``kfree``, a function that receives as +argument an area allocated by kmalloc. This feature does not suspend the current +process and can therefore be called from any context. + +lists +----- + +Because linked lists are often used, the Linux kernel API provides a unified +way of defining and using lists. This involves using a list_head structure +element in the structure we want to consider as a list node. The list_head +list_head is defined in ``include/linux/list.h`` along with all the other +functions that work on the lists. The following code shows the definition of +the list_head list_head and the use of an element of this type in another +well-known structure in the Linux kernel: + +.. code-block:: c + + struct list_head { + struct list_head *next, *prev; + }; + + struct task_struct { + ... + struct list_head children; + ... + }; + +The usual routines for working with lists are as follows: + + * ``LIST_HEAD(name)`` is used to declare the sentinel of a list + * ``INIT_LIST_HEAD(struct list_head *list)`` is used to initialize the sentinel + of a list when dynamic allocation is made by setting the value of the next and + prev to list fields. + * ``list_add(struct list_head *new, struct list_head *head)`` adds the new + element after the head element. + * ``list_del(struct list_head *entry)`` deletes the item at the entry address of + the list it belongs to. + * ``list_entry(ptr, type, member)`` returns the type structure that contains the + element ptr the member with the member name within the structure. + * ``list_for_each(pos, head)`` iterates a list using pos as a cursor. + * ``list_for_each_safe(pos, n, head)`` iterates a list, using pos as a cursor and + and ``n`` as a temporary cursor. This macro is used to delete an item from the list. + +The following code shows how to use these routines: + +.. code-block:: c + + #include <linux/slab.h> + #include <linux/list.h> + + struct pid_list { + pid_t pid; + struct list_head list; + }; + + LIST_HEAD(my_list); + + static int add_pid(pid_t pid) + { + struct pid_list *ple = kmalloc(sizeof *ple, GFP_KERNEL); + + if (!ple) + return -ENOMEM; + + ple->pid = pid; + list_add(&ple->list, &my_list); + + return 0; + } + + static int del_pid(pid_t pid) + { + struct list_head *i, *tmp; + struct pid_list *ple; + + list_for_each_safe(i, tmp, &my_list) { + ple = list_entry(i, struct pid_list, list); + if (ple->pid == pid) { + list_del(i); + kfree(ple); + return 0; + } + } + + return -EINVAL; + } + + static void destroy_list(void) + { + struct list_head *i, *n; + struct pid_list *ple; + + list_for_each_safe(i, n, &my_list) { + ple = list_entry(i, struct pid_list, list); + list_del(i); + kfree(ple); + } + } + +The evolution of the list can be seen in the following figure: + +You see the stack type behavior introduced by the list_add macro, and the use +of a sentinel. + +From the above example, it is noted that the way to define and use a list +(double-linked) is generic and, at the same time, does not introduce an +additional overhead. The list_head list_head is used to maintain the links +between the list elements. It is also noted that list iteration is also done +with this structure, and the list item is list_entry using list_entry . This +idea of implementing and using a list is not new, as The Art of Computer +Programming in The Art of Computer Programming by Donald Knuth in the 1980s. + +Several kernel list functions and macrodefinitions are presented and explained +in the include/linux/list.h header. + +Spinlock +-------- + +spinlock_t (defined in ``linux/spinlock.h``) is the basic type that implements +the spinlock concept in Linux. It describes a spinlock, and the operations +associated with a spinlock are spin_lock_init, spin_lock, spin_unlock . An +example of use is given below: + +.. code-block:: c + + #include <linux/spinlock.h> + + DEFINE_SPINLOCK(lock1); + spinlock_t lock2; + + spin_lock_init(&lock2); + + spin_lock(&lock1); + /* critical region */ + spin_unlock(&lock1); + + spin_lock(&lock2); + /* critical region */ + spin_unlock(&lock2); + + +In Linux, you can use read / write spinlocks useful for writer-reader issues. +These types of locks are identified by ``rwlock_t``, and the functions that can +work on a read / write spinlock are ``rwlock_init``, ``read_lock``, ``write_lock``. +An example of use: + + +.. code-block:: c + + #include <linux/spinlock.h> + + DEFINE_RWLOCK(lock); + + struct pid_list { + pid_t pid; + struct list_head list; + }; + + int have_pid(struct list_head *lh, int pid) + { + struct list_head *i; + void *elem; + + read_lock(&lock); + list_for_each(i, lh) { + struct pid_list *pl = list_entry(i, struct pid_list, list); + if (pl->pid == pid) { + read_unlock(&lock); + return 1; + } + } + read_unlock(&lock); + + return 0; + } + + void add_pid(struct list_head *lh, struct pid_list *pl) + { + write_lock(&lock); + list_add(&pl->list, lh); + write_unlock(&lock); + } + +mutex +----- + +A mutex is a variable of the ``struct mutex`` type (defined in linux/mutex.h ). +Functions and macros for working with mutex are listed below: + +.. code-block:: c + + #include <linux/mutex.h> + + /* functions for mutex initialization */ + void mutex_init(struct mutex *mutex); + DEFINE_MUTEX(name); + + /* functions for mutex acquire */ + void mutex_lock(struct mutex *mutex); + + /* functions for mutex release */ + void mutex_unlock(struct mutex *mutex); + +Operations are similar to classic mutex operations in userspace or spinlock +operations: the mutex is acquired before entering the critical area and +releases to the critical area. Unlike spin-locks, these operations can only be +used in process context. + +.. _atomic-variables: + +Atomic variables +---------------- + +Often, you only need to synchronize access to a simple variable, such as a +counter. For this, an ``atomic_t`` can be used (defined in include/linux/atomic.h +) that holds an integer value. Below are some operations that can be performed on +an atomic_t variable. + +.. code-block:: c + + #include <asm/atomic.h> + + void atomic_set(atomic_t *v, int i); + int atomic_read(atomic_t *v); + void atomic_add(int i, atomic_t *v); + void atomic_sub(int i, atomic_t *v); + void atomic_inc(atomic_t *v); + void atomic_dec(atomic_t *v); + int atomic_inc_and_test(atomic_t *v); + int atomic_dec_and_test(atomic_t *v); + int atomic_cmpxchg(atomic_t *v, int old, int new); + +Use of atomic variables +*********************** + +A common way of using atomic variables is to maintain the status of an action +(eg a flag). So we can use an atomic variable to mark exclusive actions. For +example, we consider that an atomic variable can have the LOCKED and UNLOCKED +values, and if LOCKED then a specific function -EBUSY with an -EBUSY message. +The mode of use is shown schematically in the code below: + +.. code-block:: c + + #define LOCKED 0 + #define UNLOCKED 1 + + static atomic_t flag; + + static int my_acquire(void) + { + int initial_flag; + + /* + * Check if flag is UNLOCKED; if not, lock it and do it atomically. + * + * This is the atomic equivalent of + * if (flag == UNLOCKED) + * flag = LOCKED; + * else + * return -EBUSY; + */ + initial_flag = atomic_cmpxchg(&flag, UNLOCKED, LOCKED); + if (initial_flag == LOCKED) { + printk(KERN_ALERT "Already locked.\n"); + return -EBUSY; + } + + /* Do your thing after getting the lock. */ + [...] + } + + static void my_release(void) + { + /* Release flag; mark it as unlocked. */ + atomic_set(&flag, UNLOCKED); + } + + void my_init(void) + { + [...] + /* Atomic variable is initially unlocked. */ + atomic_set(&flag, UNLOCKED); + + [...] + } + + +The above code is the equivalent of using a trylock (such as pthread_mutex_trylock). + +We can also use a variable to remember the size of a buffer and for atomic +updates. For example, the code below: + +.. code-block:: c + + static unsigned char buffer[MAX_SIZE]; + static atomic_t size; + + static void add_to_buffer(unsigned char value) + { + buffer[atomic_read(&size)] = value; + atomic_inc(&size); + } + + static unsigned char remove_from_buffer(void) + { + unsigned char value; + + value = buffer[atomic_read(&size)]; + atomic_dec(&size); + + return value + } + + static void reset_buffer(void) + { + atomic_set(&size, 0); + } + + void my_init(void) + { + [...] + /* Initilized buffer and size. */ + atomic_set(&size, 0); + memset(buffer, 0, sizeof(buffer)); + + [...] + } + +Atomic bitwise operations +------------------------- + +The kernel provides a set of functions (in ``asm/bitops.h``) that modify or test +bits in an atomic way. + +.. code-block:: c + + #include <asm/bitops.h> + + void set_bit(int nr, void *addr); + void clear_bit(int nr, void *addr); + void change_bit(int nr, void *addr); + int test_and_set_bit(int nr, void *addr); + int test_and_clear_bit(int nr, void *addr); + int test_and_change_bit(int nr, void *addr); + +Addr represents the address of the memory area whose bits are being modified or +tested and the nr is the bit on which the operation is performed. + +Exercises +========= + +.. important:: + + .. include:: exercises-summary.hrst + .. |LAB_NAME| replace:: kernel_api + +0. Intro +-------- + +Using |LXR|_ find the definitions of the following symbols in the Linux kernel: + + * :c:type:`struct list_head` + * :c:type:`INIT_LIST_HEAD` + * :c:func:`list_add` + * :c:func:`list_for_each` + * :c:func:`list_entry` + * :c:func:`container_of` + * :c:func:`offsetof` + +1. Memory allocation in Linux kernel +------------------------------------ + +Generate the skeleton for the task named **1-mem** and browse the +contents of the ``mem.c`` file. Observe the use of kmalloc call for +memory allocation. + + 1. Compile the source code and load the ``mem.ko`` module using ``insmod``. + 2. View the kernel messages using the ``dmesg`` command. + 3. Unload the kernel module using the ``rmmod mem`` command. + +.. note:: Review the `Memory Allocation`_ section in the lab. + +2. Sleeping in atomic context +----------------------------- + +Generate the skeleton for the task named **2-sched-spin** and browse +the contents of ``sched-spin.c`` file. + + 1. Compile the source code and load the module. + 2. Notice that it is waiting for 5 seconds until the insertion + order is complete. + 3. Unload the kernel mode. + 4. Look for the lines marked with TODO0 to create an atomic + section. Re-compile the source code and reload the module into + the kernel. + +You should now get an error. Look at the stack trace. What is the +cause of the error? + +.. hint:: In the error message, follow the line containing the BUG for + a description of the error. You are not allowed to sleep in + atomic context. The atomic context is given by a section + between a lock operation and an unlock on a spinlock. + +.. note:: The schedule_timeout function, corroborated with the + set_current_state macro, forces the current process to wait + S seconds. + +.. note:: Review the `Contexts of execution`_, `Locking` and `Spinlock`_ sections. + +3. Working with kernel memory +----------------------------- + +Generate the skeleton for the task named **3-memory** directory and +browse the contents of the ``memory.c`` file. Notice the comments +marked with TODO. You must allocate 4 structures of type ``struct +task_info`` and initialize them (in ``memory_init``), then print and +free them (in ``memory_exit``). + + 1. (TODO 1) Allocate memory for task_info structure and initialize + its fields: + + * The pid field to the PID transmitted as a parameter; + * The timestamp field to the value of the jiffies variable, + which hold the number of ticks that have occurred since the + system booted. + + 2. (TODO 2) Allocate task_info for current process, parent process, + next process, the next process of the next process. + + 3. (TODO 3) Display the four structures. + + * Use pr_info to display their two fields: pid and timestamp. + + 4. (TODO 4) Release the space occupied by structures (use kfree). + +.. hint:: + * You can access the current process using ``current`` + macro. + * Look for the relevant fields in the task_struct structure + (pid, parent). + * Use the next_task macro. The macro returns the pointer to + the next process of ``struct task_struct *`` type. + +.. note:: The task_struct struct contains two fields to designate the + parent of a task: + + * ``real_parent`` points to the process that created the + task or to process with pid 1 (init) if the parent + completed its execution. + * ``parent`` indicates to the current task parent (the + process that will be reported if the task completes + execution). + + In general, the values of the two fields are the same, but + there are situations where they differ, for example when + using the ptrace system call. + +.. hint:: Review the `Memory allocation`_ section in the lab. + + +4. Working with kernel lists +---------------------------- + +Generate the skeleton for the task named **4-list**. Browse the +contents of the ``list.c`` file and notice the comments marked with +TODO. The current process will add the four structures from the +previous exercise into a list. The list will be built in the +``task_info_add_for_current`` function which is called when module is +loaded. The list will printed and deleted in the ``list_exit`` +function and the ``task_info_purge_list`` function. + + 1. (TODO 1) Complete the task_info_add_to_list function to allocate + a ``task_info`` struct and add it to the list. + + 2. (TODO 2) Complete the task_info_purge_list function to delete + all the elements in the list. + + 3. Compile the kernel module. Load and unload the module by + following the messages displayed by the kernel. + +.. hint:: Review the labs `Lists`_ section. When deleting items from + the list, you will need to use the + :c:func:`list_for_each_safe` or + :c:func:`list_for_each_entry_safe` calls. + +5. Working with kernel lists for process handling +------------------------------------------------- + +Generate the skeleton for the task named **5-list-full**. Browse the +contents of the ``list-full.c`` and notice comments marked with +TODO. In addition to the ``4-list`` functionality we add the +following: + + * A count field showing how many times a process has been "added" + to the list. + * If a process is "added" several times, no new entry is created in + the list, but: + + * Update the timestamp field. + * Increment count. + + * To implement the counter facility, add a ``task_info_find_pid`` + function that searches for a pid in the existing list. + + * If found, return the reference to the task_info struct. If + not, return NULL + + * An expiration facility. If a process was added more than 3 + seconds ago and if it does not have a count greater than 5 then + it is considered expired and is removed from the list. + * The expiration facility is already implemented in the + ``task_info_remove_expired`` function. + + 1. (TODO 1) Implement the task_info_find_pid function. + 3. (TODO 2) Change a field of an item in the list so it does not + expire. + +.. hint:: For TODO 2, extract the first element from the list (the one + referred by head.next) and set the count field to a large + enough value. Use ``atomic_set``. + +6. Synchronizing list work +-------------------------- + +Generate the skeleton for the task named **6-list-sync**. + + 1. Browse the code and look for ``TODO`` string. + 2. Use a spinlock or a read-write lock to synchronize access to the + list. + 3. Compile, load and unload the kernel module. + +.. important:: Always lock data, not code! + +.. note:: Read `Spinlock`_ section of the lab. + +7. Test module calling in our list module +----------------------------------------- + +Generate the skeleton for the task named **7-list-test** and browse +the contents of the ``list-test.c`` file. We'll use it as a test +module. It will call functions exported by the **6-list-sync** +task. The exported functions are the ones marked with **extern** in +``list-test.c`` file. + +To export the above functions from the 6-list-sync/ module, the +following steps are required: + + 1. Functions must not be static. + 2. Use the EXPORT_SYMBOL macro to export the kernel symbols. For + example: ``EXPORT_SYMBOL(task_info_remove_expired)``; . The + macro must be used for each function after the function is + defined. + 3. Remove from the 6-list-sync/ that avoid the expiration of a + list item. + 4. Compile and load the module from 6-list-sync/ . Once loaded, it + exposes exported functions and can be used by the test + module. You can check this by searching for the function names + in /proc/kallsyms before and after loading the module. + 5. Compile the test module and then load it. + 6. Use lsmod to check that the two modules have loaded. What do + you notice? + 7. Unload the kernel test module. + +What should be the unload order of the two modules (6-list-sync and +test)? What if you use another order? diff --git a/tools/labs/templates/kernel_api/1-mem/Kbuild b/tools/labs/templates/kernel_api/1-mem/Kbuild new file mode 100644 index 0000000..85f6de9 --- /dev/null +++ b/tools/labs/templates/kernel_api/1-mem/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = mem.o diff --git a/tools/labs/templates/kernel_api/1-mem/mem.c b/tools/labs/templates/kernel_api/1-mem/mem.c new file mode 100644 index 0000000..a18029a --- /dev/null +++ b/tools/labs/templates/kernel_api/1-mem/mem.c @@ -0,0 +1,46 @@ +/* + * Kernel API lab + * + * mem.c - Memory allocation in Linux + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/ctype.h> + +MODULE_DESCRIPTION("Print memory"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +static char *mem; + +static int mem_init(void) +{ + size_t i; + + mem = kmalloc(4096 * sizeof(*mem), GFP_KERNEL); + if (mem == NULL) + goto err_mem; + + pr_info("chars: "); + for (i = 0; i < 4096; i++) { + if (isalpha(mem[i])) + printk("%c ", mem[i]); + } + pr_info("\n"); + + return 0; + +err_mem: + return -1; +} + +static void mem_exit(void) +{ + kfree(mem); +} + +module_init(mem_init); +module_exit(mem_exit); diff --git a/tools/labs/templates/kernel_api/2-sched-spin/Kbuild b/tools/labs/templates/kernel_api/2-sched-spin/Kbuild new file mode 100644 index 0000000..4401382 --- /dev/null +++ b/tools/labs/templates/kernel_api/2-sched-spin/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = sched-spin.o diff --git a/tools/labs/templates/kernel_api/2-sched-spin/sched-spin.c b/tools/labs/templates/kernel_api/2-sched-spin/sched-spin.c new file mode 100644 index 0000000..52fc3d2 --- /dev/null +++ b/tools/labs/templates/kernel_api/2-sched-spin/sched-spin.c @@ -0,0 +1,40 @@ +/* + * Kernel API lab + * + * sched-spin.c: Sleeping in atomic context + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> + +MODULE_DESCRIPTION("Sleep while atomic"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +static int sched_spin_init(void) +{ + spinlock_t lock; + + spin_lock_init(&lock); + + /* TODO 0/1: Use spin_lock to aquire the lock */ + spin_lock(&lock); + + set_current_state(TASK_INTERRUPTIBLE); + /* Try to sleep for 5 seconds. */ + schedule_timeout(5 * HZ); + + /* TODO 0/1: Use spin_unlock to release the lock */ + spin_unlock(&lock); + + return 0; +} + +static void sched_spin_exit(void) +{ +} + +module_init(sched_spin_init); +module_exit(sched_spin_exit); diff --git a/tools/labs/templates/kernel_api/3-memory/Kbuild b/tools/labs/templates/kernel_api/3-memory/Kbuild new file mode 100644 index 0000000..a29f796 --- /dev/null +++ b/tools/labs/templates/kernel_api/3-memory/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = memory.o diff --git a/tools/labs/templates/kernel_api/3-memory/memory.c b/tools/labs/templates/kernel_api/3-memory/memory.c new file mode 100644 index 0000000..1d85b71 --- /dev/null +++ b/tools/labs/templates/kernel_api/3-memory/memory.c @@ -0,0 +1,71 @@ +/* + * SO2 lab3 - task 3 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/sched/signal.h> + +MODULE_DESCRIPTION("Memory processing"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +struct task_info { + pid_t pid; + unsigned long timestamp; +}; + +static struct task_info *ti1, *ti2, *ti3, *ti4; + +static struct task_info *task_info_alloc(int pid) +{ + struct task_info *ti; + + /* TODO 1/5: allocated and initialize a task_info struct */ + ti = kmalloc(sizeof(*ti), GFP_KERNEL); + if (ti == NULL) + return NULL; + ti->pid = pid; + ti->timestamp = jiffies; + + return ti; +} + +static int memory_init(void) +{ + /* TODO 2/1: call task_info_alloc for current pid */ + ti1 = task_info_alloc(current->pid); + + /* TODO 2/1: call task_info_alloc for parent PID */ + ti2 = task_info_alloc(current->parent->pid); + + /* TODO 2/1: call task_info alloc for next process PID */ + ti3 = task_info_alloc(next_task(current)->pid); + + /* TODO 2/1: call text_info_alloc for next process of the nex process */ + ti4 = task_info_alloc(next_task(next_task(current))->pid); + + return 0; +} + +static void memory_exit(void) +{ + + /* TODO 3/4: print ti* field values */ + printk("pid: %d, timestamp: %lu\n", ti1->pid, ti1->timestamp); + printk("pid: %d, timestamp: %lu\n", ti2->pid, ti2->timestamp); + printk("pid: %d, timestamp: %lu\n", ti3->pid, ti3->timestamp); + printk("pid: %d, timestamp: %lu\n", ti4->pid, ti4->timestamp); + + /* TODO 4/4: free ti* structures */ + kfree(ti1); + kfree(ti2); + kfree(ti3); + kfree(ti4); +} + +module_init(memory_init); +module_exit(memory_exit); diff --git a/tools/labs/templates/kernel_api/4-list/Kbuild b/tools/labs/templates/kernel_api/4-list/Kbuild new file mode 100644 index 0000000..7187139 --- /dev/null +++ b/tools/labs/templates/kernel_api/4-list/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = list.o diff --git a/tools/labs/templates/kernel_api/4-list/list.c b/tools/labs/templates/kernel_api/4-list/list.c new file mode 100644 index 0000000..4745b75 --- /dev/null +++ b/tools/labs/templates/kernel_api/4-list/list.c @@ -0,0 +1,100 @@ +/* + * Kernel API lab + * + * list.c: Working with lists + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/sched/signal.h> + +MODULE_DESCRIPTION("Use list to process task info"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +struct task_info { + pid_t pid; + unsigned long timestamp; + struct list_head list; +}; + +static struct list_head head; + +static struct task_info *task_info_alloc(int pid) +{ + struct task_info *ti; + + ti = kmalloc(sizeof(*ti), GFP_KERNEL); + if (ti == NULL) + return NULL; + ti->pid = pid; + ti->timestamp = jiffies; + + return ti; +} + +static void task_info_add_to_list(int pid) +{ + struct task_info *ti; + + /* TODO 1/2: Allocate task_info and add it to list */ + ti = task_info_alloc(pid); + list_add(&ti->list, &head); +} + +static void task_info_add_for_current(void) +{ + /* Add current, parent, next and next of next to the list */ + task_info_add_to_list(current->pid); + task_info_add_to_list(current->parent->pid); + task_info_add_to_list(next_task(current)->pid); + task_info_add_to_list(next_task(next_task(current))->pid); +} + +static void task_info_print_list(const char *msg) +{ + struct list_head *p; + struct task_info *ti; + + pr_info("%s: [ ", msg); + list_for_each(p, &head) { + ti = list_entry(p, struct task_info, list); + pr_info("(%d, %lu) ", ti->pid, ti->timestamp); + } + pr_info("]\n"); +} + +static void task_info_purge_list(void) +{ + struct list_head *p, *q; + struct task_info *ti; + + /* TODO 2/5: Iterate over the list and delete all elements */ + list_for_each_safe(p, q, &head) { + ti = list_entry(p, struct task_info, list); + list_del(p); + kfree(ti); + } +} + +static int list_init(void) +{ + INIT_LIST_HEAD(&head); + + task_info_add_for_current(); + + return 0; +} + +static void list_exit(void) +{ + task_info_print_list("before exiting"); + task_info_purge_list(); +} + +module_init(list_init); +module_exit(list_exit); diff --git a/tools/labs/templates/kernel_api/5-list-full/Kbuild b/tools/labs/templates/kernel_api/5-list-full/Kbuild new file mode 100644 index 0000000..45358ad --- /dev/null +++ b/tools/labs/templates/kernel_api/5-list-full/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = list-full.o diff --git a/tools/labs/templates/kernel_api/5-list-full/list-full.c b/tools/labs/templates/kernel_api/5-list-full/list-full.c new file mode 100644 index 0000000..1184fcb --- /dev/null +++ b/tools/labs/templates/kernel_api/5-list-full/list-full.c @@ -0,0 +1,145 @@ +/* + * Kernel API lab + * + * list-full.c: Working with lists (advanced) + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/sched/signal.h> + +MODULE_DESCRIPTION("Full list processing"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +struct task_info { + pid_t pid; + unsigned long timestamp; + atomic_t count; + struct list_head list; +}; + +static struct list_head head; + +static struct task_info *task_info_alloc(int pid) +{ + struct task_info *ti; + + ti = kmalloc(sizeof(*ti), GFP_KERNEL); + if (ti == NULL) + return NULL; + ti->pid = pid; + ti->timestamp = jiffies; + atomic_set(&ti->count, 0); + + return ti; +} + +static struct task_info *task_info_find_pid(int pid) +{ + struct list_head *p; + struct task_info *ti; + + /* TODO 1/5: Look for pid and return task_info or NULL if not found */ + list_for_each(p, &head) { + ti = list_entry(p, struct task_info, list); + if (ti->pid == pid) + return ti; + } + + return NULL; +} + +static void task_info_add_to_list(int pid) +{ + struct task_info *ti; + + ti = task_info_find_pid(pid); + if (ti != NULL) { + ti->timestamp = jiffies; + atomic_inc(&ti->count); + return; + } + + ti = task_info_alloc(pid); + list_add(&ti->list, &head); +} + +static void task_info_add_for_current(void) +{ + task_info_add_to_list(current->pid); + task_info_add_to_list(current->parent->pid); + task_info_add_to_list(next_task(current)->pid); + task_info_add_to_list(next_task(next_task(current))->pid); +} + +static void task_info_print_list(const char *msg) +{ + struct list_head *p; + struct task_info *ti; + + pr_info("%s: [ ", msg); + list_for_each(p, &head) { + ti = list_entry(p, struct task_info, list); + pr_info("(%d, %lu) ", ti->pid, ti->timestamp); + } + pr_info("]\n"); +} + +static void task_info_remove_expired(void) +{ + struct list_head *p, *q; + struct task_info *ti; + + list_for_each_safe(p, q, &head) { + ti = list_entry(p, struct task_info, list); + if (jiffies - ti->timestamp > 3 * HZ && atomic_read(&ti->count) < 5) { + list_del(p); + kfree(ti); + } + } +} + +static void task_info_purge_list(void) +{ + struct list_head *p, *q; + struct task_info *ti; + + list_for_each_safe(p, q, &head) { + ti = list_entry(p, struct task_info, list); + list_del(p); + kfree(ti); + } +} + +static int list_full_init(void) +{ + INIT_LIST_HEAD(&head); + + task_info_add_for_current(); + task_info_print_list("after first add"); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(5 * HZ); + + return 0; +} + +static void list_full_exit(void) +{ + struct task_info *ti; + + /* TODO 2/2: Ensure that at least one task is not deleted */ + ti = list_entry(head.prev, struct task_info, list); + atomic_set(&ti->count, 10); + + task_info_remove_expired(); + task_info_print_list("after removing expired"); + task_info_purge_list(); +} + +module_init(list_full_init); +module_exit(list_full_exit); diff --git a/tools/labs/templates/kernel_api/6-list-sync/Kbuild b/tools/labs/templates/kernel_api/6-list-sync/Kbuild new file mode 100644 index 0000000..8105af7 --- /dev/null +++ b/tools/labs/templates/kernel_api/6-list-sync/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = list-sync.o diff --git a/tools/labs/templates/kernel_api/6-list-sync/list-sync.c b/tools/labs/templates/kernel_api/6-list-sync/list-sync.c new file mode 100644 index 0000000..5d06e76 --- /dev/null +++ b/tools/labs/templates/kernel_api/6-list-sync/list-sync.c @@ -0,0 +1,173 @@ +/* + * Linux API lab + * + * list-sync.c - Synchronize access to a list + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/sched/signal.h> + +MODULE_DESCRIPTION("Full list processing with synchronization"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +struct task_info { + pid_t pid; + unsigned long timestamp; + atomic_t count; + struct list_head list; +}; + +static struct list_head head; + +/* TODO: you can use either a spinlock or rwlock, define it here */ +DEFINE_RWLOCK(lock); + +static struct task_info *task_info_alloc(int pid) +{ + struct task_info *ti; + + ti = kmalloc(sizeof(*ti), GFP_KERNEL); + if (ti == NULL) + return NULL; + ti->pid = pid; + ti->timestamp = jiffies; + atomic_set(&ti->count, 0); + + return ti; +} + +static struct task_info *task_info_find_pid(int pid) +{ + struct list_head *p; + struct task_info *ti; + + /* TODO: Protect list, is this read or write access? */ + read_lock(&lock); + list_for_each(p, &head) { + ti = list_entry(p, struct task_info, list); + if (ti->pid == pid) { + /* TODO: Guess why this comment was added here */ + read_unlock(&lock); + return ti; + } + } + /* TODO: critical section ends here */ + read_unlock(&lock); + + return NULL; +} + +static void task_info_add_to_list(int pid) +{ + struct task_info *ti; + + ti = task_info_find_pid(pid); + if (ti != NULL) { + ti->timestamp = jiffies; + atomic_inc(&ti->count); + return; + } + + ti = task_info_alloc(pid); + /* TODO: protect list access, is this read or write access? */ + write_lock(&lock); + list_add(&ti->list, &head); + /* TODO: critical section ends here */ + write_unlock(&lock); +} + +void task_info_add_for_current(void) +{ + task_info_add_to_list(current->pid); + task_info_add_to_list(current->parent->pid); + task_info_add_to_list(next_task(current)->pid); + task_info_add_to_list(next_task(next_task(current))->pid); +} +EXPORT_SYMBOL(task_info_add_for_current); + +void task_info_print_list(const char *msg) +{ + struct list_head *p; + struct task_info *ti; + + pr_info("%s: [ ", msg); + + /* TODO: Protect list, is this read or write access? */ + read_lock(&lock); + list_for_each(p, &head) { + ti = list_entry(p, struct task_info, list); + pr_info("(%d, %lu) ", ti->pid, ti->timestamp); + } + /* TODO: Critical section ends here */ + read_unlock(&lock); + pr_info("]\n"); +} +EXPORT_SYMBOL(task_info_print_list); + +void task_info_remove_expired(void) +{ + struct list_head *p, *q; + struct task_info *ti; + + /* TODO: Protect list, is this read or write access? */ + write_lock(&lock); + list_for_each_safe(p, q, &head) { + ti = list_entry(p, struct task_info, list); + if (jiffies - ti->timestamp > 3 * HZ && atomic_read(&ti->count) < 5) { + list_del(p); + kfree(ti); + } + } + /* TODO: Critical section ends here */ + write_unlock(&lock); +} +EXPORT_SYMBOL(task_info_remove_expired); + +static void task_info_purge_list(void) +{ + struct list_head *p, *q; + struct task_info *ti; + + /* TODO: Protect list, is this read or write access? */ + write_lock(&lock); + list_for_each_safe(p, q, &head) { + ti = list_entry(p, struct task_info, list); + list_del(p); + kfree(ti); + } + /* TODO: Critical sections ends here */ + write_unlock(&lock); +} + +static int list_sync_init(void) +{ + INIT_LIST_HEAD(&head); + + task_info_add_for_current(); + task_info_print_list("after first add"); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(5 * HZ); + + return 0; +} + +static void list_sync_exit(void) +{ + struct task_info *ti; + + ti = list_entry(head.prev, struct task_info, list); + atomic_set(&ti->count, 10); + + task_info_remove_expired(); + task_info_print_list("after removing expired"); + task_info_purge_list(); +} + +module_init(list_sync_init); +module_exit(list_sync_exit); diff --git a/tools/labs/templates/kernel_api/7-list-test/Kbuild b/tools/labs/templates/kernel_api/7-list-test/Kbuild new file mode 100644 index 0000000..324750e --- /dev/null +++ b/tools/labs/templates/kernel_api/7-list-test/Kbuild @@ -0,0 +1,3 @@ +ccflags-y = -Wno-unused-function -Wno-unused-label -Wno-unused-variable + +obj-m = list-test.o diff --git a/tools/labs/templates/kernel_api/7-list-test/list-test.c b/tools/labs/templates/kernel_api/7-list-test/list-test.c new file mode 100644 index 0000000..e21c559 --- /dev/null +++ b/tools/labs/templates/kernel_api/7-list-test/list-test.c @@ -0,0 +1,32 @@ +/* + * SO2 lab3 - task 7 + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> + +MODULE_DESCRIPTION("Test list processing"); +MODULE_AUTHOR("SO2"); +MODULE_LICENSE("GPL"); + +extern void task_info_add_for_current(void); +extern void task_info_remove_expired(void); +extern void task_info_print_list(const char *msg); + +static int list_test_init(void) +{ + task_info_add_for_current(); + task_info_print_list("after new addition"); + + return 0; +} + +static void list_test_exit(void) +{ + task_info_remove_expired(); + task_info_print_list("after removing expired"); +} + +module_init(list_test_init); +module_exit(list_test_exit); -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html