文章目录
- 前言
- 一、内核态socket API
- 二、server内核态编程
- 1.源代码:server.c
- 2.Makefile
- 三、用户态编程
- 1.源代码 client.c
- 2.Makefile
- 总结
前言
在实际中,有些时候我们底层驱动有数据发生时,需要立即通知应用层获取数据。当然网上的方法有很多种,比如select、poll、sysctl、sysfs、procfs、netlink等,这篇文章是介绍内核态的socket和用户态的socket通信。
一、内核态socket API
内核态socket编程和用户态的socket编程流程一样,但接口API不同,但和用户态的API是对应关系,在net/socket.c中可以看到内核导出符号:
EXPORT_SYMBOL(sock_create_kern);
EXPORT_SYMBOL(sock_release);
EXPORT_SYMBOL(kernel_bind);
EXPORT_SYMBOL(kernel_listen);
EXPORT_SYMBOL(kernel_accept);
EXPORT_SYMBOL(kernel_connect);
EXPORT_SYMBOL(kernel_sendmsg);
EXPORT_SYMBOL(kernel_recvmsg);
这里还实现了linux的系统调用,后续讲解Linux如何实现系统调用。这里直接来一个内核态的server和用户态的client例程便于理解。为了方便学习理解,我直接把内核态的server编译为.ko文件,并在ko中开辟一个内核线程。
二、server内核态编程
模块功能:建立套接字, 绑定端口,监听端口,等待连接,接收数据。
1.源代码:server.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <net/sock.h>static struct task_struct *task; //内核线程任务头//内核线程服务函数,重点!!!!
static int socket_threadfn(void *data)
{struct socket *sock, *nsock;struct sockaddr_in addr;int err;//建立套接字err = sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);if(err < 0){printk("sock_create_kern failed.\n");return -1;}//绑定端口memset(&addr, '\0', sizeof(addr)); addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(8888);err = kernel_bind(sock, (struct sockaddr *) &addr, sizeof(addr));if (err < 0){printk("kernel_bind failed.\n");sock_release(sock);return -1;}//监听端口err = kernel_listen(sock, 1024);if (err < 0){printk("kernel_listen failed.\n");sock_release(sock);return -1;}//等待连接err = kernel_accept(sock, &nsock, 0);if (err < 0){printk("kernel_accept failed.\n");sock_release(sock);return -1;}//任务主循环while (!kthread_should_stop()) {struct msghdr msg = {NULL,};struct kvec iov;char buffer[1024];int len, buflen = sizeof(buffer);iov.iov_base = buffer;iov.iov_len = (size_t)buflen;//等待sk_bufer中数据可读 wait_event_interruptible(*sk_sleep(nsock->sk),!skb_queue_empty(&nsock->sk->sk_receive_queue) || kthread_should_stop());if(!skb_queue_empty(&nsock->sk->sk_receive_queue)){len = kernel_recvmsg(nsock, &msg, &iov, 1, buflen, MSG_DONTWAIT);if(len<0)printk("receiving message error\n");elseprintk("receiving: %s\n", buffer);}}sock_release(nsock);sock_release(sock);return 0;
}int __init test_server_init(void)
{task = kthread_run(socket_threadfn, NULL, "listen thread");return 0;
}void __exit test_server_exit(void)
{
}
module_init(test_server_init);
module_exit(test_server_exit);
MODULE_LICENSE("GPL");
2.Makefile
server-driver-objs := server.oobj-m:=server-driver.o
PWD:=$(shell pwd)
#这里是你自己已经编译好的内核源码路径
KDIR:=/home/vinda/work/RockLivetQ/linux-3.6
all:$(MAKE) -C $(KDIR) M=$(PWD)
clean:$(RM) -r *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions
在设备终端安装驱动,insmod server-derver.ko,启动内核态的服务。
三、用户态编程
模块功能:建立套接字,连接端口,发送数据。
1.源代码 client.c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char* argv[])
{struct sockaddr_in serverAddr;char buffer[1024];int sock, retl;sock = socket(PF_INET,SOCK_STREAM , 0);assert(sock >= 0);bzero(&serverAddr,sizeof(serverAddr));serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = htons(8888);serverAddr.sin_family = AF_INET;retl = connect(sock, (const struct sockaddr *)&serverAddr, (socklen_t)sizeof(struct sockaddr));assert(retl >= 0);retl=0;while(1){retl++;sprintf(buffer, "This is frame number %d", retl);send(sock, buffer, strlen(buffer), 0);sleep(1);}return 0;
}
2.Makefile
.PHONY: clean AllCC = arm-linux-gccAll: client
client:client.o$(CC) -o $@ $^
clean:@$(RM) *.o client
在终端启动client程序,这是内核打印输出如下:
总结
内核态的socket编程还是很好理解的。希望对你们有帮助,谢谢!