linux设备驱动归纳总结(三):6.poll和sellct

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

接下来会讲系统调用select在驱动中的实现,如果对系统调用select不太懂的话,建议先看书补习一下。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

一、系统调用select的简介

简单来说,select这个系统调用的作用就是在应用层调用驱动函数中的poll来检测指定的文件的状态(读、写和异常)。如果某个状态满足,select函数调用成功后返回,应用程序就可以通过指定的函数来判断现在的文件状态。注意的是:select可以指定判断的时间,指定时间内,应用程序会阻塞在select函数,直到状态满足或者超时。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

二、驱动函数poll的实现

先上代码:

9 #include

10

11 #include

12 #include

。。。。。。省略。。。。。。

23 struct _test_t{

24 char kbuf[DEV_SIZE];

25 unsigned int major;

26 unsigned int minor;

27 unsigned int cur_size;

28 dev_t devno;

29 struct cdev test_cdev;

30 wait_queue_head_t test_queue;

31 wait_queue_head_t read_queue; //定义等待队列

32 };

。。。。。。省略。。。。。。。

70 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)

71 {

72 int ret;

73 struct _test_t *dev = filp->private_data;

74

75 if(copy_from_user(dev->kbuf, buf, count)){

76 ret = - EFAULT;

77 }else{

78 ret = count;

79 dev->cur_size += count;

80 P_DEBUG("write %d bytes, cur_size:[%d]\n", count, dev->cur_size);

81 P_DEBUG("kbuf is [%s]\n", dev->kbuf);

82 wake_up_interruptible(&dev->test_queue);

83 wake_up_interruptible(&dev->read_queue); //唤醒等待队列

84 }

85

86 return ret; //返回实际写入的字节数或错误号

87 }

88 /*poll的实现*/

89 unsigned int test_poll (struct file *filp, struct poll_table_struct *table)

90 {

91 struct _test_t *dev = filp->private_data;

92 unsigned int mask = 0;

94 poll_wait(filp, &dev->read_queue, table);

96 if(dev->cur_size > 0) //设备可读

97 mask |= POLLIN;

99 P_DEBUG("***maks[%d]***\n", mask);

100 return mask;

101 }

102

103 struct file_operations test_fops = {

104 .open = test_open,

105 .release = test_close,

106 .write = test_write,

107 .read = test_read,

108 .poll = test_poll, //切记要添加,不然多牛X的代码都不能执行

109 };

110

111 struct _test_t my_dev;

112

113 static int __init test_init(void) //模块初始化函数

114 {

115 int result = 0;

116 my_dev.cur_size = 0;

117 my_dev.major = 0;

118 my_dev.minor = 0;

119

120 if(my_dev.major){

121 my_dev.devno = MKDEV(my_dev.major, my_dev.minor);

122 result = register_chrdev_region(my_dev.devno, 1, "test new driver") ;

123 }else{

124 result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver");

125 my_dev.major = MAJOR(my_dev.devno);

126 my_dev.minor = MINOR(my_dev.devno);

127 }

128

129 if(result < 0){

130 P_DEBUG("register devno errno!\n");

131 goto err0;

132 }

133

134 printk("major[%d] minor[%d]\n", my_dev.major, my_dev.minor);

135

136 cdev_init(&my_dev.test_cdev, &test_fops);

137 my_dev.test_cdev.owner = THIS_MODULE;

138 /*初始化等待队列头,注意函数调用的位置*/

139 init_waitqueue_head(&my_dev.test_queue);

140 init_waitqueue_head(&my_dev.read_queue);

141

142 result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1);

143 if(result < 0){

144 P_DEBUG("cdev_add errno!\n");

145 goto err1;

146 }

147

148 printk("hello kernel\n");

149 return 0;

150

151 err1:

152 unregister_chrdev_region(my_dev.devno, 1);

153 err0:

154 return result;

155 }

。。。。。省略。。。。。

poll函数的实现同样需要使用等待队列,在这里没有把上节阻塞型IO代码注释掉,主要是想说明一个问题,它们两个的功能是不一样的,并不会冲突。后面会具体讲述。

上面的函数其实也就三部:

