@

阻塞/非阻塞简介

  阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。

阻塞/非阻塞例程

  阻塞方式

int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

  非阻塞方式

int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

等待队列简介

  等待队列是内核中一个重要的数据结构。阻塞方式访问设备时,如果设备不可操作,那么进程就会进入休眠状态。等待队列就是来完成进程休眠操作的一种数据结构。

等待队列相关函数

定义等待队列

wait_queue_head_t my_queue;

  wait_queue_head_t是__wait_queue_head结构体的一个typedef。

初始化等待队列头

void init_waitqueue_head(wait_queue_head_t *q)

  参数q就是要初始化的等待队列头,也可以使用宏 DECLARE_WAIT_QUEUE_HEAD (name)来一次性完成等待队列头的定义的初始化。

定义并初始化一个等待队列项

DECLARE_WAITQUEUE(name, tsk)

  name就是等待队列项的名字,tsk表示这个等待队列项属于哪个任务进程,一般设置为current,在 Linux内核中 current相当于一个全局变量,表示当前进程。因此宏DECLARE_WAITQUEUE就是给当前正在运行的进程创建并初始化了一个等待队列项。

将队列项添加到等待队列头

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

  q:等待队列项要加入的等待队列头

  wait:要加入的等待队列项

  返回值:无

将队列项从等待队列头移除

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

  q:要删除的等待队列项所处的等待队列头

  wait:要删除的等待队列项。

  返回值:无

等待唤醒

void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)

  q:就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒

  wake_up函数可以唤醒处于 TASK_INTERRUPTIBLE和 TASK_UNINTERRUPTIBLE状态的进程,而wake_ up_ interruptible函数只能唤醒处于 TASK_INTERRUPTIBLE状态的进程

等待事件

wait_event(wq, condition)

  等待以wq为等待队列头的等待队列被唤醒,前提是 condition条件必须满足(为真),否则一直阻塞。此函数会将进程设置为TASK _UNINTERRUPTIBLE状态

wait_event_timeout(wq, condition, timeout)

  功能和 wait_event类似,但是此函数可以添加超时时间,以 jiffies为单位。此函数有返回值,如果返回0的话表示超时时间到,而且 condition为假。为1的话表示 condition为真,也就是条件满足了。

wait_event_interruptible(wq, condition)

  与 wait event函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断

wait_event_interruptible_timeout(wq, condition, timeout)

  与 wait event timeout函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断

轮询

  当应用程序以非阻塞的方式访问设备时,会一遍一遍的去查询我们的设备是否可以访问,这个查询操作就叫做轮询。内核中提供了poll,epoll,select函数来处理轮询操作。当应用程序在上层通过poll,epoll,select函数来查询设备时,驱动程序中的poll,epoll,select函数就要在底层实现查询,如果可以操作的话,就会从读取设备的数据或者向设备写入数据。

select

  函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

  nfds:要操作的文件描述符个数。

  readifds、 writefds和 exceptfds:这三个指针指向描述符集合,这三个参数指明了关心哪些描述符、需要满足哪些条件等等,这三个参数都是fd_set类型的, fd_set类型变量的每一个位都代表了一个文件描述符。 readfds用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取,那么 seclect就会返回一个大于0的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout参数来判断是否超时。可以将 reads设置为NULL,表示不关心任何文件的读变化。 writefds和 reads类似,只是 writers用于监视这些文件是否可以进行写操作。 exceptfds用于监视这些文件的异常

  timeout:超时时间,当我们调用 select函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval表示,结构体定义如下所示:

struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};

  当 timeout为NULL的时候就表示无限期的等待返回值。0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作;-1,发生错误;其他值,可以进行操作的文件描述符个数。

  操作fd_set变量的函数

void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)

  FD_ZERO用于将 fd set变量的所有位都清零, FD_SET用于将 fd_set变量的某个位置1,也就是向 fd_set添加一个文件描述符,参数fd就是要加入的文件描述符。 FD_CLR用户将 fd_set变量的某个位清零,也就是将一个文件描述符从 fd_set中删除,参数fd就是要删除的文件描述符。 FD_ISSET用于测试 fd_set的某个位是否置1,也就是判断某个文件是否可以进行操作,参数fd就是要判断的文件描述符。


