8.1 阻塞与非阻塞IO

8.1.0 概述

  • 阻塞:访问设备时,若不能获取资源,则进程挂起,进入睡眠状态;也就是进入等待队列

  • 非阻塞:不能获取资源时,不睡眠,要么退出、要么一直查询;直接退出且无资源时,返回-EAGAIN

  • 阻塞进程的唤醒:必须有地方能够唤醒处于睡眠状态的阻塞进程,否则就真睡不醒了。一般是在中断中。
  • 阻塞与非阻塞可以在open时设置,也可以通过fcntl和ioctl重新设置

8.1.1 等待队列

  linux驱动中,可以用等待队列wait queue实现阻塞。等待队列与linux进程调度紧密结合,理解等待队列,对理解linux调度很有帮助。

  使用等待队列,大体由一下三个方面构成:

  • 初始化init,组织等待队列
  • 睡,schedule,加入等待队列以后,可以主动触发内核调度其他进程,从而进入睡眠
  • 醒,wake up,唤醒等待队列里的进程(第一步本进程已经加入了该等待队列)
#include <linux/wait.h>

/**** 定义和初始化等待队列 *****/
// head相关
wait_queue_head_t my_queue;        // 1.定义“等待队列head”
init_waitqueue_head( &my_queue );     // 2.初始化该“等待队列head”
DECLARE_WAIT_QUEUE_HEAD(&my_queue );   // 3.相当于1+2的效果,定义+初始化 // 定义等待队列元素
DECLARE_WAITQUEUE( wait,tsk);       // 定义1个等待队列元素,与进程tsk挂钩;tsk是进程,一般用current,表示当前进程,对应task_struct; // 添加删除等待队列元素,即把1个具体的等待队列添加都head所在的链表里
void add_wait_queue( wait_queue_head_t *q, wait_queue_t *wait );
void remove_wait_queue( wait_queue_head_t *q, wait_queue_t *wait ); /**** 等待事件,不一定需要调用,linux内核有些驱动也调用了 ****/
wait_event( my_queue, condition );            // 等待my_queue的队列被唤醒,同时condition必须满足,否则继续阻塞
wait_event_interruptible( my_queue,condition );     // 可被信号打断
wait_event_timeout( my_queue, condition, timeout );  // timeout到时后,不论condition是否满足,均返回
wait_evetn_timeout_interruptible( my_queue, condition, timeout ); /**** 唤醒 ****/
void wake_up( wait_queue_head_t * queue );          // 能唤醒TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE两种状态的进程
void wake_up_interruptible( wait_queue_head_t * queue );  // 只能唤醒处于TASK_INTERRUPTIBLE状态的进程 【注】
wait和wake_up,是否带_interruptible要成对儿使用。 /* 在等待队列上睡眠,老接口,不建议用,会产生race */
sleep_on( wait_queue_head_t *q );            // 干的事挺多,进程状态设置为TASK_UNINTERRUPTIBLE——>定义wait并加到head——>调度出去
interruptible_sleep_on( wait_queue_heat *q );

  如下图,等待队列采用链表形式,先要用 DECLARE_WAIT_QUEUE_HEAD()定义并初始化一个wait_queue_head,然后再定义1个队列元素wait_queue,再通过add/remove函数把这个元素添加到队列或从队列删除。

重要的等待队列模板(假设head_t已经定义好了):

#include <linux/wait.h>
#include <linux/sched.h>  // for __set_current_state()/schedule()... static ssize_t xxx_write( struct file *file, const char *buffer, size_t count, loff_t * ppos )
{
  ......
  DECLEAR_WAITQUEUE( wait, current );  // 定义等待队列元素,current代表当前进程的task_struct
  add_wait_queue( &xxx_wait,wait );   // 将等待队列元素wait加入到头部为xxx_wait的等待队列里   do{
    avail = device_writable(...);    
    if( avail < 0 ){             // 资源不可用
      if( file->f_flags & O_NONBLOCK ){  // 非阻塞立即返回
        ret = -EAGAIN;
        goto out;
      }
  
      __set_current_state( TASK_INTERRUPTIBLE );    // 只是设置状态,进程还在运行
      schedule();                      // 调度其他进程执行,估计内核正常的调度也是调用这个函数
      
      if( signal_pending( current ) ){          // interruptible是浅睡眠,可被信号打断,醒来时要判断是否是被信号打断的
        ret = -ERESTERTSYS;
        goto out;
      }
    }
  }while(avail<0)   /* 写设备 */
  device_write(...); out:
  remove_wait_queue( &xxx_wait,wait );
  set_current_state( TASK_RUNNING );
  return ret;
}