定义并初始化等待队列头;

实现test_poll;

唤醒等待队列。

接下来先对照程序说一下poll函数的实现:

1)定义等待队列头:

poll_wait函数里面的操作需要用到等待队列,所以需要定义并初始化等待队列头。

2)test_poll的实现:

test_poll的实现有两个步骤:

2.1)调同poll_wait,将进程添加到指定的等待队列(注意,仅仅是添加,没有休眠)。

poll_wait的原型是:

unsigned int test_poll (struct file *filp, struct poll_table_struct *table)

注意:这里的两个参数都不是用户传给它的,全部都是有内核传的。可以这样说,poll没有做实际的什么操作,只是返回些信息给内核来操作。

来个代码来分析poll_wait究竟干了什么:

/*include/linux/poll.h */

31 typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

32

33 typedef struct poll_table_struct { //poll_table_struct的原型

34 poll_queue_proc qproc;

35 } poll_table;

36

37 static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

38 {

39 if (p && wait_address)

40 p->qproc(filp, wait_address, p); //这里就断了线索

41 }

42

43 static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)

44 {

45 pt->qproc = qproc;

46 }

47

48 struct poll_table_entry {

49 struct file *filp;

50 wait_queue_t wait;

51 wait_queue_head_t *wait_address;

52 };

。。。。。省略。。。。。

57 struct poll_wqueues {

58 poll_table pt;

59 struct poll_table_page *table;

60 struct task_struct *polling_task;

61 int triggered;

62 int error;

63 int inline_index;

64 struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];

65 };

poll_wait执行了一个函数,但没找出函数是做什么的。在另外的文件我找到一点线索:

/*fs/select.c*/

85 struct poll_table_page {

86 struct poll_table_page * next;

87 struct poll_table_entry * entry;

88 struct poll_table_entry entries[0];

89 };

。。。。。。。。

198 /* Add a new entry */

199 static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,

200 poll_table *p)

201 {

202 struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);

203 struct poll_table_entry *entry = poll_get_entry(pwq);

204 if (!entry)

205 return;

206 get_file(filp);

207 entry->filp = filp;

208 entry->wait_address = wait_address;

209 init_waitqueue_func_entry(&entry->wait, pollwake);

210 entry->wait.private = pwq;

211 add_wait_queue(wait_address, &entry->wait);

212 }

因为函数的传参和名字都差不多,我猜想内核是调用该函数的。

从上面的代码和《设备驱动程序》我得出来一下的结论:

1.应用层调用函数select,内核为了管理等待队列(有时候不止一个等待队列,因为select函数可以检测多个文件的状态),建立了一个poll_table_struct结构体(一个select系统调用对应一个结构体)。

2.poll_wait函数的调用,将三个参数传给了内核。内核中,通过结构体poll_table_struct找到另一个结构体poll_table_page,上面的代码可以看出来,这个结构体是一个维护多个poll_table_entry结构体的内存页链表,poll_wait函数的参数就是传到poll_table_entry结构体中。

3.再看一下poll_table_entry里面的成员,第一个成员srutct file是poll_wait的第一个参数,第二个成员就是定义了一个wait_queue_t的结构体,而这个结构体是正要添加到等待队列头中,也就是从poll_wait传来的第二个参数。

4.现在重头戏了,poll_wait的调用实际上调用了__pollwiat。看一下大概的操作:

4.1使用container_of函数,通过poll_table(即poll_table_struct)找到poll_wqueues,一看名字就猜到,它是存放等待队列的!poll_wqueues包含成员poll_table_page。

4.2通过传入的filp和等待队列头两个参数,新建一个poll_table_enter并添加到poll_table_page中。

2.2)对应设备的状态,返回相应的掩码。那就是说,如果设备可读,那就返回可读的掩码。

什么是掩码?有什么掩码?

掩码

含义

POLLIN

设备可读。

POLLRDNORM

数据可读。一般的,驱动可读,返回(POLLIN|POLLRDNORM),当然,只返回POLLIN也行,因为意思其实都可不多

POLLOUT

设备可写

POLLWRNORM

数据可写。一般的,驱动可写,返回(POLLOUT|POLLWRNORM),当然,只返回POLLOUT也行,因为意思其实都可不多

