Kthread and Signal
Topics in this section,
# |
Version |
---|---|
Ubuntu |
Ubuntu 22.10 |
Kernel |
6.7.9 |
In this program, you are going to learn
How to send signal?
How to send signal from one thread to another thread using signal APIs?
How to use Kthread APIs ?
How to use Waitqueue APIs ?
How to use Signal APIs ?
How to use below APIs ?
Here is the function prototype of the API: kthread_should_stop
#include <linux/kthread.h>
bool kthread_should_stop(void);
where
kthread_should_stop: this is used to determine whether the thread should return now. Whenever the kthread_stop() is called it will be woken and returns true.
return type: returns true when the thread is stopped, false when the thread is still in execution
Here is an example of how to use the API,
while (!kthread_should_stop())
{
//add the instructions to be performed during thread execution.
}
Here is the function prototype of the API: kthread_run
#include <linux/kthread.h>
#define kthread_run(threadfn, data, namefmt, ...)
where
kthread_run : is used to create and wake a kthread
return type: struct task_struct* (i.e) address of the kthread
threadfn: function to executed by kthread
data: data pointer for threadfn which can be used to send any possible arguments required for threadfn.
namefmt: printf-style format for the thread name which will be displayed on ps output when the thread is in execution.
Here is an example of how to use the API,
kthread_run(mythread,NULL,"sample kthread");
Here is the function prototype of the API: kthread_stop
#include <linux/kthread.h>
int kthread_stop(struct task_struct *k);
where
kthread_stop: stops a kthread.
return type: returns the return value of threadfn() which is passed as an argument during kthread creation.
k: kthread created by one of the API used to created kthread.
Here is the example of how to use the API,
kthread_stop(mythread);
Here is the prototype of the API: DECLARE_WAIT_QUEUE_HEAD
# include <linux/wait.h>
#define DECLARE_WAIT_QUEUE_HEAD(name) \
struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
where
DECLARE_WAIT_QUEUE_HEAD : To declare and initialize wait queue head.
name : name which is given to the wait queue head on declaration.
Here is the example of the API,
DECLARE_WAIT_QUEUE_HEAD(mywaitqueue);
Here is the prototype of the API: wait_event_interruptible
# include <linux/wait.h>
#define wait_event_interruptible(wq_head, condition)
where
wait_event_interruptible : sleep until a condition gets true
wq_head: the waitqueue to wait on
condition: a C expression for the event to wait for
Here is an example of the API
wait_event_interruptible(mywq_head,i < 10);
Here is the prototype of the API: allow_signal
# include <linux/signal.h>
static inline void allow_signal(int sig);
where
allow_signal : used to know that the signal code will be handled, so that they don’t get converted to SIGKILL or just silently dropped.
sig: signal code which will be handled.
Here is an example of how to use the API,
allow_signal(SIGKILL);
Here is the prototype of the API: signal_pending
#include <linux/sched/signal.h>
static inline int signal_pending(struct task_struct *p);
where
signal_pending : used to check for any pending signals queued for thread.
p: thread which needs to be checked for any pending signals being queued or not.
Here is an example of how to use the API,
signal_pending(current);
Here is the prototype of the API: sigaddset
#include <linux/signal.h>
static inline void sigaddset(sigset_t *set, int _sig);
where
sigaddset : set the corresponding signal code bit in sigset_t.
set: the set for which the bit needs to be set.
_sig: signal code whose bit needs to be set in sigset_t.
Here is an example of how to use the API
sigaddset(current->signal->shared_pending.signal,SIGKILL);
Here is the prototype of the API: send_sig
#include <linux/signal.h>
int send_sig(int sig, struct task_struct *p, int priv);
where
send_sig : send signal to a thread
sig: signal code which needs to be sent
p: receiver thread which receives the signal sent.
Here is an example of how to use the API
send_sig(SIGKILL,receiver_thread,0);
Here is the prototype of the API: flush_signals
#include <linux/sched/signal.h>
void flush_signals(struct task_struct *t);
where
flush_signals : Flush all pending signals for this kthread.
t: thread whose signals needs to be flushed.
Here is an example of the API
flush_signals(current);
Here is the prototype of module paramter APIs
#include <linux/module.h>
#define MODULE_LICENSE(_license) MODULE_FILE MODULE_INFO(license, _license)
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
#define MODULE_DESCRIPTION(_description) MODULE_INFO(description, _description)
where
MODULE_LICENSE: tells the kernel what license is used by our module.
MODULE_AUTHOR: denotes the author of this kernel module.
MODULE_DESCRIPTION: gives a basic idea about what the kernel module does.
These information can be found when modinfo command is used which lists out all these above mentioned information.
Here is the example of how to use the Module parameter APIs,
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Usr");
MODULE_DESCRIPTION("Sample kernel module");
Here is the prototype of the API: IS_ERR
#include <linux/err.h>
static inline bool __must_check IS_ERR(__force const void *ptr);
where
IS_ERR: Detects an error pointer
return type: return true if it’s an error pointer else return false.
ptr: pointer which needs to detected.
Here is an example of how to use the API,
if(IS_ERR(sample_ptr)) {
//instructions to be executed when error ptr is detected
} else {
//instructions to be executed when error ptr is not detected
}
Here is the example of the API: wake_up_process
#include <linux/sched.h>
extern int wake_up_process(struct task_struct *tsk);
where
wake_up_process: wake up a specific process
return type: returns 1 if the process is woken up, 0 if the process is in running state.
tsk: process to be woken up.
Here is the example of how to use the API,
wake_up_process(mythread);
Here is the prototype of the Driver entry point API’s
#include <linux/module.h>
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);
where
module_init: driver initialization entry point which will be called at module insertion time.
module_exit: driver exit entry point which will be called during the removal of module.
- x:
function to be run at module insertion for module_init function.
function to be run when driver is removed for module_exit function.
Here is an example of how to use the driver entry point API’s
module_init(myinitmodule);
module_exit(myexitmodule);
In this example let’s see how to send signals between threads and perform certain operation based on the signal received.
Include the follow header files(.h) to refer the API being used for the execution.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/signal.h>
#include <linux/sched/signal.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/random.h>
Add the following module macros to display information about the license, author and description about the module.
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux Usr");
MODULE_DESCRIPTION("sending signals between threads");
Declare the thread and waitqueue variables which we are going to create and use in this example
#define MAX_SIGNAL_NOS 31
static int signal_list[MAX_SIGNAL_NOS];
DECLARE_WAIT_QUEUE_HEAD(recv_wait_queue);
static struct task_struct *sender_thread;
static struct task_struct *receiver_thread;
Add the module init function which will be executed once we load the kernel module using insmod command.
static int __init kthread_signal_init(void)
{
pr_info("Inside thread init function\n");
thread_start();
return 0;
}
Add the thread start function which is called from module init function, creates the thread and starts it’s execution.
void thread_start(void)
{
int i;
pr_info("inside kthread init function\n");
for(i = 0; i < MAX_SIGNAL_NOS; i++)
signal_list[i] = i+1;
receiver_thread = kthread_run(recv_thread_fn,NULL,"kthread_signal receiver_thread example");
if (IS_ERR(receiver_thread))
pr_info("error in creating receiver thread\n");
else
pr_info("successfully created receiver thread\n");
sender_thread = kthread_run(sender_thread_fn,NULL,"kthread_signal sender_thread example");
if (IS_ERR(sender_thread))
pr_info("error in creating sender thread\n");
else
pr_info("successfully created sender thread\n");
}
Add the module exit function which will be executed once we unload the kernel module using rmmod command.
static void __exit kthread_signal_exit(void)
{
pr_info("Inside kthread exit function\n");
thread_stop();
}
Add the thread stop function which is called from module exit function, destroys the thread created and stops it’s execution.
void thread_stop(void)
{
if(sender_thread)
kthread_stop(sender_thread);
if(receiver_thread)
kthread_stop(receiver_thread);
pr_info("destroyed threads\n");
}
Add the thread function API which will be called as soon as the kthread is created and is in running state.
static int recv_thread_fn (void *data)
{
long unsigned int *signum;
int j;
for (j = 0;j<MAX_SIGNAL_NOS;j++)
allow_signal(signal_list[j]);
while(!kthread_should_stop()) {
wait_event_interruptible(recv_wait_queue,(signal_pending(current) || kthread_should_stop()));
signum = current->signal->shared_pending.signal.sig;
for (int i = 0; i <= MAX_SIGNAL_NOS; i++) {
if ((*signum & 1 << i) >> i == 1) {
handle_signal(i+1);
break;
}
}
flush_signals(current);
msleep(1000);
}
return 0;
}
static int sender_thread_fn (void *data)
{
int random_num;
pr_info("sending signal from sender thread -> receiver thread\n");
while (!kthread_should_stop())
{
random_num = get_random_u32() % 32;
sigaddset(&receiver_thread->signal->shared_pending.signal,signal_list[random_num]);
send_sig(signal_list[random_num],receiver_thread,0);
msleep(1000);
}
return 0;
}
This API gives the set of instructions on how to handle each signal.
static void handle_signal(int signum)
{
switch(signum) {
case SIGHUP:
pr_info("executing sighup instructions\n");
break;
case SIGINT:
pr_info("executing sigint instructions\n");
break;
case SIGQUIT:
pr_info("executing sigquit instructions\n");
break;
case SIGILL:
pr_info("executing sigill instructions\n");
break;
case SIGTRAP:
pr_info("executing sigtrap instructions\n");
break;
case SIGABRT:
pr_info("executing sigabrt instructions\n");
break;
case SIGBUS:
pr_info("executing sigbus instructions\n");
break;
case SIGFPE:
pr_info("executing sigfpe instructions\n");
break;
case SIGKILL:
pr_info("executing sigkill instructions\n");
break;
case SIGUSR1:
pr_info("executing sigusr1 instructions\n");
break;
case SIGSEGV:
pr_info("executing sigsegv instructions\n");
break;
case SIGUSR2:
pr_info("executing sigusr2 instructions\n");
break;
case SIGPIPE:
pr_info("executing sigpipe instructions\n");
break;
case SIGALRM:
pr_info("executing sigalrm instructions\n");
break;
case SIGTERM:
pr_info("executing sigterm instructions\n");
break;
case SIGSTKFLT:
pr_info("executing sigstkflt instructions\n");
break;
case SIGCHLD:
pr_info("executing sigchld instructions\n");
break;
case SIGCONT:
pr_info("executing sigcont instructions\n");
break;
case SIGSTOP:
pr_info("executing sigstop instructions\n");
break;
case SIGTSTP:
pr_info("executing sigtstp instructions\n");
break;
case SIGTTIN:
pr_info("executing sigttfin instructions\n");
break;
case SIGTTOU:
pr_info("executing sigttou instructions\n");
break;
case SIGURG:
pr_info("executing sigurg instructions\n");
break;
case SIGXCPU:
pr_info("executing sigxcpu instructions\n");
break;
case SIGXFSZ:
pr_info("executing sigxfsz instructions\n");
break;
case SIGVTALRM:
pr_info("executing sigvtalrm instructions\n");
break;
case SIGPROF:
pr_info("executing sigprof instructions\n");
break;
case SIGWINCH:
pr_info("executing sigwinch instructions\n");
break;
case SIGPOLL:
pr_info("executing sigpoll instructions\n");
break;
case SIGPWR:
pr_info("executing sigpwr instructions\n");
break;
case SIGSYS:
pr_info("executing sigsys instructions\n");
break;
default:
break;
}
}
Add the driver entry points which will be executed once the module is inserted or removed from the kernel.
module_init(kthread_signal_init);
module_exit(kthread_signal_exit);
1#include <linux/module.h>
2#include <linux/kernel.h>
3#include <linux/init.h>
4#include <linux/kthread.h>
5#include <linux/signal.h>
6#include <linux/sched/signal.h>
7#include <linux/wait.h>
8#include <linux/delay.h>
9#include <linux/random.h>
10
11#define MAX_SIGNAL_NOS 31
12
13MODULE_LICENSE("GPL");
14MODULE_AUTHOR("linux usr");
15MODULE_DESCRIPTION("handling multiple switch cases using switch case");
16
17static int signal_list[MAX_SIGNAL_NOS];
18DECLARE_WAIT_QUEUE_HEAD(recv_wait_queue);
19static struct task_struct *sender_thread;
20static struct task_struct *receiver_thread;
21
22/* handle_signal - used to execute different instructions for different signals,
23 * executes when a signal is sent to receiver thread by sending thread */
24
25static void handle_signal(int signum)
26{
27 switch(signum) {
28 case SIGHUP:
29 pr_info("executing sighup instructions\n");
30 break;
31 case SIGINT:
32 pr_info("executing sigint instructions\n");
33 break;
34 case SIGQUIT:
35 pr_info("executing sigquit instructions\n");
36 break;
37 case SIGILL:
38 pr_info("executing sigill instructions\n");
39 break;
40 case SIGTRAP:
41 pr_info("executing sigtrap instructions\n");
42 break;
43 case SIGABRT:
44 pr_info("executing sigabrt instructions\n");
45 break;
46 case SIGBUS:
47 pr_info("executing sigbus instructions\n");
48 break;
49 case SIGFPE:
50 pr_info("executing sigfpe instructions\n");
51 break;
52 case SIGKILL:
53 pr_info("executing sigkill instructions\n");
54 break;
55 case SIGUSR1:
56 pr_info("executing sigusr1 instructions\n");
57 break;
58 case SIGSEGV:
59 pr_info("executing sigsegv instructions\n");
60 break;
61 case SIGUSR2:
62 pr_info("executing sigusr2 instructions\n");
63 break;
64 case SIGPIPE:
65 pr_info("executing sigpipe instructions\n");
66 break;
67 case SIGALRM:
68 pr_info("executing sigalrm instructions\n");
69 break;
70 case SIGTERM:
71 pr_info("executing sigterm instructions\n");
72 break;
73 case SIGSTKFLT:
74 pr_info("executing sigstkflt instructions\n");
75 break;
76 case SIGCHLD:
77 pr_info("executing sigchld instructions\n");
78 break;
79 case SIGCONT:
80 pr_info("executing sigcont instructions\n");
81 break;
82 case SIGSTOP:
83 pr_info("executing sigstop instructions\n");
84 break;
85 case SIGTSTP:
86 pr_info("executing sigtstp instructions\n");
87 break;
88 case SIGTTIN:
89 pr_info("executing sigttfin instructions\n");
90 break;
91 case SIGTTOU:
92 pr_info("executing sigttou instructions\n");
93 break;
94 case SIGURG:
95 pr_info("executing sigurg instructions\n");
96 break;
97 case SIGXCPU:
98 pr_info("executing sigxcpu instructions\n");
99 break;
100 case SIGXFSZ:
101 pr_info("executing sigxfsz instructions\n");
102 break;
103 case SIGVTALRM:
104 pr_info("executing sigvtalrm instructions\n");
105 break;
106 case SIGPROF:
107 pr_info("executing sigprof instructions\n");
108 break;
109 case SIGWINCH:
110 pr_info("executing sigwinch instructions\n");
111 break;
112 case SIGPOLL:
113 pr_info("executing sigpoll instructions\n");
114 break;
115 case SIGPWR:
116 pr_info("executing sigpwr instructions\n");
117 break;
118 case SIGSYS:
119 pr_info("executing sigsys instructions\n");
120 break;
121 default:
122 break;
123 }
124}
125
126/* recv_thread_fn - executes when receiver thread is created,
127 * allows all the signal using allow_signal,
128 * waits for an event where the condition is set as checking for any pending signal,
129 * based on the signal code respective instructions will be executing in handle_signal,
130 * flush all the pending signal using flush_signals,
131 * stops when kthread_stop is called */
132
133static int recv_thread_fn (void *data)
134{
135 long unsigned int *signum;
136 int j;
137 for (j = 0;j<MAX_SIGNAL_NOS;j++)
138 allow_signal(signal_list[j]);
139 while(!kthread_should_stop()) {
140 wait_event_interruptible(recv_wait_queue,(signal_pending(current) || kthread_should_stop()));
141 signum = current->signal->shared_pending.signal.sig;
142 for (int i = 0; i <= MAX_SIGNAL_NOS; i++) {
143 if ((*signum & 1 << i) >> i == 1) {
144 handle_signal(i+1);
145 break;
146 }
147 }
148 flush_signals(current);
149 msleep(1000);
150 }
151 return 0;
152}
153
154/* sender_thread_fn - executes when sender thread is created,
155 * generates a random signal code,
156 * sets the corresponding signal code bit,
157 * sends signal to receiving thread,
158 * stops when kthread_stop is called */
159
160static int sender_thread_fn (void *data)
161{
162 int random_num;
163 pr_info("sending signal from sender thread -> receiver thread\n");
164 while (!kthread_should_stop())
165 {
166 random_num = get_random_u32() % 32;
167 sigaddset(&receiver_thread->signal->shared_pending.signal,signal_list[random_num]);
168 send_sig(signal_list[random_num],receiver_thread,0);
169 msleep(1000);
170 }
171 return 0;
172}
173
174/* thread_start - creates thread and starts execution */
175
176void thread_start(void)
177{
178 int i;
179 pr_info("inside kthread init function\n");
180 for(i = 0; i < MAX_SIGNAL_NOS; i++)
181 signal_list[i] = i+1;
182 receiver_thread = kthread_run(recv_thread_fn,NULL,"kthread_signal receiver_thread example");
183 if (IS_ERR(receiver_thread))
184 pr_info("error in creating receiver thread\n");
185 else
186 pr_info("successfully created receiver thread\n");
187 sender_thread = kthread_run(sender_thread_fn,NULL,"kthread_signal sender_thread example");
188 if (IS_ERR(sender_thread))
189 pr_info("error in creating sender thread\n");
190 else
191 pr_info("successfully created sender thread\n");
192}
193
194/* kthread_signal_init - calls thread_start,
195 * executes when the module is loaded */
196
197static int __init kthread_signal_init(void)
198{
199 pr_info("inside kthread init function\n");
200 thread_start();
201 return 0;
202}
203
204/* thread_stop - destroys thread and stops execution */
205
206void thread_stop(void)
207{
208 if(sender_thread)
209 kthread_stop(sender_thread);
210 if(receiver_thread)
211 kthread_stop(receiver_thread);
212 pr_info("destroyed threads\n");
213}
214
215/* kthread_signal_exit - calls thread_stop,
216 * executes when the module is unloaded */
217
218static void __exit kthread_signal_exit(void)
219{
220 pr_info("inside kthread exit function\n");
221 thread_stop();
222}
223
224module_init(kthread_signal_init);
225module_exit(kthread_signal_exit);
1obj-m += kthread.o
2
3all:
4 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
5
6clean:
7 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
8
Run make to compile the kernel source and generate the .ko image.
make -C /lib/modules/6.7.9/build M=$HOME/kthread_examples/
make[1]: Entering directory '/usr/src/linux-headers-6.7.9'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc (Ubuntu 12.2.0-3ubuntu1) 12.2.0
You are using: gcc (Ubuntu 12.2.0-3ubuntu1) 12.2.0
CC [M] $HOME/kthread_examples/kthread.o
MODPOST $HOME/kthread_examples/Module.symvers
CC [M] $HOME/kthread_examples/kthread.mod.o
LD [M] $HOME/kthread_examples/kthread.ko
BTF [M] $HOME/kthread_examples/kthread.ko
Skipping BTF generation for $HOME/kthread_examples/kthread.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-6.7.9'
Check if the .ko is generated or not using ls command.
test@test-V520-15IKL:~$ ls -l
total 360
-rw-rw-r-- 1 test test 713 Mar 18 16:06 kthread.c
-rw-rw-r-- 1 test test 169784 Mar 18 16:08 kthread.ko
-rw-rw-r-- 1 test test 58 Mar 18 16:08 kthread.mod
-rw-rw-r-- 1 test test 1047 Mar 18 16:08 kthread.mod.c
-rw-rw-r-- 1 test test 96512 Mar 18 16:08 kthread.mod.o
-rw-rw-r-- 1 test test 74696 Mar 18 16:08 kthread.o
-rw-rw-r-- 1 test test 161 Mar 18 16:00 Makefile
-rw-rw-r-- 1 test test 58 Mar 18 16:08 modules.order
-rw-rw-r-- 1 test test 0 Mar 18 16:08 Module.symvers
Run modinfo command to get the information about the kernel module.
test@test-V520-15IKL:~/.../tc_1$ modinfo kthread.ko
filename: $HOME/kthread_examples/kthread.ko
description: sending signals between threads
author: Linux Usr
license: GPL
srcversion: 8D2147F67AB01CF0E482DAC
depends:
retpoline: Y
name: kthread
vermagic: 6.7.9 SMP preempt mod_unload modversions
insert the module using insmod command.
test@test-V520-15IKL:~$ sudo insmod ./kthread.ko
check if the module is loaded or not using lsmod command.
test@test-V520-15IKL:~$ sudo lsmod | grep kthread
kthread 16384 0
check if the thread is created or not using ps command.
test@test-V520-15IKL:~$ ps -N | grep kthread_signal
14226 ? 00:00:00 kthread_signal receiver_thread example
14227 ? 00:00:00 kthread_signal sender_thread example
check for the kernel messages from init function and thread function once the module is loaded and thread is created.
test@test-V520-15IKL:~$ sudo dmesg
[ 2535.581826] inside kthread init function
[ 2535.581830] inside kthread init function
[ 2535.581866] successfully created receiver thread
[ 2535.581883] successfully created sender thread
[ 2535.581885] sending signal from sender thread -> receiver thread
[ 2535.581892] executing sigcont instructions
[ 2536.604333] executing sigpipe instructions
[ 2537.628340] executing sigurg instructions
[ 2538.652358] executing sigcont instructions
[ 2539.676403] executing sigint instructions
[ 2540.700430] executing sigttfin instructions
remove the module from kernel using rmmod command.
test@test-V520-15IKL:~$ sudo rmmod kthread
check if the module is still loaded after removing the kernel module using lsmod if it is not displayed in lsmod output it is verified that the module is removed successfully.
test@test-V520-15IKL:~$ sudo lsmod | grep kthread
test@test-V520-15IKL:~$
check if the thread is destroyed using ps command if it is not displayed in ps output we can confirm that the thread is destroyed successfully.
test@test-V520-15IKL:~$ ps -N | grep kthread_signal
test@test-V520-15IKL:~$
Check for kernel messages from exit function using dmesg command.
test@test-V520-15IKL:~$ sudo dmesg
[ 2553.059738] inside kthread exit function
[ 2555.036884] destroyed threads
kthread API |
Learning |
---|---|
kthread_run |
Create and wake a thread |
kthread_should_stop |
To determine when thread should exit |
kthread_stop |
Stop a thread created by kthread_create |
Workqueue API |
Learning |
---|---|
DECLARE_WAIT_QUEUE_HEAD |
Declare waitqueue head |
wait_event_interruptible |
sleeps until the condition is true |
Signal API |
Learning |
---|---|
allow_signal |
used to allow signal so that it can be handled |
signal_pending |
check for pending signals |
sigaddset |
set the corresponding signal code bit |
send_sig |
sending signal to thread |
flush_signals |
flush all pending signals |
API |
Learning |
---|---|
MODULE_LICENSE |
Used to denote the license used in the kernel module |
MODULE_AUTHOR |
Used to mention the author of the kernel module |
MODULE_DESCRIPTION |
Used to describe what the module does |
IS_ERR |
Detects an error pointer |
wake_up_process |
wake up a specific process |
msleep |
will put in sleep for a certain amount of msecs time. |
module_init |
Driver initialization entry point |
module_exit |
Driver exit entry point |
pr_info |
Print an info-level message |