#include <asm-generic/current.h>    // current代表当前进程的task_struct

#define get_current() (current_thread_info()->task)
#define current get_current()

#include <linux/sched.h>

/*
* set_current_state() includes a barrier so that the write of current->state
* is correctly serialised wrt the caller's subsequent test of whether to
* actually sleep:
*
* set_current_state(TASK_UNINTERRUPTIBLE);
* if (do_i_need_to_sleep())
* schedule();
*
* If the caller does not need such serialisation then use __set_current_state()
*/
#define __set_current_state(state_value) \
do { current->state = (state_value); } while (0)
#define set_current_state(state_value) \
set_mb(current->state, (state_value))

8.1.2 支持阻塞等待的驱动

把globalmem做成FIFO形式,满了再写就阻塞,空了再读就阻塞,读唤醒写,写唤醒读。

/*
注意:1.用等待队列实现读写阻塞,把globalmem当成FIFO处理
*/ #include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/sched.h> #define DEV_NAME "globalmem" #define GLOBALMEN_LEN 1024 struct globalmem_dev_t
{
struct cdev cdev;
struct class * class;
dev_t dev_no;
wait_queue_head_t r_wait_head;
wait_queue_head_t w_wait_head;
struct mutex mutex;
unsigned int curr_len;
char buf[GLOBALMEN_LEN];
}globalmem_dev; int globalmem_open(struct inode * inode, struct file * filp)
{
filp->private_data = &globalmem_dev;
#if 0 // 不能放在这,因为每次写,例如echo命令,就会open一次,会重新初始化r_wait_head,导致read等待队列重新初始化,引起等待队列异常。 唉,基本常识都忘记了
init_waitqueue_head(&globalmem_dev.r_wait_head);
init_waitqueue_head(&globalmem_dev.w_wait_head);
mutex_init(&globalmem_dev.mutex);
#endif
printk("\r\nglobalmem open.");
printk("\r\nglobalmem open.");
return ;
} ssize_t globalmem_read(struct file *filp, char __user * buf, size_t len, loff_t * pos)
{
struct globalmem_dev_t * globalmem_devp;
size_t len_rd;
int ret; globalmem_devp = filp->private_data; DECLARE_WAITQUEUE(wait,current); mutex_lock(&globalmem_devp->mutex);
add_wait_queue(&globalmem_devp->r_wait_head,&wait);
while(globalmem_devp->curr_len==)
{
// non-block
if( filp->f_flags&O_NONBLOCK )
{
ret = -EAGAIN;
goto out;
} printk("\r\nglobelmem read before schedule.");
__set_current_state( TASK_INTERRUPTIBLE );
mutex_unlock(&globalmem_devp->mutex); // 睡前一定要解锁,否则可能引起死锁
schedule(); // schudule
printk("\r\nglobelmem read after schedule.");
if( signal_pending( current ) ){
ret = -ERESTARTSYS;
goto out2;
}
printk("\r\nglobelmem after signal_pending.");
mutex_lock(&globalmem_devp->mutex);
} if( len>globalmem_devp->curr_len )
len_rd = globalmem_devp->curr_len;
else
len_rd = len; if( copy_to_user(buf,globalmem_devp->buf,len_rd) ){
ret=-EFAULT;
goto out;
}
else{
memcpy(globalmem_devp->buf,&globalmem_devp->buf[len_rd],globalmem_devp->curr_len-len_rd);
globalmem_devp->curr_len-=len_rd;
printk(KERN_INFO"read %d bytes,current_len %d bytes.",len_rd,globalmem_devp->curr_len);
wake_up_interruptible(&globalmem_devp->w_wait_head); // 唤醒等待队列里的写
ret = len_rd;
} out:
mutex_unlock(&globalmem_devp->mutex);
out2:
remove_wait_queue(&globalmem_devp->r_wait_head,&wait);
set_current_state(TASK_RUNNING);
return ret;
} ssize_t globalmem_write (struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{
struct globalmem_dev_t * globalmem_devp;
size_t len_wr;
int ret; printk("\r\nEnter glocalmem_write."); globalmem_devp = filp->private_data;
DECLARE_WAITQUEUE(wait,current); mutex_lock(&globalmem_devp->mutex);
add_wait_queue(&globalmem_devp->w_wait_head,&wait);
while(globalmem_devp->curr_len==GLOBALMEN_LEN)
{
// non-block
if( filp->f_flags&O_NONBLOCK )
{
ret = -EAGAIN;
goto out;
} __set_current_state( TASK_INTERRUPTIBLE );
mutex_unlock(&globalmem_devp->mutex); // 睡前一定要解锁,否则可能引起死锁
schedule(); // schudule if( signal_pending( current ) ){
ret = -ERESTARTSYS;
goto out2;
} mutex_lock(&globalmem_devp->mutex);
} if( len>(GLOBALMEN_LEN-globalmem_devp->curr_len) )
len_wr = GLOBALMEN_LEN - globalmem_devp->curr_len;
else
len_wr = len; if( copy_from_user(globalmem_devp->buf+globalmem_devp->curr_len,buf,len_wr) ){
ret=-EFAULT;
goto out;
}
else{
globalmem_devp->curr_len+=len_wr;
printk(KERN_INFO"write %d bytes,current_len %d bytes.",len_wr,globalmem_devp->curr_len);
wake_up_interruptible(&globalmem_devp->r_wait_head); // 唤醒等待队列里的写
ret = len_wr;
} out:
mutex_unlock(&globalmem_devp->mutex);
out2:
remove_wait_queue(&globalmem_devp->w_wait_head,&wait);
set_current_state(TASK_RUNNING);
return ret;
} loff_t globalmem_llseek(struct file *filp, loff_t offset, int whence )
{
loff_t ret; // 注意要有返回值 switch(whence){
case SEEK_SET:
if( offset < )
return -EINVAL;
if( offset > GLOBALMEN_LEN )
return -EINVAL;
filp->f_pos = offset;
ret = filp->f_pos;
break;
case SEEK_CUR:
if((filp->f_pos+offset)< )
return -EINVAL;
if((filp->f_pos+offset)> GLOBALMEN_LEN )
return -EINVAL;
filp->f_pos += offset;
ret = filp->f_pos;
break;
case SEEK_END:
if((filp->f_pos+offset)< )
return -EINVAL;
if((filp->f_pos+offset) > GLOBALMEN_LEN )
return -EINVAL;
filp->f_pos += (offset+GLOBALMEN_LEN);
ret = filp->f_pos;
break;
default:
return -EINVAL;
break;
} return ret;
} int globalmem_release(struct inode * inode, struct file * filp)
{
return ;
} struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.read = globalmem_read,
.write = globalmem_write,
.llseek = globalmem_llseek,
.release = globalmem_release,
}; static int __init globalmem_init( void )
{
int ret; printk("enter globalmem_init()\r\n"); cdev_init(&globalmem_dev.cdev,&globalmem_fops);
globalmem_dev.cdev.owner=THIS_MODULE; if( (ret=alloc_chrdev_region(&globalmem_dev.dev_no,,,DEV_NAME))< )
{
printk("alloc_chrdev_region err.\r\n");
return ret;
}
ret = cdev_add(&globalmem_dev.cdev,globalmem_dev.dev_no,);
if( ret )
{
printk("cdev_add err.\r\n");
return ret;
} /*
* $ sudo insmod globalmem.ko 如果使用class_create,insmod时会报错,dmesg查看内核log信息发现找不到class相关函数
* insmod: ERROR: could not insert module globalmem.ko: Unknown symbol in module
* $ dmesg
* [ 5495.606920] globalmem: Unknown symbol __class_create (err 0)
* [ 5495.606943] globalmem: Unknown symbol class_destroy (err 0)
* [ 5495.607027] globalmem: Unknown symbol device_create (err 0)
*/ globalmem_dev.class = class_create( THIS_MODULE, DEV_NAME );
device_create(globalmem_dev.class,NULL,globalmem_dev.dev_no,NULL,DEV_NAME); /* init mem and pos */
memset(globalmem_dev.buf,,GLOBALMEN_LEN); init_waitqueue_head(&globalmem_dev.r_wait_head);
init_waitqueue_head(&globalmem_dev.w_wait_head);
mutex_init(&globalmem_dev.mutex); return ;
} static void __exit globalmem_exit( void )
{
printk("enter globalmem_exit()\r\n");
unregister_chrdev_region(globalmem_dev.dev_no, );
cdev_del(&globalmem_dev.cdev);
device_destroy(globalmem_dev.class,globalmem_dev.dev_no);
class_destroy(globalmem_dev.class);
} module_init(globalmem_init);
module_exit(globalmem_exit); MODULE_LICENSE("GPL"); // 不加此声明,会报上述Unknown symbol问题