void main(void)
{ int ret, fd; /* 要监视的文件描述符 */
fd_set readfds; /* 读操作文件描述符集 */
struct timeval timeout; /* 超时结构体 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
FD_ZERO(&readfds); /* 清除readfds */
FD_SET(fd, &readfds); /* 将fd添加到readfds里面 */
/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超时 */
printf("timeout!\r\n");
break;
case -1: /* 错误 */
printf("error!\r\n");
break;
default: /* 可以读取数据 */
if(FD_ISSET(fd, &readfds)) /* 判断是否为fd文件描述符 */
{
/* 使用read函数读取数据 */
}
break;
}
}

poll

  在单个线程中, select函数能够监视的文件描述符数量有最大的限制,一般为1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用poll函数, poll函数本质上和 select没有太大的差别,但是poll函数没有最大文件描述符限制,Linx应用程序中poll函数原型如下所示:

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

  函数参数和返回值含义如下

  fds:要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 polled类型的, pollfd结构体如下所示

struct pollfd
{
int fd; /* 文件描述符 文件描述符 文件描述符 */
short events; /* 请求的事件 请求的事件 请求的事件 */
short revents; /* 返回的事件 返回的事件 返回的事件 */
};

  fd是要监视的文件描述符,如果f无效的话那么 events监视事件也就无效,并且 revents返回0。 events是要监视的事件,可监视的事件类型如下所示

POLLIN		//有数据可以读取。
POLLPRI //有紧急的数据需要读取。
POLLOUT //可以写数据POLLERR指定的文件描述符发生错误POLLHUP指定的文件描述符挂起POLLNVAL无效的请求POLLRDNORM等同于 POLLIN

  revents:返回参数,也就是返回的事件,有Linux内核设置具体的返回事件。

  nfds:poll函数要监视的文件描述符数量

  timeout:超时时间,单位为ms

  返回值:返回 revents域中不为0的 polled结构体个数,也就是发生事件或错误的文件描述符数量;0,超时;-1,发生错误,并且设置errno为错误类型

void main(void)
{
int ret;
int fd; /* 要监视的文件描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
/* 构造结构体 */
fds.fd = fd;
fds.events = POLLIN; /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时500ms */
if (ret)
{ /* 数据有效 */
/* 读取数据 */
} else if (ret == 0)
{
/* 超时 */
} else if (ret < 0)
{
/* 错误 */
}
}

epoll

  传统的 selcet和poll函数都会随着所监听的fd数量的增加,出现效率低下的问题,而且poll函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此,epoll因运而生,epoll就是为处理大并发而准备的,一般常常在网络编程中使用epoll函数。应用程序需要先使用 epoll_create函数创建一个 epoll句柄, epoll create函数原至如下.

int epoll_create(int size)

  函数参数和返回值含义如下:

  size;从 Linux2.6.8开始此参数已经没有意义了,随便填写一个大于0的值就可以

  返回值:epoll句柄,如果为-1的话表示创建失败,epoll句柄创建成功以后使用,epoll ctl函数向其中添加要监视的文件描述符以及监视的事ct函数原型如下所示

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

  函数参数和返回值含义如下

  epfd;要操作的epoll句柄,也就是使用 epoll_create函数创建的epoll句柄。

  p:表示要对epfd( epoll句柄)进行的操作,可以设置为

EPOLL CTL ADD		//向印fd添加文件参数d表示的描述符EPOLL CTL MOD修改参数fd的 event事件。
EPOLL CTL DEL //从f中删除过l描述符

  fd:要监视的文件描述

  event:要监视的事件类型,为 epoll_event结构体类型指针, epoll_event结构体类型如下所

struct epoll_event
{
uint32_t events; /* epoll事件 */
epoll_data_t data; /* 用户数据 用户数据 */
};

  结构体 epoll_event的 events成员变量表示要监视的事件,可选的事件如下所示

