System Call: This is the most common method. A user-space program requests the kernel to perform a specific action through the system call interface (e.g., open, read, write, fork). System calls serve as the bridge between user-space and kernel-space, allowing user programs to request kernel services.
Interrupts: Interrupts include soft interrupts and hard interrupts. When an interrupt arrives, the CPU suspends the currently executing user code and switches to kernel-space to handle the interrupt. The interrupt mechanism allows the kernel to intervene in the execution of user-space programs at appropriate times, handling urgent or special situations.
Signal: The kernel notifies a user-space process of certain events via signals. User-space programs can register signal handlers to respond to specific signal events. For example, SIGTERM and SIGINT are signals used to terminate or interrupt a process.
Shared Memory: Allows multiple processes to share a memory region in their address spaces, enabling efficient communication between user-space and kernel-space. This method avoids frequent user-kernel switches but requires handling data synchronization and consistency issues.
IOCTL: This is an older interaction method between user-space and kernel-space. User programs call the ioctl function with a command, the kernel dispatches it to the corresponding driver for processing, and finally returns the result to user-space.
Netlink: Essentially a special socket used for message passing between the kernel and multiple user processes. Netlink supports bidirectional communication between kernel and user-space, making it a full-duplex communication method.
procfs/sysfs: In Linux, procfs and sysfs are special filesystems for information exchange between the kernel and user-space. procfs provides various information about the kernel and processes, while sysfs provides information about devices and drivers. User-space programs can obtain kernel information by reading files in these filesystems, or configure the kernel/driver by writing to specific files.
Each communication method has its specific application scenarios, advantages, and disadvantages. Developers need to choose the appropriate method based on specific requirements.
Implementing communication between user-space and kernel-space typically involves writing a kernel module (running in kernel-space) and a user-space program (running in user-space). I will provide simple examples demonstrating how to write a kernel module that offers a system call interface, and a user-space program that invokes that system call.
System Call
First, we need to write a kernel module that registers a new system call. This system call will simply return the parameter passed to it.
// syscall_example.c - Kernel module source code
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
// Define a new syscall number (normally auto-assigned, but manually defined here for simplicity)
#define __NR_my_syscall 333
// Define the syscall prototype
SYSCALL_DEFINE1(my_syscall, int, arg)
{
printk(KERN_INFO "my_syscall called with arg %d\n", arg);
return arg;
}
// Module initialization
static int __init syscall_example_init(void)
{
// Register the syscall
if (syscall_register(__NR_my_syscall, (sys_call_ptr_t)my_syscall) < 0) {
printk(KERN_ALERT "Failed to register syscall!\n");
return -1;
}
printk(KERN_INFO "syscall_example module loaded\n");
return 0;
}
// Module cleanup
static void __exit syscall_example_exit(void)
{
// Unregister syscall (normally not necessary as kernel cleans up on module unload)
syscall_unregister( );
printk(KERN_INFO "syscall_example module unloaded\n");
}
module_init(syscall_example_init);
module_exit(syscall_example_exit);
MODULE_LICENSE("GPL");
To compile this kernel module, you need a Makefile:
# Makefile - For compiling kernel module
obj-m += syscall_example.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
After compiling and loading this module, the new system call can be invoked using the number __NR_my_syscall.
Next, we write a user-space program to call this new system call.
// user_program.c - User-space program source code
#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h>
// Define the new syscall number; must match the value in the kernel module
#define __NR_my_syscall 333
int main() {
int arg = 42; // Parameter to pass to the syscall
long result;
// Use syscall() to invoke the system call
result = syscall(__NR_my_syscall, arg);
if (result >= 0) {
printf("my_syscall returned: %ld\n", result);
} else {
perror("my_syscall failed");
}
return 0;
}
Compile and run the user-space program:
gcc -o user_program user_program.c
sudo insmod syscall_example.ko # Load kernel module
./user_program # Run user-space program
sudo rmmod syscall_example # Unload kernel module (when no longer needed)
Note that loading and unloading kernel modules usually requires root privileges. Moreover, developing kernel modules and system calls involves deep understanding of the kernel and potential risks, so caution is necessary in real applications. Additionally, syscall numbers are normallly managed by the kernel; in a real environment, you would not manually assign syscall numbers but use kernel mechanisms to register you're syscall.
Interrupts
This example provides a simple C implementation for communication between user-space and kernel-space using interrupts. It will use a character device driver and the ioctl system call. Note that this example is for demonstration only; real applications may require more error handling and functionality.
Kernel-Space (Character Device Driver)
First, we create a simple character device driver. This driver will implement the ioctl system call so that user-space programs can communicate with kernel-space.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/ioctl.h>
#include<linux/uaccess.h>
#define DEVICE_NAME "my_device"
#define DEVICE_MAJOR 240
#define DEVICE_MINOR 0
static int my_device_open(struct inode *inode, struct file *file) {
return 0;
}
static int my_device_release(struct inode *inode, struct file *file) {
return 0;
}
static long my_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int data;
switch (cmd) {
case 1: // Read data
data = 42; // Example data
if (copy_to_user((void *)arg, &data, sizeof(int))) {
return -EFAULT;
}
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations my_device_fops = {
.owner = THIS_MODULE,
.open = my_device_open,
.release = my_device_release,
.unlocked_ioctl = my_device_ioctl,
};
static struct cdev my_device_cdev;
static int __init my_device_init(void) {
int ret;
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
ret = register_chrdev_region(devno, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Failed to register device region\n");
return ret;
}
cdev_init(&my_device_cdev, &my_device_fops);
my_device_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_device_cdev, devno, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add device\n");
unregister_chrdev_region(devno, 1);
return ret;
}
printk(KERN_INFO "My device initialized\n");
return 0;
}
static void __exit my_device_exit(void) {
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
cdev_del(&my_device_cdev);
unregister_chrdev_region(devno, 1);
printk(KERN_INFO "My device exited\n");
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
User-Space (Application)
Next, create a simple user-space application that communicates with kernel-space. This application will use the ioctl system call to read data.
#include<stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define DEVICE_NAME "/dev/my_device"
int main() {
int fd, data;
fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
if (ioctl(fd, 1, (unsigned long)&data) < 0) {
perror("ioctl");
exit(EXIT_FAILURE);
}
printf("Data from kernel: %d\n", data);
close(fd);
return 0;
}
In this example, we used the ioctl system call to implement communication. The user-space application sends a command (in this case 1) via ioctl, and the kernel driver performs the corresponding operation and returns data.
Signal
This example provides a simple C implementation for communication between user-space and kernel-space using signals. It uses a character device driver and the sigaction system call.
Kernel-Space (Character Device Driver)
First, create a simple character device driver that implements signal handling so user-space programs can communicate with kernel-space.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/uaccess.h>
#include<linux/signal.h>
#define DEVICE_NAME "my_device"
#define DEVICE_MAJOR 240
#define DEVICE_MINOR 0
static int my_device_open(struct inode *inode, struct file *file) {
return 0;
}
static int my_device_release(struct inode *inode, struct file *file) {
return 0;
}
static ssize_t my_device_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos) {
int pid;
if (copy_from_user(&pid, buf, sizeof(int))) {
return -EFAULT;
}
struct siginfo info;
memset(&info, 0, sizeof(struct siginfo));
info.si_signo = SIGIO;
info.si_code = SI_QUEUE;
info.si_int = 42; // Example data
send_sig_info(SIGIO, &info, current);
return count;
}
static struct file_operations my_device_fops = {
.owner = THIS_MODULE,
.open = my_device_open,
.release = my_device_release,
.write = my_device_write,
};
static struct cdev my_device_cdev;
static int __init my_device_init(void) {
int ret;
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
ret = register_chrdev_region(devno, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Failed to register device region\n");
return ret;
}
cdev_init(&my_device_cdev, &my_device_fops);
my_device_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_device_cdev, devno, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add device\n");
unregister_chrdev_region(devno, 1);
return ret;
}
printk(KERN_INFO "My device initialized\n");
return 0;
}
static void __exit my_device_exit(void) {
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
cdev_del(&my_device_cdev);
unregister_chrdev_region(devno, 1);
printk(KERN_INFO "My device exited\n");
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
Useer-Space (Application)
Next, create a simple user-space application that communicates with kernel-space. This application will use signal handling to receive signals from kernel-space.
#include<stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include<signal.h>
#define DEVICE_NAME "/dev/my_device"
void signal_handler(int sig, siginfo_t *info, void *ucontext) {
if (sig == SIGIO) {
printf("Data from kernel: %d\n", info->si_int);
}
}
int main() {
int fd, pid;
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = signal_handler;
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGIO, &sa, NULL) < 0) {
perror("sigaction");
exit(EXIT_FAILURE);
}
pid = getpid();
fd = open(DEVICE_NAME, O_WRONLY);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
if (write(fd, &pid, sizeof(int)) < 0) {
perror("write");
exit(EXIT_FAILURE);
}
close(fd);
while (1) {
sleep(1);
}
return 0;
}
In this example, we used the sigaction system call for signal handling. The user-space application registers a signal handler via sigaction. When a signal is received, the handler is invoked. The kernel driver sends a signal using send_sig_info, and upon receiving the signal, the user-space handler prints the data.
Shared Memory
This example provides a simple C implementation for communication between user-space and kernel-space using shared memory. It uses a character device driver and the mmap system call.
Kernel-Space (Character Device Driver)
First, create a simple character device driver. This driver will implement shared memory mapping so user-space programs can communicate with kernel-space.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/mm.h>
#include<linux/slab.h>
#define DEVICE_NAME "my_device"
#define DEVICE_MAJOR 240
#define DEVICE_MINOR 0
static int my_device_open(struct inode *inode, struct file *file) {
return 0;
}
static int my_device_release(struct inode *inode, struct file *file) {
return 0;
}
static int my_device_mmap(struct file *file, struct vm_area_struct *vma) {
unsigned long size = vma->vm_end - vma->vm_start;
void *data = kmalloc(size, GFP_KERNEL);
if (!data) {
return -ENOMEM;
}
if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(data) >> PAGE_SHIFT, size, vma->vm_page_prot)) {
kfree(data);
return -EAGAIN;
}
file->private_data = data;
return 0;
}
static struct file_operations my_device_fops = {
.owner = THIS_MODULE,
.open = my_device_open,
.release = my_device_release,
.mmap = my_device_mmap,
};
static struct cdev my_device_cdev;
static int __init my_device_init(void) {
int ret;
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
ret = register_chrdev_region(devno, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Failed to register device region\n");
return ret;
}
cdev_init(&my_device_cdev, &my_device_fops);
my_device_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_device_cdev, devno, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add device\n");
unregister_chrdev_region(devno, 1);
return ret;
}
printk(KERN_INFO "My device initialized\n");
return 0;
}
static void __exit my_device_exit(void) {
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
cdev_del(&my_device_cdev);
unregister_chrdev_region(devno, 1);
printk(KERN_INFO "My device exited\n");
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
User-Space (Application)
Next, create a simple user-space application that communicates with kernel-space. This application will use the mmap system call to map shared memory, then store and read data in that shared memory.
#include<stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define DEVICE_NAME "/dev/my_device"
int main() {
int fd;
void *data;
size_t size = sysconf(_SC_PAGESIZE);
fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
// Store data in shared memory
*(int *)data = 42;
// Read data from shared memory
printf("Data from kernel: %d\n", *(int *)data);
munmap(data, size);
close(fd);
return 0;
}
In this example, we used the mmap system call for shared memory. The user-space application maps shared memory via mmap, stores and reads data. The kernel driver maps physical memory to user space using remap_pfn_range.
IOCTL
This example provides a simple C implementation for communication between user-space and kernel-space using the ioctl system call. It uses a character device driver and the ioctl system call.
Kernel-Space (Character Device Driver)
First, create a simple character device driver. This driver will implement the ioctl system call so user-space programs can communicate with kernel-space.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/uaccess.h>
#include<linux/ioctl.h>
#define DEVICE_NAME "my_device"
#define DEVICE_MAJOR 240
#define DEVICE_MINOR 0
#define IOCTL_CMD_GET_DATA _IOR('d', 1, int)
#define IOCTL_CMD_SET_DATA _IOW('d', 2, int)
static int my_device_open(struct inode *inode, struct file *file) {
return 0;
}
static int my_device_release(struct inode *inode, struct file *file) {
return 0;
}
static long my_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
int data;
switch (cmd) {
case IOCTL_CMD_GET_DATA:
data = 42; // Example data
if (copy_to_user((void *)arg, &data, sizeof(int))) {
return -EFAULT;
}
break;
case IOCTL_CMD_SET_DATA:
if (copy_from_user(&data, (void *)arg, sizeof(int))) {
return -EFAULT;
}
// Example data handling
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations my_device_fops = {
.owner = THIS_MODULE,
.open = my_device_open,
.release = my_device_release,
.unlocked_ioctl = my_device_ioctl,
};
static struct cdev my_device_cdev;
static int __init my_device_init(void) {
int ret;
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
ret = register_chrdev_region(devno, 1, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR "Failed to register device region\n");
return ret;
}
cdev_init(&my_device_cdev, &my_device_fops);
my_device_cdev.owner = THIS_MODULE;
ret = cdev_add(&my_device_cdev, devno, 1);
if (ret < 0) {
printk(KERN_ERR "Failed to add device\n");
unregister_chrdev_region(devno, 1);
return ret;
}
printk(KERN_INFO "My device initialized\n");
return 0;
}
static void __exit my_device_exit(void) {
dev_t devno = MKDEV(DEVICE_MAJOR, DEVICE_MINOR);
cdev_del(&my_device_cdev);
unregister_chrdev_region(devno, 1);
printk(KERN_INFO "My device exited\n");
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
User-Space (Application)
Next, create a simple user-space application that communicates with kernel-space. This application will use the ioctl system call to read data.
#include<stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define DEVICE_NAME "/dev/my_device"
#define IOCTL_CMD_GET_DATA _IOR('d', 1, int)
#define IOCTL_CMD_SET_DATA _IOW('d', 2, int)
int main() {
int fd, data;
fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
if (ioctl(fd, IOCTL_CMD_GET_DATA, (unsigned long)&data) < 0) {
perror("ioctl");
exit(EXIT_FAILURE);
}
printf("Data from kernel: %d\n", data);
close(fd);
return 0;
}
In this example, we used the ioctl system call to implement communication. The user-space application sends a command (here IOCTL_CMD_GET_DATA) via ioctl, and the kernel driver performs the corresponding operation and returns data.
Netlink
This example provides a simple C implementation for communication between user-space and kernel-space using netlink. It uses a kernel module and a user-space application.
Kernel-Space (Kernel Module)
First, create a simple kernel module that implements a netlink socket to communicate with user space.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/netlink.h>
#include<linux/skbuff.h>
#include <net/sock.h>
#define NETLINK_USER 31
static void netlink_recv_msg(struct sk_buff *skb) {
struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data;
int data = 42; // Example data
if (nlh->nlmsg_type == NLMSG_NOOP) {
struct sk_buff *skb_out;
struct nlmsghdr *nlh_out;
int *data_out;
skb_out = nlmsg_new(NLMSG_SPACE(sizeof(int)), GFP_ATOMIC);
if (!skb_out) {
printk(KERN_ERR "Failed to allocate new skb\n");
return;
}
nlh_out = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, sizeof(int));
if (!nlh_out) {
printk(KERN_ERR "Failed to put new message\n");
kfree_skb(skb_out);
return;
}
data_out = nlmsg_data(nlh_out);
*data_out = data;
netlink_unicast(skb_out, nlh->nlmsg_pid, MSG_DONTWAIT);
}
}
static int __init netlink_init(void) {
struct netlink_kernel_cfg cfg = {
.input = netlink_recv_msg,
};
struct sock *sock = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
if (!sock) {
printk(KERN_ERR "Failed to create netlink socket\n");
return -ENOMEM;
}
printk(KERN_INFO "Netlink initialized\n");
return 0;
}
static void __exit netlink_exit(void) {
netlink_kernel_release(netlink_kernel_create(&init_net, NETLINK_USER, NULL));
printk(KERN_INFO "Netlink exited\n");
}
module_init(netlink_init);
module_exit(netlink_exit);
MODULE_LICENSE("GPL");
User-Space (Application)
Next, create a simple user-space application that communicates with kernel-space using a netlink socket to read data.
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
#include <unistd.h>
#include <sys/socket.h>
#include<linux/netlink.h>
#define NETLINK_USER 31
int main() {
int sockfd, n;
struct sockaddr_nl src_addr, dest_addr;
struct msghdr msg;
struct iovec iov;
struct nlmsghdr *nlh;
int data;
char buffer[NLMSG_SPACE(sizeof(int))];
sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups = 0;
if (bind(sockfd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // Kernel
dest_addr.nl_groups = 0;
nlh = (struct nlmsghdr *)buffer;
nlh->nlmsg_len = NLMSG_SPACE(sizeof(int));
nlh->nlmsg_type = NLMSG_NOOP;
nlh->nlmsg_flags = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = src_addr.nl_pid;
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
if (sendmsg(sockfd, &msg, 0) < 0) {
perror("sendmsg");
exit(EXIT_FAILURE);
}
n = recv(sockfd, buffer, sizeof(buffer), 0);
if (n < 0) {
perror("recv");
exit(EXIT_FAILURE);
}
data = *(int *)NLMSG_DATA(nlh);
printf("Data from kernel: %d\n", data);
close(sockfd);
return 0;
}
In this example, we used netlink sockets to implement communication. The user-space application sends a request via netlink socket, and the kernel driver performs the corresponding operation and returns data.
procfs/sysfs
This example provides a simple C implementation for communication between user-space and kernel-space using procfs for read and write. It uses a kernel module and a user-space application.
Kernel-Space (Kernel Module)
First, create a simple kernel module. This module will implement a procfs file for communication with user space.
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/proc_fs.h>
#include<linux/seq_file.h>
#define PROC_FILE_NAME "my_proc_file"
static int data = 42; // Example data
static int my_proc_show(struct seq_file *m, void *v) {
seq_printf(m, "%d\n", data);
return 0;
}
static int my_proc_open(struct inode *inode, struct file *file) {
return single_open(file, my_proc_show, NULL);
}
static ssize_t my_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos) {
char kbuf[16];
int value;
if (count > sizeof(kbuf) - 1) {
return -EINVAL;
}
if (copy_from_user(kbuf, buf, count)) {
return -EFAULT;
}
kbuf[count] = '\0';
if (sscanf(kbuf, "%d", &value) != 1) {
return -EINVAL;
}
data = value;
return count;
}
static const struct file_operations my_proc_fops = {
.owner = THIS_MODULE,
.open = my_proc_open,
.read = seq_read,
.write = my_proc_write,
.llseek = seq_lseek,
.release = single_release,
};
static int __init my_proc_init(void) {
proc_create(PROC_FILE_NAME, 0666, NULL, &my_proc_fops);
printk(KERN_INFO "My proc file initialized\n");
return 0;
}
static void __exit my_proc_exit(void) {
remove_proc_entry(PROC_FILE_NAME, NULL);
printk(KERN_INFO "My proc file exited\n");
}
module_init(my_proc_init);
module_exit(my_proc_exit);
MODULE_LICENSE("GPL");
User-Space (Application)
Next, create a simple user-space application that communicates with kernel-space using the procfs file to read and write data.
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PROC_FILE_PATH "/proc/my_proc_file"
int main() {
int fd, n;
char buffer[256];
// Read data
fd = open(PROC_FILE_PATH, O_RDONLY);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
n = read(fd, buffer, 255);
if (n < 0) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[n] = '\0';
printf("Data from kernel: %s\n", buffer);
close(fd);
// Write data
fd = open(PROC_FILE_PATH, O_WRONLY);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
n = write(fd, "24\n", 3);
if (n < 0) {
perror("write");
exit(EXIT_FAILURE);
}
close(fd);
// Read again
fd = open(PROC_FILE_PATH, O_RDONLY);
if (fd < 0) {
perror("open");
exit(EXIT_FAILURE);
}
n = read(fd, buffer, 255);
if (n < 0) {
perror("read");
exit(EXIT_FAILURE);
}
buffer[n] = '\0';
printf("Data from kernel: %s\n", buffer);
close(fd);
return 0;
}
In this example, we used a procfs file to implement read/write communication between user-space and kernel-space. The user-space application reads and writes data via the procfs file, and the kernel driver reads the data into kernel space and writes to the file.