8.1.3 在用户空间验证阻塞驱动

运行结果:打开两个终端窗口,在一个里面用echo写入FIFO,两一个cat实时显示。

终端1:

:~$ sudo cat /dev/globalmem &
:[]
~$
success
nb 终端2:
sudo echo "success" > /dev/globalmem
bash: /dev/globalmem: 权限不够
:~$ sudo su                 // 必须切换到root,否则没有权限写
[sudo] 密码:
root@***# echo 'success' > /dev/globalmem
root@***# echo 'nb' > /dev/globalmem

8.2 轮询操作

  对于无阻塞访问,应用程序一般要查询是否可以无阻塞的访问。一般用select/poll查询是否可无阻塞访问,可能在select/poll里阻塞睡眠.

  

8.2.1 用户空间轮询编程

#include <sys/select.h>

/* Check the first NFDS descriptors each in READFDS (if not NULL) for read
readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS
(if not NULL) for exceptional conditions. If TIMEOUT is not NULL, time out
after waiting the interval specified therein. Returns the number of ready
descriptors, or -1 for errors.

This function is a cancellation point and therefore not marked with
__THROW. */
extern int select (int __nfds, fd_set * readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

__nfds:最大fd+1

#define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)

#define FD_CLR(fd, fdsetp) __FD_CLR (fd, fdsetp)
#define FD_ISSET(fd, fdsetp) __FD_ISSET (fd, fdsetp)
#define FD_ZERO(fdsetp) __FD_ZERO (fdsetp)

/* A time value that is accurate to the nearest
microsecond but also has a range of years. */
struct timeval
{
  __time_t tv_sec; /* Seconds. */
  __suseconds_t tv_usec; /* Microseconds. */
};

#include <poll.h>

/* Data structure describing a polling request. */
struct pollfd
{
  int fd; /* File descriptor to poll. */
  short int events; /* Types of events poller cares about. */
  short int revents; /* Types of events that actually occurred. */
};

__BEGIN_DECLS

/* Poll the file descriptors described by the NFDS structures starting at
FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for
an event to occur; if TIMEOUT is -1, block until an event occurs.
Returns the number of file descriptors with events, zero if timed out,
or -1 for errors.

This function is a cancellation point and therefore not marked with
__THROW. */
extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);