当然,还有其他的掩码,我这里就不意义介绍。

3)唤醒等待队列

其实一开始我也很奇怪为什么需要唤醒,毕竟poll_wait函数并不会导致休眠。为什么要唤醒?在哪里唤醒?

我上面的驱动函数,test_poll返回掩码,如果掩码为0,则表示设备不可读,这时,内核接到返回的掩码,知道设备不可读,此时select函数就会阻塞,进程休眠,等待有数据时被唤醒。所以,在写入数据后,需要唤醒等待队列头read_queue。此时设备可读了,就会再次调用test_poll函数,返回掩码POLLIN,select调用成功。

所以,这里得出两个结论:

1.test_poll并不会导致休眠,进程阻塞是系统调用select搞的鬼。

2.系统调用select的阻塞会导致test_poll被调用多次。

既然大概知道了函数怎么写的。那就验证一下程序吧。应用程序我就不贴了。在app目录下,直接来结果:

现象一:先写后读

[root: 1st]# insmod test.ko

major[253] minor[0]

hello kernel

[root: 1st]# mknod /dev/test c 253 0

[root: app]# ./monitor& //1.先后台运行检测程序monitor

[test_poll]***maks[0]*** //2.在我还没写之前,test_poll被调度了两遍后阻塞

[test_poll]***maks[0]***

[root: app]# ./app_write //3过了一段时间,我写入数据

[test_write]write 10 bytes, cur_size:[10]

[test_write]kbuf is [xiao bai]

[test_poll]***maks[1]*** //4.test_poll再次被调用,掩码改变了!

monitor:[device readable]

[root: app]# [test_poll]***maks[1]***

monitor:[device readable] //5select隔四秒就调用一遍,没有被阻塞

[test_poll]***maks[1]***

monitor:[device readable]

[test_poll]***maks[1]***

monitor:[device readable]

[test_poll]***maks[1]***

monitor:[device readable]

[root: app]# ./app_read //6我读数据

[test_read]read data.....

[test_read]read 10 bytes, cur_size:[0]

[xiao bai]

[root: app]# [test_poll]***maks[0]*** //7读完他又阻塞了。

现象二:先写后读

[root: app]# ./monitor& //1.先后台运行检测程序monitor

[test_poll]***maks[0]*** //2.在我还没写之前,test_poll被调度了两遍后阻塞

[test_poll]***maks[0]***

[root: app]# ./app_read& //3.再后台运行read

[root: app]# [test_read]read data..... //4.它阻塞了,这里不关poll的原因,这是因为上节说的阻塞型IO

[root: app]# ./app_write
//5.再写数据

[test_write]write 10 bytes, cur_size:[10]

[test_write]kbuf is [xiao bai]

[test_poll]***maks[1]*** //select被唤醒,返回可读掩码

[test_read]read 10 bytes, cur_size:[0] //test_read被唤醒,读取数据

monitor:[device readable]

[xiao bai]

[2] + Done ./app_read

[root: app]# [test_poll]***maks[0]*** //没数据,select又阻塞了

注:上面的驱动程序使用了两个等待队列头,细心可以发现,其实只要一个等待队列头,就可以实现阻塞型IO和poll了。具体就不讲解了,程序在3rd_char_6/and,很简单的改动。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

三、poll同时检测可读和可写两个状态:

也很简单,直接上程序

/*3rd_char_6/2st/test.c*/

23 struct _test_t{

24 char kbuf[DEV_SIZE];

25 unsigned int major;

26 unsigned int minor;

27 unsigned int cur_size;

28 dev_t devno;

29 struct cdev test_cdev;

30 wait_queue_head_t test_queue;

31 wait_queue_head_t read_queue; //定义两个等待队列

32 wait_queue_head_t write_queue;

33 };

。。。。。。省略。。。。。。。

48 ssize_t test_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)