EPOLLIN			//有数据可以读取EPOLLOUT可以写数据
EPOLLPRI //有紧急的数据需要读取EPOLLERI指定的文件描述符发生错误。
EPOLLHUP //指定的文件描述符挂起POLLET设置epo为边沿触发,默认触发模式为水平触发王
POLLONESHOT //一次性的监视,当监视完成以后还需要再次监视某个fd,那么就需要将fd重新添加到 epoll 里面

  上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件返回值:0,成功;-1,失败,并且设置errno的值为相应的错误码。一切都设置好以后应用程序就可以通过 epoll_wait函数来等待事件的发生,类似 select函数。 epoll_wait函数原型如下所示

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

  函数参数和返回值含义如下

  epfd:要等待的 epoll

  events:指向 epoll_event结构体的数组,当有事件发生的时候Iimx内核会填写 events,调用者可以根据 events判断发生了哪些事件。

  prevents:events数组大小,必须大于0

  timeout:超时时间,单位为ms返回值:0,超时;-1,错误;其他值,准备就绪的文件描述符数量。

  epoll更多的是用在大规模的并发服务器上,因为在这种场合下 select和poll并不适合。当设计到的文件描述符(fd比较少的时候就适合用 selcet和pl本章我们就使用 sellect和poll这两个函数

异步通知概念

  阻塞与非阻塞访问、poll函数提供了较好的解决设备访问的机制,但是如果有了异步通知,整套机制则更加完整了。

  异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。

  阻塞I/O意味着一直等待设备可访问后再访问,非阻塞I/O中使用poll()意味着查询设备是否可访问,而异步通知则意味着设备通知用户自身可访问,之后用户再进行I/O处理。由此可见,这几种I/O方式可以相互补充。

Linux信号

  异步通知的核心就是信号,在 arch/xtensa/include/uapi/asm/signal.h文件中定义了Linux所支持的所有信号

#define SIGHUP      1/* 终端挂起或控制进程终止 */
#define SIGINT 2/* 终端中断(Ctrl+C组合键) */
#define SIGQUIT 3 /* 终端退出(Ctrl+\组合键) */
#define SIGILL 4/* 非法指令 */
#define SIGTRAP 5/* debug使用,有断点指令产生 */
#define SIGABRT 6/* 由abort(3)发出的退出指令 */
#define SIGIOT 6 /* IOT指令 */
#define SIGBUS 7 /* 总线错误 */
#define SIGFPE 8 /* 浮点运算错误 */
#define SIGKILL 9 /* 杀死、终止进程 */
#define SIGUSR1 10 /* 用户自定义信号1 */
#define SIGSEGV 11 /* 段违例(无效的内存段) */
#define SIGUSR2 12 /* 用户自定义信号2 */
#define SIGPIPE 13 /* 向非读管道写入数据 */
#define SIGALRM 14 /* 闹钟 */
#define SIGTERM 15 /* 软件终止 */
#define SIGSTKFLT 16 /* 栈异常 */
#define SIGCHLD 17 /* 子进程结束 */
#define SIGCONT 18 /* 进程继续 */
#define SIGSTOP 19 /* 停止进程的执行,只是暂停 */
#define SIGTSTP 20 /* 停止进程的运行(Ctrl+Z组合键) */
#define SIGTTIN 21 /* 后台进程需要从终端读取数据 */
#define SIGTTOU 22 /* 后台进程需要向终端写数据 */
#define SIGURG 23 /* 有"紧急"数据 */
#define SIGXCPU 24 /* 超过CPU资源限制 */
#define SIGXFSZ 25 /* 文件大小超额 */
#define SIGVTALRM 26 /* 虚拟时钟信号 */
#define SIGPROF 27 /* 时钟信号描述 */
#define SIGWINCH 28 /* 窗口大小改变 */
#define SIGIO 29 /* 可以进行输入/输出操作 */
#define SIGPOLL SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 30 /* 断点重启 */
#define SIGSYS 31 /* 非法的系统调用 */
#define SIGUNUSED 31 /* 未使用信号 */

异步通知代码

  我们使用中断的时候需要设置中断处理函数,同样的,如果要在应用程序中使用信号,那么就必须设置信号所使用的信号处理函数,在应用程序中使用 signal函数来设置指定信号的处理函数, signal函数原型如下所示

void (*signal(int signum, void (*handler))(int)))(int);

  该函数原型较难理解,它可以分解为:

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));

  第一个参数指定信号的值,第二个参数指定针对前面信号值的处理函数,若为SIG_IGN,表示忽略该信号;若为SIG_DFL,表示采用系统默认方式处理信号;若为用户自定义的函数,则信号被捕获到后,该函数将被执行。

  如果signal调用成功,它返回最后一次为信号signum绑定的处理函数的handler值,失败则返回SIG_ERR。