/* These are specified by iBCS2 */
#define POLLIN 0x0001     // 可读
#define POLLPRI 0x0002     // 可读高优先级数据
#define POLLOUT 0x0004        // 可写
#define POLLERR 0x0008     // 已出错
#define POLLHUP 0x0010     // 已挂断
#define POLLNVAL 0x0020    // 描述符不引用一打开文件

/* The rest seem to be more-or-less nonstandard. Check them! */
#define POLLRDNORM 0x0040   // 可读普通数据
#define POLLRDBAND 0x0080   // 可读非0优先级波段数据
#ifndef POLLWRNORM       // 与POLLOUT相同
#define POLLWRNORM 0x0100
#endif
#ifndef POLLWRBAND       // 可写非0优先级波段数据
#define POLLWRBAND 0x0200
#endif
#ifndef POLLMSG
#define POLLMSG 0x0400
#endif
#ifndef POLLREMOVE
#define POLLREMOVE 0x1000
#endif
#ifndef POLLRDHUP
#define POLLRDHUP 0x2000
#endif

#define POLLFREE 0x4000 /* currently only for epoll */

#define POLL_BUSY_LOOP 0x8000

struct pollfd {
int fd;
short events;
short revents;
};

! 8.2.2 驱动轮询编程

原型:poll( struct file * filp, poll_table * wait )
要干两件事:
  • 对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应的等待队列头部添加到poll_table中。
  • 返回表示涉笔是否可无阻塞读、写访问的掩码