49 {

50 int ret;

51 struct _test_t *dev = filp->private_data;

52

53 if(filp->f_flags & O_NONBLOCK)

54 return - EAGAIN;

55

56 P_DEBUG("read data.....\n");

57 if(wait_event_interruptible(dev->test_queue, dev->cur_size > 0))

58 return - ERESTARTSYS;

59

60 if (copy_to_user(buf, dev->kbuf, count)){

61 ret = - EFAULT;

62 }else{

63 ret = count;

64 dev->cur_size -= count;

65 P_DEBUG("read %d bytes, cur_size:[%d]\n", count, dev->cur_size);

66 wake_up_interruptible(&dev->write_queue);

67 }

68

69 return ret; //返回实际写入的字节数或错误号

70 }

71

72 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)

73 {

74 int ret;

75 struct _test_t *dev = filp->private_data;

76

77 if(copy_from_user(dev->kbuf, buf, count)){

78 ret = - EFAULT;

79 }else{

80 ret = count;

81 dev->cur_size += count;

82 P_DEBUG("write %d bytes, cur_size:[%d]\n", count, dev->cur_size);

83 P_DEBUG("kbuf is [%s]\n", dev->kbuf);

84 wake_up_interruptible(&dev->test_queue);

85 wake_up_interruptible(&dev->read_queue);

86 }

87

88 return ret; //返回实际写入的字节数或错误号

89 }

91 unsigned int test_poll (struct file *filp, struct poll_table_struct *table)

92 {

93 struct _test_t *dev = filp->private_data;

94 unsigned int mask = 0;

96 poll_wait(filp, &dev->read_queue, table);

97 poll_wait(filp, &dev->write_queue, table);

99 if(dev->cur_size > 0) //设备可读

100 mask |= POLLIN;

101 if(dev->cur_size < DEV_SIZE) //设备可写

102 mask |= POLLOUT;

104 P_DEBUG("***************************\n");

105 return mask;

106 }

107

108 struct file_operations test_fops = {

109 .open = test_open,

110 .release = test_close,

111 .write = test_write,

112 .read = test_read,

113 .poll = test_poll,

114 };

115

116 struct _test_t my_dev;

117

118 static int __init test_init(void) //模块初始化函数

119 {

120 int result = 0;

121 my_dev.cur_size = 0;

122 my_dev.major = 0;

123 my_dev.minor = 0;

124

125 if(my_dev.major){

126 my_dev.devno = MKDEV(my_dev.major, my_dev.minor);

127 result = register_chrdev_region(my_dev.devno, 1, "test new driver") ;

128 }else{

129 result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver");

130 my_dev.major = MAJOR(my_dev.devno);

131 my_dev.minor = MINOR(my_dev.devno);

132 }

133

134 if(result < 0){

135 P_DEBUG("register devno errno!\n");

136 goto err0;

137 }

138

139 printk("major[%d] minor[%d]\n", my_dev.major, my_dev.minor);

140

141 cdev_init(&my_dev.test_cdev, &test_fops);

142 my_dev.test_cdev.owner = THIS_MODULE;

143 /*初始化等待队列头,注意函数调用的位置*/

144 init_waitqueue_head(&my_dev.test_queue);

145 init_waitqueue_head(&my_dev.read_queue);

146 init_waitqueue_head(&my_dev.write_queue);

147

148 result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1);

149 if(result < 0){

150 P_DEBUG("cdev_add errno!\n");

151 goto err1;

152 }

153

154 printk("hello kernel\n");

155 return 0;

156

157 err1:

158 unregister_chrdev_region(my_dev.devno, 1);

159 err0:

160 return result;

161 }

。。。。。。。省略。。。。。。。

验证一下:注意的是,这次的select并没有阻塞,原因很简单,要不就可读要不就可写,肯定有掩码返回,根本不用阻塞。

[root: app]# ./monitor& //1.执行建材程序

[root: app]# [test_poll]***mask[4]***//2每个四秒调用一次select,没阻塞

monitor:[device writeable]

[test_poll]***mask[4]***

monitor:[device writeable]

[test_poll]***mask[4]***

monitor:[device writeable]

[root: app]# ./app_write //3写入数据

[test_write]write 10 bytes, cur_size:[10]

[test_write]kbuf is [xiao bai]

[root: app]# [test_poll]***mask[5]***

monitor:[device readable] //4掩码改变,但没有阻塞