驱动中的信号处理

fasync_struct结构体

  首先我们需要在驱动程序中定义个 fasync_struct结构体指针变量, fasync_struct结构体内容如下

struct fasync_struct
{ spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};

  一般将 fasync_struct结构体指针变量定义到设备结构体中,比如在xxx_dev结构体中添加一个 fasync_struct结构体指针变量,结果如下所示

struct xxx_dev
{
struct device *dev;
struct class *cls;
struct cdev cdev;
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};

fasync函数

  如果要使用异步通知,需要在设备驱动中实现file_ operations操作集中的 fasync函数,此函数格式如下所示:

int (*fasync) (int fd, struct file *filp, int on)

  fasync函数里面一般通过调用 fasync_helper函数来初始化前面定义的 fasync_struct结构体指针, fasync_helper函数原型如下

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)

  fasync_helper函数的前三个参数就是 fasync函数的那三个参数,第四个参数就是要初始化的 fasync_ struct结构体指针变量。当应用程序通过结构体指针变量。当应用程序通过“ fcntl(fd, F_SETFL, flags | FASYNC)”改变fasync标记的时候,驱动程序 file_operations操作集中的 fasync函数就会执行。

struct xxx_dev
{
......
struct fasync_struct *async_queue; /* 异步相关结构体 */
};
static int xxx_fasync(int fd, struct file *filp, int on)
{
struct xxx_dev *dev = (xxx_dev)filp->private_data;
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
static struct file_operations xxx_ops =
{
......
.fasync = xxx_fasync,
......
};

  在关闭驱动文件的时候需要在file_ operations操作集中的 release函数中释放 fasyn_fasync struct的释放函数同样为 fasync_helper, release函数参数参考实例如下

static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 删除异步通知 */
}
static struct file_operations xxx_ops =
{
......
.release = xxx_release,
};

  第3行通过调用示例代码 xxx_fasync函数来完成 fasync_struct的释放工作,但是,其最终还是通过 fasync_helper函数完成释放工作。

kill_fasync函数

  当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生“中断” kill_fasync函数负责发送指定的信号, kill_fasync函数原型如下所示

void kill_fasync(struct fasync_struct **fp, int sig, int band)

  函数参数和返回值含义如下:

  fasync struct 要操作的文件指针

  sig:要发送的信号

   band:可读时设置为 POLL IN,可写时设置为 POLL OUT。

  返回值:无。

应用程序对异步通知的处理

  应用程序对异步通知的处理包括以下三步

  1、注册信号处理函数应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal函数来设置信号的处理函数。前面已经详细的讲过了,这里就不细讲了。

  2、将本应用程序的进程号告诉给内核使用fcntl(fd, F_SETOWN, getpid)将本应用程序的进程号告诉给内核

  3、开启异步通知使用如下两行程序开启异步通知:

flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态*/
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */

  重点就是通过 fcntl函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync函数就会执行。

大家的鼓励是我继续创作的动力,如果觉得写的不错,欢迎关注,点赞,收藏,转发,谢谢!

有任何问题,均可通过公告中的二维码联系我

