使用sockopt实现内核与用户之间通信
使用sockopt实现内核与用户之间通信
1. Linux下的sockopt
Linux提供了多种通信方式来实现内核和用户之间的数据通信,基于socket的sockopt是最常用也比较简单易用的一种方式。它的本质和ioctl()很相似,只是ioctl()需要创建新的设备文件,而sockopt只需要创建一个socket套接字便可以使用户与内核进行通信。
这里分别从内核和用户两方面来介绍sockopt的使用。
2. 内核中使用sockopt
在内核中,Netfilter提供了struct nf_sockopt_ops来将sockopt的操作定义为一个节点来加入链表中。同时提供了注册/解注册函数来使用sockopt。
数据结构和函数
sockopt操作结构体 struct nf_sockopt_ops { struct list_head list; int pf; /* Non-inclusive ranges: use 0/0/NULL to never get called. */ int set_optmin; int set_optmax; int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len); int (*compat_set)(struct sock *sk, int optval, void __user *user, unsigned int len); int get_optmin; int get_optmax; int (*get)(struct sock *sk, int optval, void __user *user, int *len); int (*compat_get)(struct sock *sk, int optval, void __user *user, int *len); /* Number of users inside set() or get(). */ unsigned int use; struct task_struct *cleanup_task; }; 注册和解注册函数 int nf_register_sockopt(struct nf_sockopt_ops *reg) void nf_unregister_sockopt(struct nf_sockopt_ops *reg) 用户数据读写函数 int copy_from_user(void *to, const void __user *from, int n) int copy_to_user(void __user *to, const void *from, int n) |
内核模块代码:
sockopt_srv.c
#include <linux/module.h> #define SOCKET_OPS_BASE 128 #define KMSG "a message from kernel" MODULE_LICENSE("GPL"); static int recv_msg(struct sock *sk, int cmd, void *user, unsigned int len) static int send_msg(struct sock *sk, int cmd, void *user, int *len) static struct nf_sockopt_ops test_sockops = static int __init init_sockopt(void) static void __exit fini_sockopt(void) module_init(init_sockopt);
|
3. 用户使用sockopt
Linux同样提供了一组用户接口来读写sockopt
#include <sys/types.h> #include <sys/socket.h> int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen); int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
|
在用户进程中,我们可以创建一个socket,然后通过socket来调用getsockopt/setsockopt来和内核空间通信。
用户代码
sockopt_clt.c
#include <unistd.h> #define SOCKET_OPS_BASE 128 #define UMSG "a message from userspace" char kmsg[64]; int main() sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); /*call function recv_msg()*/ /*call function send_msg()*/ close(sockfd);
|
Linux系统软中断
Linux系统软中断
1. Linux软中断
中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。
因此,内核把中断处理分为两部分:上半部(top half)和下半部(bottom half),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理:
快速的“上半部”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。
下半部运行时是允许中断请求的,而上半部运行时是关中断的,这是二者之间的主要区别。
内核到底什时候执行下半部,以何种方式组织下半部?这就是我们要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做bottom half(简称bh),在2.4以后的版本中有了新的发展和改进,改进的目标使下半部可以在多处理机上并行执行,并有助于驱动程序的开发者进行驱动程序的开发。下面主要介绍常用的小任务(Tasklet)机制及2.6内核中的工作队列机制。除此之外,还简要介绍2.4以前内核中的下半部和任务队列机制。
2. softirq软中断
struct softirq_action { void (*action)(struct softirq_action *); void *data; }; static struct softirq_action softirq_vec[32] __cacheline_aligned; typedef struct { unsigned int __softirq_active; //活动中断向量标志 unsigned int __softirq_mask; //软中断向量的屏蔽掩码 unsigned int __local_irq_count; unsigned int __local_bh_count; unsigned int __syscall_count; unsigned int __nmi_count; /* arch dependent */ } ____cacheline_aligned irq_cpustat_t; static inline void __cpu_raise_softirq(int cpu, int nr) { softirq_active(cpu) |= (1<<nr); } enum { HI_SOFTIRQ = 0, //高优先级的软中断 NET_TX_SOFTIRQ, //网络数据的中断 NET_RX_SOFTIRQ, //网络数据的中断 TASKLET_SOFTIRQ //tasklet中断 }; static inline void raise_softirq(int nr) void __init softirq_init() void open_softirq(int nr, void (*action)(struct softirq_action*), void *data) asmlinkage void do_softirq() |
3. tasklet小任务
Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。
小任务是指对要推迟执行的函数进行组织的一种机制。小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来也比较快。我们既可以静态地创建小任务,也可以动态地创建它。选择那种方式取决于到底是想要对小任务进行直接引用还是一个间接引用。如果准备静态地创建一个小任务(也就是对它直接引用)。
tasklet数据结构:
struct tasklet_struct { struct tasklet_struct *next; /*指向链表中的下一个结构*/ unsigned long state; /* 小任务的状态 */ atomic_t count; /* 引用计数器 */ void (*func) (unsigned long); /* 要调用的函数 */ unsigned long data; /* 传递给函数的参数 */ }; enum { TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */ TASKLET_STATE_RUN /* Tasklet is running (SMP only) */ };
|
多个tasklet可以通过tasklet描述符中的next成员指针链接成一个单向对列。为此,Linux专门在头文件include/linux/interrupt.h中定义了数据结构tasklet_head来描述一个tasklet对列的头部指针。
struct tasklet_head { struct tasklet_struct *list; }; Tasklet主要函数: Tasklet声明: #define DECLARE_TASKLET(name, func, data) / struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data } #define DECLARE_TASKLET_DISABLED(name, func, data) / struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data } tasklet初始化 void tasklet_init( struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data); tasklet调度函数 static inline void tasklet_schedule(struct tasklet_struct *t) tasklet开启和停止 static inline void tasklet_enable(struct tasklet_struct *t) static inline void tasklet_disable(struct tasklet_struct *t) tasklet任务结束 void tasklet_kill(struct tasklet_struct *t); void tasklet_kill_immediate(struct tasklet_struct *t, unsigned int cpu); tasklet同步控制 inline int tasklet_trylock(struct tasklet_struct *t) inline void tasklet_unlock(struct tasklet_struct *t) inline void tasklet_unlock_wait(struct tasklet_struct *t)
|
4. workqueue工作队列
工作队列(work queue)是另外一种将工作推后执行的形式 ,它和我们前面讨论的所有其他形式都有不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。
工作队列数据结构
struct work_struct { unsigned long pending; /* 这个工作正在等待处理吗?*/ struct list_head entry; /* 连接所有工作的链表 */ void (*func) (void *); /* 要执行的函数 */ void *data; /* 传递给函数的参数 */ void *wq_data; /* 内部使用 */ struct timer_list timer; /* 延迟的工作队列所用到的定时器 */ }; struct execute_work { struct work_struct work; }; struct workqueue_struct { struct cpu_workqueue_struct *cpu_wq; const char *name; struct list_head list; /* Empty if single thread */ }; 工作创建: DECLARE_WORK(name, void (*func) (void *), void *data); 工作初始化: INIT_WORK(struct work_struct *work, woid(*func) (void *), void *data); 工作队列创建: struct workqueue_struct * create_workqueue(char* name) struct workqueue_struct * create_singlethread_workqueue(char* name) 工作队列销毁: void destroy_workqueue(struct workqueue_struct *wq); static inline int cancel_delayed_work(struct work_struct *work) 工作调度 schedule_work(struct work_struct *work) schedule_delayed_work(struct work_struct *work, unsigned long delay) 工作队列处理函数原型: void work_handler(void *data);
|
编写自己的Linux设备中断服务程序
编写自己的Linux设备中断服务程序 作者:JuKevin
1. 设备中断
Linux系统中断可以分为硬中断和软中断两种。软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断","信号则是由内核(或其他进程)对某个进程的中断"。
2. 设备中断相关数据结构和函数
中断请求函数: 中断请求flag: 中断释放函数 中断处理函数原型 irqreturn_t 返回值类型 #define IRQ_NONE (0) struct net_device unsigned long mem_end; /* shared mem end */ unsigned char if_port; /* Selectable AUI, TP,..*/ unsigned long state; struct net_device *next; /* Net device features */ unsigned int flags; /* interface flags (a la BSD) */ /* Interface address info. */
|
3. 自己编写设备中断服务程序
myirq.c源文件
#include <linux/module.h> static int irq; module_param(interface, charp, 0644); //中断服务例程 return IRQ_NONE; //自己的设备中断模块初始化函数 //设备中断模块退出函数 MODULE_AUTHOR("Kevin");
|
Linux下设备中断表保存在/proc目录下的interrupts文件中。
我们可以查看一下它的内容
cat /proc/interrupts
CPU0 CPU1
0: 38369 22506 IO-APIC-edge timer
1: 37 2 IO-APIC-edge i8042
6: 2 2 IO-APIC-edge floppy
7: 0 0 IO-APIC-edge parport0
8: 0 1 IO-APIC-edge rtc
9: 0 0 IO-APIC-level acpi
12: 230 3 IO-APIC-edge i8042
15: 927 802 IO-APIC-edge ide1
169: 0 0 IO-APIC-level uhci_hcd:usb1, Ensoniq AudioPCI
177: 7255 2307 IO-APIC-level ioc0
185: 178 14 IO-APIC-level eth0
我们看到, 以太网驱动eth0的IRQ是185, 我们编译好自己的模块程序,然后装载模块,并输入参数,来替换eth0的中断服务程序:
insmod myirq.ko irq=185 interface=myirq
这时候再查看中断表:
cat /proc/interrupts
CPU0 CPU1
0: 38369 22506 IO-APIC-edge timer
1: 37 2 IO-APIC-edge i8042
6: 2 2 IO-APIC-edge floppy
7: 0 0 IO-APIC-edge parport0
8: 0 1 IO-APIC-edge rtc
9: 0 0 IO-APIC-level acpi
12: 230 3 IO-APIC-edge i8042
15: 927 802 IO-APIC-edge ide1
169: 0 0 IO-APIC-level uhci_hcd:usb1, Ensoniq AudioPCI
177: 7255 2307 IO-APIC-level ioc0
185: 178 14 IO-APIC-level eth0 myirq
我们看到,自己的myirq服务例程已经替换掉了原有的以太网eth0的中断服务例程了。
我们来验证一下,用dmesg查看模块日志:
myirq Request on IRQ 185 succeeded
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
myirq...<6>interrupt: dev_id=185 irq=185 name=myirq
设备eth0的中断服务程序已经被我们自己编写的中断服务程序所替换掉了。