monitor:[device writeable]

[test_poll]***mask[5]***

monitor:[device readable]

monitor:[device writeable]

[test_poll]***mask[5]***

monitor:[device readable]

monitor:[device writeable]

[root: app]# ./app_read //5读取数据

[test_read]read data.....

[test_read]read 10 bytes, cur_size:[0]

[xiao bai]

[root: app]# [test_poll]***mask[4]***

monitor:[device writeable] //6掩码改变,但没有阻塞

[test_poll]***mask[4]***

monitor:[device writeable]

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

四、select的阻塞操作

上面说了select会造成休眠,接下来简单的谈谈。select里面会调用函数do_select。

贴上程序,并附上大致的运行顺序:

/*fs/select.c*/

365 int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

366 {

367 ktime_t expire, *to = NULL;

368 struct poll_wqueues table; //1.这个就是前面说用来方等待队列的poll_wqueues

。。。。省略。。。。

380

381 poll_initwait(&table);

382 wait = &table.pt;

383 if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {

384 wait = NULL;

385 timed_out = 1;

386 }

387

388 if (end_time && !timed_out)

389 slack = estimate_accuracy(end_time);

390

391 retval = 0;

392 for (;;) { //2.注意这个大循环,如果条件不成立休眠后,唤醒正在这个大循环里

393 unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;

394

395 inp = fds->in; outp = fds->out; exp = fds->ex;

396 rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;

397

398 for (i = 0; i < n; ++rinp, ++routp, ++rexp) {

。。。。。省略。。。。。

411 for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {

412 int fput_needed;

413 if (i >= n)

414 break;

415 if (!(bit & all_bits))

416 continue;

417 file = fget_light(i, &fput_needed);

418 if (file) { //3.循环里面里边所有所有被检测的filp

419 f_op = file->f_op;

420 mask = DEFAULT_POLLMASK;

421 if (f_op && f_op->poll) //4.调用我们实现的poll函数,这也是poll被多次调用的原因,以为他在循环里面。

422 mask = (*f_op->poll)(file, retval ? NULL : wait);

423 fput_light(file, fput_needed);

424 if ((mask & POLLIN_SET) && (in & bit)) { ,下面的起码有一个条件会实现。retval++。

425 res_in |= bit;

426 retval++;

427 }

428 if ((mask & POLLOUT_SET) && (out & bit)) {

429 res_out |= bit;

430 retval++;

431 }

432 if ((mask & POLLEX_SET) && (ex & bit)) {

433 res_ex |= bit;

434 retval++;

435 }

436 }

437 }

438 if (res_in)

439 *rinp = res_in;

440 if (res_out)

441 *routp = res_out;

442 if (res_ex)

443 *rexp = res_ex;

444 cond_resched();

445 }

446 wait = NULL;

447 if (retval || timed_out || signal_pending(current))//6.如果条件成立或者 延时或者被中断

448 break; //7.调用break跳出大循环,do_select调用完毕

449 if (table.error) {

450 retval = table.error;

451 break;

452 }

453

454 /*

455 * If this is the first loop and we have a timeout

456 * given, then we convert to ktime_t and set the to

457 * pointer to the expiry value.

458 */

459 if (end_time && !to) {

460 expire = timespec_to_ktime(*end_time);

461 to = &expire;

462 }

463 /*8如果上面的条件没有成立,走到这里,进程状态改变有TASK_INTERRUPTIBLE,并加入指定的等待队列头,让出CPU,进程休眠,等待唤醒*/

464 if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,

465 to, slack))

466 timed_out = 1;

467 }

468

469 poll_freewait(&table);

470

471 return retval;

472 }

上面只是想说明:poll只是做了一个判断工作,真正的阻塞在select中。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

五、总结:

今天内容有讲完了,讲了以下内容:

、poll的实现:

1.1调用poll_wait

1.2返回掩码。

2.poll_wait的实现。

3.select中的阻塞。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

源代码: 3rd_char_6.rar