void poll_wait( struct file * filp, wait_queue_head_t * queue, poll_table * wait ); 此函数并不阻塞,只是将当前进程添加到等待列表 poll_table中。实际作用是可以让queue对应的等待队列可以唤醒因select()

睡眠的进程。

模板如下:

驱动poll函数模板:
static unsigned int xxx_poll( struct file * filp, poll_table * wait )
{
  unsigned int mask=0;
  struct xxx_dev *dev=filp->private_data;   ...
  poll_wait( filp, &dev->r_wait, wait );
  poll_wait( filp, &dev->w_wait, wait );    // 把具体驱动的等待队列头部加入到 poll_table中   if( ... )    // 可读
    mask = POLLIN | POLLRDNORM;   if( ... )    // 可写
    mask = POLLOUT | POLLWRNORM;   ...   return mask;
}

8.2.3支持轮询的globalmem驱动

unsigned int globalmem_poll( struct file * filp, poll_table * wait )
{
unsigned int mask=;
struct globalmem_dev_t * globalmem_devp; globalmem_devp = filp->private_data; mutex_lock( &globalmem_devp->mutex );
poll_wait( filp, &globalmem_devp->r_wait_head, wait );
poll_wait( filp, &globalmem_devp->w_wait_head, wait ); if( globalmem_devp->curr_len != )
mask |= POLLIN | POLLRDNORM; if( globalmem_devp->curr_len != GLOBALMEN_LEN )
mask |= POLLOUT | POLLWRNORM;
mutex_unlock( &globalmem_devp->mutex );
return mask;
} struct file_operations globalmem_fops = {
.owner = THIS_MODULE,
.open = globalmem_open,
.read = globalmem_read,
.write = globalmem_write,
.llseek = globalmem_llseek,
.release = globalmem_release,
.poll = globalmem_poll,
};

8.2.4 应用层验证

#include <stdio.h>    // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <fcntl.h> // open
#include <sys/select.h> #define FILE_NAME "/dev/globalmem" int main(int args, char *argv[])
{
int fd;
fd_set rd_fd_set,wr_fd_set; printf("\r\nstart."); // open file
fd=open(FILE_NAME,O_RDONLY|O_NONBLOCK);
if( fd< )
{
printf("\r\nopen file err");
exit(-);
} while()
{
FD_ZERO(&rd_fd_set);
FD_ZERO(&wr_fd_set);
FD_SET(fd,&rd_fd_set);
FD_SET(fd,&wr_fd_set); select(fd+,&rd_fd_set,&wr_fd_set,NULL,NULL);
if( FD_ISSET(fd,&rd_fd_set) )
printf("\r\nPoll monitor:can be read");
if( FD_ISSET(fd,&wr_fd_set) )
printf("\r\nPoll monitor:can be write");
}
close(fd);
exit();
}
终端1执行应用程序:
1.初始状态,只显示可写
Poll monitor:can be write
..... .执行命令1后,可读可写
Poll monitor:can be write
Poll monitor:can be read
....
.执行命令2后,又变回可写,不可读了
Poll monitor:can be write
.....

终端2读写globalmem
# echo "test" > /dev/globalmem   // 命令1
# cat /dev/globalmem        // 命令2
test

8.3 总结