你真的懂Linux内核中的阻塞和异步通知机制吗?的更多相关文章

  1. Linux内核中锁机制之RCU、大内核锁

    在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核锁(BKL).文章的最后对<大话Linu ...

  2. Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

    在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论内存屏障的相关内容. 三.内存屏障 不知读者是是否记得在笔 ...

  3. 大话Linux内核中锁机制之RCU、大内核锁

    大话Linux内核中锁机制之RCU.大内核锁 在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核 ...

  4. 大话Linux内核中锁机制之内存屏障、读写自旋锁及顺序锁

    大话Linux内核中锁机制之内存屏障.读写自旋锁及顺序锁 在上一篇博文中笔者讨论了关于原子操作和自旋锁的相关内容,本篇博文将继续锁机制的讨论,包括内存屏障.读写自旋锁以及顺序锁的相关内容.下面首先讨论 ...

  5. 向linux内核中添加外部中断驱动模块

    本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...

  6. 进程在Linux内核中的角色扮演

    在Linux内核中,内核将进程.线程和内核线程一视同仁,即内核使用唯一的数据结构task_struct来分别表示他们:内核使用相同的调度算法对这三者进行调度:并且内核也使用同一个函数do_fork() ...

  7. Linux内核中锁机制之完成量、互斥量

    在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内容,首先需明确完成量表示为一个执行单元需要等 ...

  8. 大话Linux内核中锁机制之完成量、互斥量

    大话Linux内核中锁机制之完成量.互斥量 在上一篇博文中笔者分析了关于信号量.读写信号量的使用及源码实现,接下来本篇博文将讨论有关完成量和互斥量的使用和一些经典问题. 八.完成量 下面讨论完成量的内 ...

  9. 嵌入式C语言自我修养 01:Linux 内核中的GNU C语言语法扩展

    1.1 Linux 内核驱动中的奇怪语法 大家在看一些 GNU 开源软件,或者阅读 Linux 内核.驱动源码时会发现,在 Linux 内核源码中,有大量的 C 程序看起来“怪怪的”.说它是C语言吧, ...

  10. Linux内核中的中断栈与内核栈的补充说明【转】

    转自:http://blog.chinaunix.net/uid-12461657-id-3487463.html 原文地址:Linux内核中的中断栈与内核栈的补充说明 作者:MagicBoy2010 ...

随机推荐

  1. 基于DotNetty实现一个接口自动发布工具 - 通信实现

    基于 DotNetty 实现通信 DotNetty : 是微软的 Azure 团队,使用 C#实现的 Netty 的版本发布.是.NET 平台的优秀网络库. 项目介绍 OpenDeploy.Commu ...

  2. Macbook磁盘系统结构/文件/目录介绍分析

    1. 系统磁盘根目录详解: 1.1 磁盘根目录结构 / (根目录)|-- Applications # 存放应用程序|-- Users # 存放用户文件和设置|-- cores # 存放核心转储文件, ...

  3. AtCoder_abc331

    AtCoder_abc331 (这次题真的真的真的好难) 比赛链接 A - Tomorrow 题目链接 题目大意 有一个\(M\)个月,\(D\)天的日历,请输出\(y年m月z日\)的下一天. 解题思 ...

  4. [ABC262E] Red and Blue Graph

    Problem Statement You are given a simple undirected graph with $N$ vertices and $M$ edges. The verti ...

  5. Head First Java学习:第十章-数字很重要

     1.Math 方法:最接近全局的方法 一种方法的行为不依靠实例变量值,方法对参数执行操作,但是操作不受实例变量状态影响,那么为了执行该方法去堆上建立对象实例比较浪费. 举例: Math mathOb ...

  6. 【scikit-learn基础】--『预处理』之 缺失值处理

    数据的预处理是数据分析,或者机器学习训练前的重要步骤.通过数据预处理,可以 提高数据质量,处理数据的缺失值.异常值和重复值等问题,增加数据的准确性和可靠性 整合不同数据,数据的来源和结构可能多种多样, ...

  7. 技巧:在Excel或Word中将回车替换掉

    一.在Excel中替换 将回车替换为逗号或其他字符,如下面的屏幕截图所示. 1. 在 查找和替换 对话框中 查找内容 字段,请按 Ctrl + J 键,然后在 更换 字段中,键入所需的字符,在这种情况 ...

  8. Linux发行版的基础目录名称、命名法则及功能规定

    罗列Linux发行版的基础目录名称命名法则及功用规定 目录描述 /主层次 的根,也是整个文件系统层次结构的根目录 /bin存放在单用户模式可用的必要命令二进制文件,所有用户都可用,如 cat.ls.c ...

  9. Spring源码学习笔记5——注册BeanPostProcessor,初始化事件多播器,注册事件监听器

    一丶前言 上篇Spring容器回调完所有的BeanFactoryPostPocessor,之后可以做到替换所有占位符,解析所有配置类等工作,这篇还会迎来一个Spring留给我们扩展的一个接口,涉及到A ...

  10. CodeForces 1459C 数论 GCD

    CodeForces 1459C 数论 GCD 原题链接 题意 首先给出n个数 之后给出m个数,每次问之前的n个数加上当前的这个数之后,总体的gcd是多少,也就是答案需要求出m个总体的gcd 思路 因 ...