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. 矩形面积并-扫描线 线段树 离散化 模板-poj1151 hdu1542

    今天刚看到这个模板我是懵逼的,这个线段树既没有建树,也没有查询,只有一个update,而且区间成段更新也没有lazy标记....研究了一下午,我突然我发现我以前根本不懂扫描线,之所以没有lazy标记, ...

  2. 转 如何观察 undo Oracle DML语句回滚开销估算

    https://searchdatabase.techtarget.com.cn/7-20392/ --use_urec 详细解读: select USED_UREC from v$transacti ...

  3. 牛客网Java刷题知识点之子类继承不了父类里的(private属性、private方法、构造方法)

    不多说,直接上干货! 子类可以继承父类的属性和方法,除了那些private的外还有一样是子类继承不了的---构造器.对于构造器而言,它只能够被子类调用,而不能被子类继承. 调用父类的构造方法我们使用s ...

  4. Java基础语法(练习)

    Java基础语法 今日内容介绍 u 循环练习 u 数组方法练习 第1章 循环练习 1.1 编写程序求 1+3+5+7+……+99 的和值. 题目分析: 通过观察发现,本题目要实现的奇数(范围1-100 ...

  5. 在使用添加按钮给table插入新的一行时遇见的问题总结及处理方法

    添加按钮的功能:点击添加按钮之后完成添加新的一行. 遇见的问题:当多次点击添加按钮生成新的多行之后,生成的每行内部按钮的保存按钮点击事件出现最晚添加的一行的行内保存点击事件执行一次,倒数第二次添加的行 ...

  6. CSS冗余简化(持续更新)

    1.float属性会把元素默认成inline-block状态,不需要再专门定义display了 2.对于inline而言,您设置line-height多大,很多时候并不需要定义height,其实际占据 ...

  7. shareTo 网页版分享

    // share -------- var shareTo = function (dest, shareCode) { var appKey = "1667889534"; // ...

  8. c#在不安装Oracle客户端的情况下与服务器上的Oracle数据库交互

     概述:     C#通过使用ADO的方式在未安装Oracle数据库的前提下,客户端程序远程访问服务器,会出现:“System.Data.OracleClient 需要 Oracle 客户端软件 8. ...

  9. 命令方式重新签名apk

    1.(每个指令之间要有一个空格) 注:拿到一个apk后,首先删除META-INF. 1.如果你的电脑装的是jdk1.6,就用下面的命令: 打开命令符,首先直接输入: Jarsigner -keysto ...

  10. UVA 1153 Keep the Customer Satisfied 顾客是上帝(贪心)

    因为每增加一个订单,时间是会增加的,所以先按截止时间d排序, 这样的话无论是删除一个订单,或者增加订单,都不会影响已经选好的订单. 然后维护一个已经选好的订单的大根堆(优先队列),如果当前无法选择的话 ...