《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO的更多相关文章

  1. linux设备驱动开发详解 笔记

      在目录的 Makefile 中关于 RTC_DRV_S3C 的编译脚本为: obj -$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o 上述脚本意味着如果 RTC_DRV_S3 ...

  2. Linux设备驱动开发详解

    Linux设备驱动开发详解 http://download.csdn.net/detail/wuyouzi067/9581380

  3. 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道

    http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...

  4. 《linux设备驱动开发详解》笔记——15 linux i2c驱动

    结合实际代码和书中描述,可能跟书上有一定出入.本文后续芯片相关代码参考ZYNQ. 15.1 总体结构 如下图,i2c驱动分为如下几个重要模块 核心层core,完成i2c总线.设备.驱动模型,对用户提供 ...

  5. 《linux设备驱动开发详解》笔记——14 linux网络设备驱动

    14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx();  注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用 ...

  6. 《linux设备驱动开发详解》笔记——12linux设备驱动的软件架构思想

    本章重点讲解思想.思想.思想. 12.1 linux驱动的软件架构 下述三种思想,在linux的spi.iic.usb等复杂驱动里广泛使用.后面几节分别对这些思想进行详细说明. 思想1:驱动与设备分离 ...

  7. 《linux设备驱动开发详解》笔记——6字符设备驱动

    6.1 字符设备驱动结构 先看看字符设备驱动的架构: 6.1.1 cdev cdev结构体是字符设备的核心数据结构,用于描述一个字符设备,cdev定义如下: #include <linux/cd ...

  8. Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)

    Linux 文件系统与设备文件系统(3) 成于坚持,败于止步 sysfs 文件系统与 Linux 设备模型 1.sysfs 文件系统 Linux 2.6 内核引入了 sysfs 文件系统,sysfs ...

  9. Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)

    Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...

随机推荐

  1. 51nod 1515 明辨是非 并查集+set维护相等与不等关系

    考试时先拿vector瞎搞不等信息,又没离散化,结果好像MLE:后来想起课上讲过用set维护,就开始瞎搞迭代器...QWQ我太菜了.. 用并查集维护相等信息,用set记录不相等的信息: 如果要求变量不 ...

  2. 部署ASP.NET Core应用程序在CentOS 7

    CentOS 7部署ASP.NET Core应用程序 看了几篇大牛写的关于Linux部署ASP.NET Core程序的文章,今天来实战演练一下.2017年最后一个工作日,提前预祝大家伙元旦快乐.不扯淡 ...

  3. python2 学习 数据类型和变量

    数据类型和变量 数据类型 整数 Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等. 计算机由于使用二进制,所以,有时 ...

  4. HDU 1069 Monkey and Banana DP LIS变形题

    http://acm.hdu.edu.cn/showproblem.php?pid=1069 意思就是给定n种箱子,每种箱子都有无限个,每种箱子都是有三个参数(x, y, z)来确定. 你可以选任意两 ...

  5. Uncaught Error: Bootstrap's JavaScript requires jQuery

    在写bootstarp的时候,一直报 Uncaught Error: Bootstrap's JavaScript requires jQuery 查看了自己引入的文件路径是对的,也可以使用jquer ...

  6. c# 串口操作

    public class CommPort : IDisposable { public string Port = ""; ///<summary> ///波特率96 ...

  7. MyBatis配置文件之概述

    MyBatis配置文件所有元素 <?xml version="1.0" encoding="UTF-8"?> <configuration&g ...

  8. 阿里云服务器Centos7安装FastDFS(一)

    安装步骤一 安装FastDFS需要安装:gcc.libevent.libfastcommon.FastDFS(包括tracker和storage) 安装gcc 判断是否安装了gcc gcc -V 如果 ...

  9. POJ 1845 Sumdiv (数学,乘法逆元)

    题意: 给出数字A和B,要求AB的所有因子(包括AB和1)之和 mod 9901 的结果. 思路: 即使知道公式也得推算一阵子. 很容易知道,先把分解得到,那么得到,那么的所有因子之和的表达式如下: ...

  10. 日常-acm-开灯问题

    开灯问题.有n盏灯,编号1-n.第一个人把所有的灯打开,第二个人按下所有编号为二的倍数的开关(全关掉),第三个人按下所有编号为三的倍数的开关,以此类推.一共k个人,问最后开着的灯的编号.输入n和k,输 ...