select本质是通过设置或检查存放fd标志位的数据结构来进行下一步的处理。会阻塞,直到有一个或多个I/O就绪。
监视的文件描述符分为三类set,每一种对应不同的事件。readfds、writefds和exceptfds是指向描述符集的指针。
readfds列出的文件描述符被监视是否有数据可供读取。(可读)
writefds列出的文件描述符被监视是否有写入操作完成。(可写)
exceptfds列出的文件描述符被监视是否发生异常,或无法控制的数据是否可用。(仅仅用于socket)
这三类set为NULL时,select()不监视其对应的该类事件。
select()成功返回时,每组set都被修改以使它只包含准备好的I/O描述符。
其缺点:(a)单个进程可监视的fd数量被限制;
(b)需要维护一个用来存放大量fd的数据结构,这样会使用户空间和内核空间在传递该结构时复制开销大;
(c)对fd进行扫描是线性的,fd剧增后,IO效率较低,因为每次调用都对fd进行线性扫描遍历,所以随着fd的增加会造成遍历速度慢的性能问题;
(d)内核需要将消息传递用户空间,需要内核拷贝动作;
(e)最大支持1024个fd。
2.实战server端代码,文件名为:select-server.c
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/time.h> #include <sys/types.h> #define MAXBUF 1024 /************关于本文档******************************************** *filename: select-server.c *purpose: 演示网络异步通讯、select用法,这是服务器端程序 *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 * 但请遵循GPL *Thanks to: Google.com *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献! *********************************************************************/ int main(int argc, char **argv) { int sockfd, new_fd; socklen_t len; struct sockaddr_in my_addr, their_addr; unsigned int myport, lisnum; char buf[MAXBUF + 1]; fd_set rfds; struct timeval tv; int retval, maxfd = -1; if (argv[1]) myport = atoi(argv[1]); else myport = 7838; if (argv[2]) lisnum = atoi(argv[2]); else lisnum = 2; if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = PF_INET; my_addr.sin_port = htons(myport); if (argv[3]) my_addr.sin_addr.s_addr = inet_addr(argv[3]); else my_addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(1); } if (listen(sockfd, lisnum) == -1) { perror("listen"); exit(1); } while (1) { printf ("\n----等待新的连接到来开始新一轮聊天……\n"); len = sizeof(struct sockaddr); if ((new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &len)) == -1) { perror("accept"); exit(errno); } else printf("server: got connection from %s, port %d, socket %d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd); /* 开始处理每个新连接上的数据收发 */ printf ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n"); while (1) { /* 把集合清空 */ FD_ZERO(&rfds); /* 把标准输入(stdin)句柄0加入到集合中 */ FD_SET(0, &rfds); maxfd = 0; /* 把当前连接(socket)句柄new_fd加入到集合中 */ FD_SET(new_fd, &rfds); if (new_fd > maxfd) maxfd = new_fd; /* 设置最大等待时间 */ tv.tv_sec = 1; tv.tv_usec = 0; /* 开始等待 */ retval = select(maxfd + 1, &rfds, NULL, NULL, &tv); if (retval == -1) { printf("将退出,select出错! %s", strerror(errno)); break; } else if (retval == 0) { /* printf ("没有任何消息到来,用户也没有按键,继续等待……\n"); */ continue; } else { /*判断当前IO是否是stdin*/ if (FD_ISSET(0, &rfds)) { /* 用户按键了,则读取用户输入的内容发送出去 */ bzero(buf, MAXBUF + 1); fgets(buf, MAXBUF, stdin); if (!strncasecmp(buf, "quit", 4)) { printf("自己请求终止聊天!\n"); break; } len = send(new_fd, buf, strlen(buf) - 1, 0); if (len > 0) printf ("消息:%s\t发送成功,共发送了%d个字节!\n", buf, len); else { printf ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n", buf, errno, strerror(errno)); break; } } /*判断当前IO是否是来自socket*/ if (FD_ISSET(new_fd, &rfds)) { /* 当前连接的socket上有消息到来则接收对方发过来的消息并显示 */ bzero(buf, MAXBUF + 1); /* 接收客户端的消息 */ len = recv(new_fd, buf, MAXBUF, 0); if (len > 0) printf ("接收消息成功:'%s',共%d个字节的数据\n", buf, len); else { if (len < 0) printf ("消息接收失败!错误代码是%d,错误信息是'%s'\n", errno, strerror(errno)); else printf("对方退出了,聊天终止\n"); break; } } } } close(new_fd); /* 处理每个新连接上的数据收发结束 */ printf("还要和其它连接聊天吗?(no->退出)"); fflush(stdout); bzero(buf, MAXBUF + 1); fgets(buf, MAXBUF, stdin); if (!strncasecmp(buf, "no", 2)) { printf("终止聊天!\n"); break; } } close(sockfd); return 0; }
client端代码,文件名为select-client.c
#include <stdio.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <resolv.h> #include <stdlib.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/time.h> #include <sys/types.h> #define MAXBUF 1024 /************关于本文档******************************************** // *filename: select-client.c *purpose: 演示网络异步通讯,这是客户端程序 *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 * 但请遵循GPL *Thanks to: Google.com *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献! *********************************************************************/ int main(int argc, char **argv) { int sockfd, len; struct sockaddr_in dest; char buffer[MAXBUF + 1]; fd_set rfds; struct timeval tv; int retval, maxfd = -1; if (argc != 3) { printf ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息", argv[0], argv[0]); exit(0); } /* 创建一个 socket 用于 tcp 通信 */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket"); exit(errno); } /* 初始化服务器端(对方)的地址和端口信息 */ bzero(&dest, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(atoi(argv[2])); if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) { perror(argv[1]); exit(errno); } /* 连接服务器 */ if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) { perror("Connect "); exit(errno); } printf ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n"); while (1) { /* 把集合清空 */ FD_ZERO(&rfds); /* 把标准输入句柄0加入到集合中 */ FD_SET(0, &rfds); maxfd = 0;
大家都知道在c语言的运行过程中,局部变量都是存放在栈中的,且是从高位到低位进行进行空间分配。
先看一个程序。
很明显,地址从高到低分配,和预计的一样。
稍微修改一下,再运行。
很明显,从低位到高位!!!
明确一下问题:栈区会应为局部变量的占内存的大小更改内存的分配方式。
为什么?为什么?为什么?
用-S生成汇编语言看一下
第一种情况的汇编语言
.file "main.c" .section .rodata .LC0: .string "Address s = Ox%x\n" .LC1: .string "Address d = Ox%x\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp movl %gs:20, %eax movl %eax, 28(%esp) xorl %eax, %eax movl $6513249, 24(%esp) movw $25185, 21(%esp) movb $0, 23(%esp) leal 24(%esp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf leal 21(%esp), %eax movl %eax, 4(%esp) movl $.LC1, (%esp) call printf movl $1, %eax movl 28(%esp), %edx xorl %gs:20, %edx je .L3 call __stack_chk_fail .L3: leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3" .section .note.GNU-stack,"",@progbits
第二种情况的汇编语言
.file "main.c" .section .rodata .LC0: .string "Address s = Ox%x\n" .LC1: .string "Address d = Ox%x\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp movl %gs:20, %eax movl %eax, 28(%esp) xorl %eax, %eax movl $6513249, 17(%esp) movl $1684234849, 21(%esp) movw $26213, 25(%esp) movb $0, 27(%esp) leal 17(%esp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf leal 21(%esp), %eax movl %eax, 4(%esp) movl $.LC1, (%esp) call printf movl $1, %eax movl 28(%esp), %edx xorl %gs:20, %edx je .L3 call __stack_chk_fail .L3: leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3" .section .note.GNU-stack,"",@progbits
在前面的几句mov有很明显的不同,一个是从低到高分配,一个是从高到低分配.
猜想:编译器对语言进行的优化,让长的字符串先进栈。
但为什么要这么做呢?
求解答。
本文转载自:http://blog.csdn.net/qp120291570/article/details/8889950
tasklet例程
简单实现步骤1.DECLARE_TASKLET() 2. tasklet_schedule(&demo_tasklet);
//设备相关的指针
static struct demo_dev *p = ....;
//延迟操作函数
void demo_delay_action(unsigned long data)
{
// 通过data获得设备相关指针
static struct demo_dev * pdev = (static struct demo_dev *)data;
// 延迟操作
.....
}
//用DECLARE_TASKLET(name, func, data)定义一个tasklet对象demo_tasklet
DECLARE_TASKLET(demo_tasklet, demo_delay_action, (unsigned log)p );
//中断处理例程
irqreturn_t demo_isr(int irq, void * dev_id)
{
.................
通过tasklet_schedule实现延迟操作
tasklet_schedule(&demo_tasklet); //此处提交tasklet对象和SOFTIRQ部分调用处理
}
在任意时刻,同一tasklet只能有一个实例在运行,即使是多处理器系统也如此。tasklet另一个特性是:
那个处理器调用tasklet_schedule提交的tasklet,只能在该处理器上运行。
工作队列work queue
简单实现步骤:1. INIT_WORK(_work, _func); 2. schedule_work();
struct work_struct //工作节点
struct cpu_workqueue_struct //cpu工作队列管理结构
struct workqueue_struct //工作队列管理管理结构
驱动程序可以调用create_singlethread_workaueue和creat_workqueue函数来让内核生成属于自己的工作队列,两者区别
前者只在系统中第一个CPU上创建工作队列和工人线程,后者函数会在系统中每个CPU上创建工作队列和工人线程。在用
queue_work提交工作节点时,如果是singlethread类型,只能提交到这唯一的一个worklist。反之,如果不是singlethread
类型,那么工作节点将会提交到当前运行queue_work的CPU所在的worklist中。
动态初始化:INIT_WORK(_work, _func)
静态初始化:DECLARE_WORK(n,f)
提交工作节点时只需调用schedule_work()函数就可以,对于queue_delayed_work,对于内核创建的工作队列而言,
延迟提交函数变成schedule_delayed_work().
使用内核工作队列好处是驱动无须创建自己的工作队列就可以提交节点来实现延迟操作,坏处是正在与系统
中其它模块共享一个工作队列以及该队列上的work_thread,所以无法预期提交一个节点需多久才调度执行。
内核提供了另一个提交节点的函数queue_delayed_work
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned long delay);
自己创建工作队列例程:
//定义全局性的struct workqueue_struct指针demo_dev_wq
static struct workqueue_struct * demo_dev_wq;
//设备特定的数据结构,实际使用中大部分struct work_struct结构都内嵌在这个数据结构中
struct demo_device{
....................
struct work_struct work;
...............
}
static struct demo_device * demo_dev;
//定义延迟操作函数
void demo_work_func(ftruct work_struct *work)
{.................}
// 驱动程序模块初始化代码调用create_singlethread_workqueue创建工作队列
static int_init demo_dev_init(void)
{ ..................
demo_dev = kzalloc(sizeof *demo_dev,GFP_KERNEL);
demo_dev_wq = create_singlethread_workqueue("demo_dev_workqueue");
INIT_WORK(&demo_dev->work,demo_work_func);
...................
}
//模块退出函数
static void demo_dev _exit(void)
{.................
flush_wokqueue(demo_dev_wq);
destroy_workqueue(demo_dev_wq);
.........................
}
//中断处理函数
irqreturn_t demo_isr(int irq, void * dev_id)
{...........................
queue_work(demo_dev_wq, &demo_dev->work);
..........................
}