【Linux开发】linux设备驱动归纳总结(三):6.poll和sellct的更多相关文章

  1. 【Linux开发】linux设备驱动归纳总结(三):4.ioctl的实现

    linux设备驱动归纳总结(三):4.ioctl的实现 一.ioctl的简介: 虽然在文件操作结构体"struct file_operations"中有很多对应的设备操作函数,但是 ...

  2. 【Linux开发】linux设备驱动归纳总结(三):2.字符型设备的操作open、close、read、write

    linux设备驱动归纳总结(三):2.字符型设备的操作open.close.read.write 一.文件操作结构体file_operations 继续上次没讲完的问题,文件操作结构体到底是什么东西, ...

  3. 【Linux开发】linux设备驱动归纳总结(三):7.异步通知fasync

    linux设备驱动归纳总结(三):7.异步通知fasync xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  4. 【Linux开发】linux设备驱动归纳总结(三):5.阻塞型IO实现

    linux设备驱动归纳总结(三):5.阻塞型IO实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  5. 【Linux开发】linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现

    linux设备驱动归纳总结(三):3.设备驱动面向对象思想和lseek的实现 一.结构体struct file和struct inode 在之前写的函数,全部是定义了一些零散的全局变量.有没有办法整合 ...

  6. 【Linux开发】linux设备驱动归纳总结(三):1.字符型设备之设备申请

    linux设备驱动归纳总结(三):1.字符型设备之设备申请 操作系统:Ubunru 10.04 实验平台:S3C2440 + linux2.6.29内核 注:在今后驱动程序的学习中经常需要查看内核源代 ...

  7. linux设备驱动归纳总结(三):4.ioctl的实现【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-59419.html linux设备驱动归纳总结(三):4.ioctl的实现 一.ioctl的简介: 虽 ...

  8. linux设备驱动归纳总结(三):2.字符型设备的操作open、close、read、write【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-59417.html linux设备驱动归纳总结(三):2.字符型设备的操作open.close.rea ...

  9. 【Linux开发】linux设备驱动归纳总结(八):3.设备管理的分层与面向对象思想

    linux设备驱动归纳总结(八):3.设备管理的分层与面向对象思想 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

随机推荐

  1. 归类常用控件GroupBox、TabControl、Expander

    <StackPanel Orientation="Horizontal"> <GroupBox Header="颜色" Margin=&quo ...

  2. ga

    https://developers.google.com/analytics/devguides/collection/gtagjs/events ga 添加事件示例: 在代码中指定您自己的值,就可 ...

  3. Gulp error in WebStorm: Failed to list gulp tasks

    I have the same problem with webstorm after install a updated version of node. The solution for me i ...

  4. 路由器配置——单臂路由实现VLAN间通信

    一.实验目的:用路由器使同和不同vlan可以通信 二.拓扑图如下: 三.具体步骤: (1)R1路由器配置: Router>en  --进入特权模式Router#conf t  --进入全局配置模 ...

  5. 51 Nod 1092 回文字符串

    1092 回文字符串  基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题  收藏  关注 回文串是指aba.abba.cccbccc.aaaa这种左右对称的字符串.每 ...

  6. poj 2376 Cleaning Shifts 贪心 区间问题

    <pre name="code" class="html"> Cleaning Shifts Time Limit: 1000MS   Memory ...

  7. noi.ac #535 生成树

    题目链接:戳我 我们考虑按照编号依次加点,然后维护一个栈. 预设生成树的颜色为color. 对于当前点x,如果它和栈首的点连边颜色相同,那么他们的连边可以作为生成树上面的边,点i已经连接,直接brea ...

  8. easyUI datagrid中checkbox选中事件以及行点击事件,翻页之后还可以选中

    DataGrid其中与选择,勾选相关 DataGrid属性:singleSelect boolean 如果为true,则只允许选择一行. false ctrlSelect boolean 在启用多行选 ...

  9. AVPython:Python Support for ArcView

    AVPython embeds the Python programming language within ArcView GIS 3.x. This project will also encom ...

  10. 前端 Jenkins 自动化部署

    这两天折腾了一下 Jenkins 持续集成,由于公司使用自己搭建的 svn 服务器来进行代码管理,因此这里 Jenkins 是针对 svn 服务器来进行的配置,Git 配置基本一致,后面也介绍了下针对 ...