Linux内核态和用户态的socket编程

文章目录

  • 前言
  • 一、内核态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 
#include 
#include 
#include 

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");
			else
				printk("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.o

obj-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 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#include 
#include 

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 All

CC				= arm-linux-gcc

All: client
client:client.o
	$(CC)  -o $@  $^	
clean:
	@$(RM) *.o client

在终端启动client程序,这是内核打印输出如下:
Linux内核态和用户态的socket编程_第1张图片

总结

内核态的socket编程还是很好理解的。希望对你们有帮助,谢谢!

你可能感兴趣的:(Linux,内核驱动,linux,网络,tcp/ip)