精华内容
下载资源
问答
  • Android uart driver

    千次阅读 2018-10-29 20:54:08
    我们都知道可以从手机的耳机口,通过USB-串口转换器链接到电脑USB接口,然后在电脑上使用putty或者cutecom,设置好波特率之类的参数,就可以读取到手机中kernel的log,甚至还能到xbl,abl阶段的log. 那么,这些log到底是...

    我们都知道可以从手机的耳机口,通过USB-串口转换器链接到电脑USB接口,然后在电脑上使用putty或者cutecom,设置好波特率之类的参数,就可以读取到手机中kernel的log,甚至还能读到xbl,abl阶段的log.  那么,这些log到底是怎么来的呢? 我们所说的uart到底是啥?

    1.UART是什么?

    百度百科上是这么说的:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是电脑硬件的一部分。它将要传输的资料在串行通信和并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。

    当然,在手机里面也可以有uart,并且我们的kernel中的printk log就是通过这个uart最终发送出来的.

    2.UART驱动结构分析

    2.1 驱动的init

    先认识几个比较重要的结构.

    console结构,这个里面的write函数其实就是后面printk会调用到的write.后面会具体分析.

    static struct console cons_ops = {
    	.name = "ttyMSM",
    	.write = msm_geni_serial_console_write,
    	.device = uart_console_device,
    	.setup = msm_geni_console_setup,
    	.flags = CON_PRINTBUFFER,
    	.index = -1,
    	.data = &msm_geni_console_driver,  //是下面的结构体
    };

    uart_driver结构,显然,这个结构中有个cons的元素,就是上面的console结构:

    static struct uart_driver msm_geni_console_driver = {
    	.owner = THIS_MODULE,
    	.driver_name = "msm_geni_console",
    	.dev_name = "ttyMSM",
    	.nr =  GENI_UART_NR_PORTS,
    	.cons = &cons_ops, // ==>是上面的那个结构体.
    };
    

    还有两个uart_ops的结构:

    static const struct uart_ops msm_geni_console_pops = {
    	.tx_empty = msm_geni_serial_tx_empty,
    	.stop_tx = msm_geni_serial_stop_tx,
    	.start_tx = msm_geni_serial_start_tx,
    	.stop_rx = msm_geni_serial_stop_rx,
    	.set_termios = msm_geni_serial_set_termios,
    	.startup = msm_geni_serial_startup,
    	.config_port = msm_geni_serial_config_port,
    	.shutdown = msm_geni_serial_shutdown,
    	.type = msm_geni_serial_get_type,
    	.set_mctrl = msm_geni_cons_set_mctrl,
    	.get_mctrl = msm_geni_cons_get_mctrl,
    #ifdef CONFIG_CONSOLE_POLL
    	.poll_get_char	= msm_geni_serial_get_char,
    	.poll_put_char	= msm_geni_serial_poll_put_char,
    #endif
    	.pm = msm_geni_serial_cons_pm,
    };
    
    
    serial_core.c中:
    static const struct tty_operations uart_ops = {
    	.open		= uart_open,
    	.close		= uart_close,
    	.write		= uart_write,
    	.put_char	= uart_put_char,
    	.flush_chars	= uart_flush_chars,
    	.write_room	= uart_write_room,
    	.chars_in_buffer= uart_chars_in_buffer,
    	.flush_buffer	= uart_flush_buffer,
    	.ioctl		= uart_ioctl,
    	.throttle	= uart_throttle,
    	.unthrottle	= uart_unthrottle,
    	.send_xchar	= uart_send_xchar,
    	.set_termios	= uart_set_termios,
    	.set_ldisc	= uart_set_ldisc,
    	.stop		= uart_stop,
    	.start		= uart_start,
    	.hangup		= uart_hangup,
    	.break_ctl	= uart_break_ctl,
    	.wait_until_sent= uart_wait_until_sent,
    #ifdef CONFIG_PROC_FS
    	.proc_fops	= &uart_proc_fops,
    #endif
    	.tiocmget	= uart_tiocmget,
    	.tiocmset	= uart_tiocmset,
    	.get_icount	= uart_get_icount,
    #ifdef CONFIG_CONSOLE_POLL
    	.poll_init	= uart_poll_init,
    	.poll_get_char	= uart_poll_get_char,
    	.poll_put_char	= uart_poll_put_char,
    #endif
    };
    

    接下来,看看init函数是怎么实现的.

    static int __init msm_geni_serial_init(void)
    {
    	int ret = 0;
    	int i;
    
    	for (i = 0; i < GENI_UART_NR_PORTS; i++) { 
    		msm_geni_serial_ports[i].uport.iotype = UPIO_MEM;
    		msm_geni_serial_ports[i].uport.ops = &msm_geni_serial_pops;
    		msm_geni_serial_ports[i].uport.flags = UPF_BOOT_AUTOCONF;
    		msm_geni_serial_ports[i].uport.line = i;
    	}
    
    	for (i = 0; i < GENI_UART_CONS_PORTS; i++) { 
    		msm_geni_console_port.uport.iotype = UPIO_MEM;
    		msm_geni_console_port.uport.ops = &msm_geni_console_pops; //这个操作函数结构体在上面
    		msm_geni_console_port.uport.flags = UPF_BOOT_AUTOCONF; 
    		msm_geni_console_port.uport.line = i;
    	}
    
    	ret = console_register(&msm_geni_console_driver);  //看下面,这个函数命名有点坑,实际上就是调用了 uart_register_driver().
    	if (ret)
    		return ret;
    
    	ret = uart_register_driver(&msm_geni_serial_hs_driver);//看上去是有register了两个 uart driver.
    	if (ret) {
    		uart_unregister_driver(&msm_geni_console_driver);  
    		return ret;
    	}
    
    	ret = platform_driver_register(&msm_geni_serial_platform_driver); //再注册一个platform driver
    	if (ret) {
    		console_unregister(&msm_geni_console_driver);
    		uart_unregister_driver(&msm_geni_serial_hs_driver);
    		return ret;
    	}
    
    	pr_info("%s: Driver initialized", __func__);
    	return ret;
    }

    嗯,其实init函数也没什么很特别的,关键就是调用了uart_register_driver. 后面要好好看一下这个注册函数的实现.

    /**
     *	uart_register_driver - register a driver with the uart core layer
     *	@drv: low level driver structure
     *
     *	Register a uart driver with the core driver.  We in turn register
     *	with the tty layer, and initialise the core driver per-port state.
     *
     *	We have a proc file in /proc/tty/driver which is named after the
     *	normal driver.
     *
     *	drv->port should be NULL, and the per-port structures should be
     *	registered using uart_add_one_port after this call has succeeded.
     */
    int uart_register_driver(struct uart_driver *drv)
    {
    	struct tty_driver *normal;
    	int i, retval;
    
    	BUG_ON(drv->state);
    
    	/*
    	 * Maybe we should be using a slab cache for this, especially if
    	 * we have a large number of ports to handle.
    	 */
    	drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);  //nr是15.分配多个state
    	if (!drv->state)
    		goto out;
    
    	normal = alloc_tty_driver(drv->nr); //normal是一个 struct tty_driver *
    	if (!normal)
    		goto out_kfree;
    
    	drv->tty_driver = normal;
    
    	normal->driver_name	= drv->driver_name;
    	normal->name		= drv->dev_name;
    	normal->major		= drv->major;
    	normal->minor_start	= drv->minor;
    	normal->type		= TTY_DRIVER_TYPE_SERIAL;
    	normal->subtype		= SERIAL_TYPE_NORMAL;
    	normal->init_termios	= tty_std_termios;
    	normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    	normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
    	normal->flags		= TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    	normal->driver_state    = drv;
    	tty_set_operations(normal, &uart_ops); //就是normal->ops=&uart_ops.
    
    	/*
    	 * Initialise the UART state(s).
    	 */
    	for (i = 0; i < drv->nr; i++) {
    		struct uart_state *state = drv->state + i;
    		struct tty_port *port = &state->port;  //每一个state有一个tty_port
    
    		tty_port_init(port); //这个init主要是设置这个port的一些参数
    		port->ops = &uart_port_ops; //port的操作函数结构体是uart_port_ops
    	}
    
    	retval = tty_register_driver(normal); //这个比较重要
    	if (retval >= 0)
    		return retval;
    
    	for (i = 0; i < drv->nr; i++)
    		tty_port_destroy(&drv->state[i].port);
    	put_tty_driver(normal);
    out_kfree:
    	kfree(drv->state);
    out:
    	return -ENOMEM;
    }

    其实这个注册函数看上去也还好,就是让我们的uart_driver结构变得更加庞大了.
    drv->tty_driver===>也就是上面函数中的normal,并且初始化了这个tty_driver结构体.
    drv->state->port ====>这个state还会指向一个 struct tty_port结构

    2.2 probe函数

    看probe函数.省略掉不感兴趣的部分:

    static int msm_geni_serial_probe(struct platform_device *pdev)
    {
    	int ret = 0;
    	int line;
    	struct msm_geni_serial_port *dev_port;
    	struct uart_port *uport;
    	struct resource *res;
    	struct uart_driver *drv;
    	const struct of_device_id *id;
    	bool is_console = false;
    	struct platform_device *wrapper_pdev;
    	struct device_node *wrapper_ph_node;
    	u32 wake_char = 0;
    
    	id = of_match_device(msm_geni_device_tbl, &pdev->dev);  //根据这个id,选出的是我们的msm_geni_console_driver
    	if (id) {
    		dev_dbg(&pdev->dev, "%s: %s\n", __func__, id->compatible);
    		drv = (struct uart_driver *)id->data;
    	} else {
    		dev_err(&pdev->dev, "%s: No matching device found", __func__);
    		return -ENODEV;
    	}
    
    	if (pdev->dev.of_node) {
    		if (drv->cons)
    			line = of_alias_get_id(pdev->dev.of_node, "serial"); //这个.
    		else
    			line = of_alias_get_id(pdev->dev.of_node, "hsuart");
    	} else {
    		line = pdev->id;
    	}
    
    	if (line < 0)
    		line = atomic_inc_return(&uart_line_id) - 1;
    
    	if ((line < 0) || (line >= GENI_UART_NR_PORTS))
    		return -ENXIO;
    	is_console = (drv->cons ? true : false);  //我们的这个drv是有cons的呀.
    	dev_port = get_port_from_line(line, is_console); //又出来一个新的结构,struct msm_geni_serial_port *dev_port;实际上这个结构在init函数里面有初始化的.
    	if (IS_ERR_OR_NULL(dev_port)) {
    		ret = PTR_ERR(dev_port);
    		dev_err(&pdev->dev, "Invalid line %d(%d)\n",
    					line, ret);
    		goto exit_geni_serial_probe;
    	}
    
    	uport = &dev_port->uport;  //重点,上面刚出现的这个结构指向的uport就是很重要的uport,也就是struct uart_port *uport;
    
    	/* Don't allow 2 drivers to access the same port */
    	if (uport->private_data) {
    		ret = -ENODEV;
    		goto exit_geni_serial_probe;
    	}
    
    	uport->dev = &pdev->dev;
    
    	......//中间做了不少对uport的设置.
    	
    
    	return uart_add_one_port(drv, uport); //将drv和uport绑定在一起.
    
    exit_geni_serial_probe:
    	return ret;
    }
    

    probe函数中,好像也没有做什么特别的事情,比较重要的是定义了一个struct msm_geni_serial_port *dev_port;而这个dev_port又指向一个uart_port的结构,并且对这个uart_port做了一些初始化.
    当然,对系统结构分析最重要的是最后调用的函数uart_add_one_port. 接下来继续看看这个函数是怎么实现的.

     

    /**
     *	uart_add_one_port - attach a driver-defined port structure
     *	@drv: pointer to the uart low level driver structure for this port
     *	@uport: uart port structure to use for this port.
     *
     *	This allows the driver to register its own uart_port structure
     *	with the core driver.  The main purpose is to allow the low
     *	level uart drivers to expand uart_port, rather than having yet
     *	more levels of structures.
     */
    int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
    {
    	struct uart_state *state;
    	struct tty_port *port;
    	int ret = 0;
    	struct device *tty_dev;
    	int num_groups;
    
    	BUG_ON(in_interrupt());
    
    	if (uport->line >= drv->nr)
    		return -EINVAL;
    
    	state = drv->state + uport->line;  //uport->line=0.
    	port = &state->port; //struct tty_port *port;
    
    	mutex_lock(&port_mutex);
    	mutex_lock(&port->mutex);
    	if (state->uart_port) {
    		ret = -EINVAL;
    		goto out;
    	}
    
    	/* Link the port to the driver state table and vice versa */
    	atomic_set(&state->refcount, 1);
    	init_waitqueue_head(&state->remove_wait);
    	state->uart_port = uport;   //当当当!state指向的uart_port结构就是传参传进来的uport
    	uport->state = state; //相应地,uport的state也就是传进来的drv->state.这两步就是将drv和uport绑定在一起的!
    
    	state->pm_state = UART_PM_STATE_UNDEFINED;
    	uport->cons = drv->cons;  //uport的cons就是drv的cons
    	uport->minor = drv->tty_driver->minor_start + uport->line;
    
    	/*
    	 * If this port is a console, then the spinlock is already
    	 * initialised.
    	 */
    	if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
    		spin_lock_init(&uport->lock);
    		lockdep_set_class(&uport->lock, &port_lock_key);
    	}
    	if (uport->cons && uport->dev)
    		of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
    	
    	//这个uart_configure_port里面会根据条件判断是否需要调用一个register_console.
    //并且会加入到console_drivers的list中.这个如果注册成功,在printk最后会调到uport->cons->write函数.
    	//也就是开篇的static struct console cons_ops中的write函数.
    	uart_configure_port(drv, state, uport);  
    
    	port->console = uart_console(uport);
    
    	num_groups = 2;
    	if (uport->attr_group)
    		num_groups++;
    
    	uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
    				    GFP_KERNEL);
    	if (!uport->tty_groups) {
    		ret = -ENOMEM;
    		goto out;
    	}
    	uport->tty_groups[0] = &tty_dev_attr_group;
    	if (uport->attr_group)
    		uport->tty_groups[1] = uport->attr_group;
    
    	/*
    	 * Register the port whether it's detected or not.  This allows
    	 * setserial to be used to alter this port's parameters.
    	 */
    	tty_dev = tty_port_register_device_attr(port, drv->tty_driver,
    			uport->line, uport->dev, port, uport->tty_groups);
    	if (likely(!IS_ERR(tty_dev))) {
    		device_set_wakeup_capable(tty_dev, 1);
    	} else {
    		dev_err(uport->dev, "Cannot register tty device on line %d\n",
    		       uport->line);
    	}
    
    	/*
    	 * Ensure UPF_DEAD is not set.
    	 */
    	uport->flags &= ~UPF_DEAD;
    
     out:
    	mutex_unlock(&port->mutex);
    	mutex_unlock(&port_mutex);
    
    	return ret;
    }
    

    到现在位置,驱动的初始化部分就结束了. 后面再看看是如何使用这个uart进行通信的.

    3 printk分析

    直接上code:

    /**
     * printk - print a kernel message
     * @fmt: format string
     *
     * This is printk(). It can be called from any context. We want it to work.
     *
     * We try to grab the console_lock. If we succeed, it's easy - we log the
     * output and call the console drivers.  If we fail to get the semaphore, we
     * place the output into the log buffer and return. The current holder of
     * the console_sem will notice the new output in console_unlock(); and will
     * send it to the consoles before releasing the lock.
     *
     * One effect of this deferred printing is that code which calls printk() and
     * then changes console_loglevel may break. This is because console_loglevel
     * is inspected when the actual printing occurs.
     *
     * See also:
     * printf(3)
     *
     * See the vsnprintf() documentation for format string extensions over C99.
     */
    asmlinkage __visible int printk(const char *fmt, ...)
    {
    	va_list args;
    	int r;
    
    	va_start(args, fmt);
    	r = vprintk_func(fmt, args);
    	va_end(args);
    
    	return r;
    }
    EXPORT_SYMBOL(printk);
    

    接着看:

    static inline __printf(1, 0) int vprintk_func(const char *fmt, va_list args)
    {
    	return vprintk_default(fmt, args);
    }

    再看:

    int vprintk_default(const char *fmt, va_list args)
    {
    	int r;
    
    #ifdef CONFIG_KGDB_KDB  //这个是没有定义的...
    	if (unlikely(kdb_trap_printk)) {
    		r = vkdb_printf(KDB_MSGSRC_PRINTK, fmt, args);
    		return r;
    	}
    #endif
    	r = vprintk_emit(0, LOGLEVEL_DEFAULT, NULL, 0, fmt, args);
    
    	return r;
    }
    EXPORT_SYMBOL_GPL(vprintk_default);

    继续往后看:

    asmlinkage int vprintk_emit(int facility, int level,
    			    const char *dict, size_t dictlen,
    			    const char *fmt, va_list args)
    {
    	static bool recursion_bug;
    	static char textbuf[LOG_LINE_MAX];
    	static char textbuf1[LOG_LINE_MAX];
    	char *text = textbuf;
    	char *text1 = textbuf1;
    	size_t text_len = 0;
    	enum log_flags lflags = 0;
    	unsigned long flags;
    	int this_cpu;
    	int printed_len = 0;
    	int nmi_message_lost;
    	bool in_sched = false;
    	/* cpu currently holding logbuf_lock in this function */
    	static unsigned int logbuf_cpu = UINT_MAX;
    	
    
    	if (level == LOGLEVEL_SCHED) {
    		level = LOGLEVEL_DEFAULT;
    		in_sched = true;   //如果传进来的level==LOGLEVEL_SCHED,那就设为true
    	}
    
    	boot_delay_msec(level);
    	printk_delay();
    
    	......
    	/*
    	 * The printf needs to come first; we need the syslog
    	 * prefix which might be passed-in as a parameter.
    	 */
    	text_len = vscnprintf(text1, sizeof(textbuf1), fmt, args);
    
    	......
    
    #ifdef CONFIG_EARLY_PRINTK_DIRECT
    	printascii(text1);
    #endif
    
    	if(needPrintTime)
    	{
    		char buf[64];
    		size_t buf_size = sprintf(buf, "(CPU:%d-pid:%d:%s) ", smp_processor_id(), current->pid, current->comm);
    		strncpy(text, buf, buf_size);
    		strncpy(text + buf_size, text1, text_len);
    		text_len += buf_size;
    	}
    	else
    	{
    		strncpy(text, text1, text_len);
    	}
    
    	if (level == LOGLEVEL_DEFAULT)
    		level = default_message_loglevel;
    
    	if (dict)
    		lflags |= LOG_PREFIX|LOG_NEWLINE;
    
    	printed_len += log_output(facility, level, lflags, dict, dictlen, text, text_len);
    	
    	if(lflags & LOG_NEWLINE)
    	{
    		needPrintTime = true;
    	}
    	else
    	{
    		needPrintTime = false;
    	}
    
    	logbuf_cpu = UINT_MAX;
    	raw_spin_unlock(&logbuf_lock);
    	lockdep_on();
    	local_irq_restore(flags);
    
    	/* If called from the scheduler, we can not call up(). */
    	if (!in_sched) {
    		lockdep_off();
    		/*
    		 * Try to acquire and then immediately release the console
    		 * semaphore.  The release will print out buffers and wake up
    		 * /dev/kmsg and syslog() users.
    		 */
    		if (console_trylock())
    			console_unlock();  //这里...
    		lockdep_on();
    	}
    
    	return printed_len;
    }
    EXPORT_SYMBOL(vprintk_emit);

    再看console_unlock, 这个函数中调了   call_console_drivers(level, ext_text, ext_len, text, len);

    那么再看call_console_drivers:

    /*
     * Call the console drivers, asking them to write out
     * log_buf[start] to log_buf[end - 1].
     * The console_lock must be held.
     */
    static void call_console_drivers(int level,
    				 const char *ext_text, size_t ext_len,
    				 const char *text, size_t len)
    {
    	struct console *con;
    
    	trace_console_rcuidle(text, len);
    
    	if (!console_drivers)
    		return;
    
    	for_each_console(con) {  //遍历console_drivers,实际上只有前面提到的一个地方有成功加到这个list中
    		if(strncmp(con->name,"logk",4) != 0){
    			if (level >= console_loglevel && !ignore_loglevel)
    				continue;
    			if (exclusive_console && con != exclusive_console)
    				continue;
    	}
    		if (!(con->flags & CON_ENABLED))
    			continue;
    		if (!con->write)
    			continue;
    		if (!cpu_online(smp_processor_id()) &&
    		    !(con->flags & CON_ANYTIME))
    			continue;
    		if (con->flags & CON_EXTENDED)
    			con->write(con, ext_text, ext_len);
    		else
    			con->write(con, text, len);  //这个write函数就是最开始的console结构体中的write函数.
    	}
    }

    至于上面这个函数中遍历的console_drivers,这个list成员的注册,有一个要求是根据cmdline传过来的参数需要一致. 在机台中读到cmdline中含有console=ttyMSM0. 也就是为什么只有最开篇的这个console结构体中的write函数最终被调到,并且有log 通过uart吐出来.

    可以看到,printk丢log还是单向的.也就是说是只是printk函数最终调用了uart_driver这个结构体指向的一个结构console的write函数.

    很显然,这并不是uart的全部功能,uart应该是双向通信,这个是可以通过对节点进行open,write,read等操作来实现的. 

    参考博文:

    http://www.cnblogs.com/lidabo/p/5414007.html  驱动程序调试方法之printk——printk的原理与直接使用

    非常感谢!

     

    展开全文
  • title: Docker之理解image,container和storage driver date: 2015-11-27 11:23:40 tags: dockercategories: ...为了高效的使用Docker,必须理解Docker的文件系统,了解容器,镜像是怎么存储的,以及在对容器进行

    title: Docker之理解image,container和storage driver
    date: 2015-11-27 11:23:40
    tags: docker

    categories: Docker Others

    本篇文查主要介绍镜像,容器的文件系统,写时复制(CoW)机制和数据卷

    为了高效的使用Docker,必须理解Docker的文件系统,了解容器,镜像是怎么存储的,以及在对容器进行读写操作时,其文件系统发生了哪些变化。
    本文的图片来自于Docker官网,地址为:docker docs

    镜像(image)和容器(container)都是基于层(layer)的

    Docker的镜像是由一系列只读层组成的一个栈,上面的层依赖其下面的层,这些层从外面看起来是一个整体。栈底的镜像被称作基础镜像(base image),所有上面的层都基于这个基础镜像。
    下面是ubuntu:15.04镜像各层之间的关系:

    当你在一个容器中进行了某些操作比如添加了一个文件,然后调用docker commit操作创建新的镜像时,Docker会在镜像栈的最上面创建一个新的层,这个层包含了新添加的文件。
    或者,通过Dockerfile创建新的镜像时,通过FROM指令指定的就是基础镜像。此后的每条指令都会创建一个新的层,层中包含了这条指令对镜像的修改。

    容器container不仅包含镜像的所有层,它还在最上面添加了一个可读层称作容器层container layer。下面是一个基于ubuntu:15.04运行起来的容器的层之间的关系:

    容器与镜像的主要区别就在于这个可写层(writable layer),对容器的所有写操作无论是添加新内容还是修改原来的内容都会保存在这个可读层中。如果容器被删除,writable layer也会被删除,但镜像层不变。
    正是因为每个镜像都有自己的可写层,所以容器之间可以共享同一个镜像的各层。下面是多个容器使用同一个镜像的例子:

    存储驱动器需要管理所有的镜像层和容器只读层,而进行这些管理除了需要分层机制外还需要写时复制策略(CoW)。

    写时复制策略

    CoW策略有两个关键词:“分享”share和“复制”copy。这个策略是指,所有需要某个文件的进程都共用同一个文件实例而不是各自拥有一份副本,即分享。一旦有进程需要改写某个文件,系统就给他一个文件副本供它操作,即复制。只有需要写的进程才会拥有一份源文件的拷贝,其他进程仍然共享源文件。
    Docker在镜像管理和容器中都使用了写时复制策略,这不仅缩小了镜像对磁盘空间的利用率,也加快了容器的启动速度。

    共享使镜像更小

    所有镜像和容器的层都存储在宿主机中,并由storage driver管理。
    当我们在本地执行docker pull命令时,可以看到下载了多个文件,每个文件都是一个镜像层,每一个镜像层都有一个标识符(UUID),这些层加在一起组成了一个完整的镜像。
    上面已经提到,镜像之间可以共享镜像层的,也就是说,当执行pull操作时,如果要拉取的镜像与本地已有的镜像共享某些层,这些层不会重复下载,而是只下载本地没有的层。
    在基于已有镜像创建新镜像时同样如此。比如,在一个空文件夹下创建如下Dockerfile

    FROM ubuntu:15.04
    RUN echo "hello" > /tmp/newfile

    基于此Dockerfile创建新镜像,运行docker build -t changed-ubuntu .,得到新的镜像changed-ubuntu,在运行docker history命令查看changed-ubuntu,可以看到如下信息:

    IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
    03b964f68d06        About a minute ago   /bin/sh -c echo "Hello world" > /tmp/newfile    12 B                
    013f3d01d247        6 weeks ago          /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B                 
    2bd276ed39d5        6 weeks ago          /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.879 kB            
    13c0c663a321        6 weeks ago          /bin/sh -c echo '#!/bin/sh' > /usr/sbin/polic   701 B               
    6e6a100fa147        6 weeks ago          /bin/sh -c #(nop) ADD file:49710b44e2ae0edef4   131.4 MB

    通过创建时间我们可以看出,刚才的docker build命令仅创建了最上面的层,其大小仅有12B,而它下面的各层其实就是组成ubuntu:15.04镜像的各层,我们可以通过比较UUID确定这一点。新镜像和ubuntu:15.04之间的关系可以用下面的图来表示:

    也就是说,事实上changed-ubuntu镜像仅占用了12B的磁盘空间。

    复制使容器更高效

    上面已经提到,每个容器都有属于自己的读写层,读写层下面的层都是只读层,不能被修改。此外,只读层是可以共享的,也就是说一个镜像的各层可以被多个容器同时使用。
    当容器中发生写操作时,使用写时复制策略,大体步骤如下:
    - 从上至下搜索要写的文件
    - 将找到的文件复制(copy up)一份到读写层中
    - 修改复制后的文件

    如果一个文件被复制到了读写层中,其源文件仍然存在,但是会被读写层中的文件覆盖掉。也就说,源文件并不改变,但其在当前容器中不再发挥作用,而是由复制到读写层的文件代替它的作用。

    将文件复制到读写层的操作copy up会带来不小的开销,尤其是对大文件的操作,层数比较多或是文件在文档树比较深的层次时,开销会更大。索性copy up操作仅在第一次修改文件时才会发生,此后对文件的操作不会引起copy up操作,而是直接对复制到读写层的文件进行操作。

    Docker的写时复制策略不仅减小了容器的磁盘开销,而且加快了容器的启动速度。当我们启动一个容器时,Docker仅创建了一个读写层writable layer,而镜像中的只读层是共享的。尤其当多个容器基于一个镜像的时候,这种优势尤其明显。

    数据卷和存储驱动器

    当容器被删除时,读写层也被删除,所有没有写入数据卷的内容也都会被删除。数据卷是挂载在容器中的一个目录或文件。
    数据卷并不由存储驱动(storage driver)管理,写入数据卷的内容绕过存储驱动器直接写在宿主机上。你可以向容器挂在任意数量的数据卷,一个数据卷也可以在多个容器间共享。
    下面的图展示了数据卷和容器之间的关系:

    当容器被删除时,数据卷中的内容会继续保存在宿主机上。

    展开全文
  • Primary(主)是MongoDB复制集中的最重要的角色,是能够接受客户端/Driver写请求的节点,(请求也是默认路由到Primary节点)。在复制集中,与Primary相对应的有Secondary节点和Arbiter节点,分别表示从节点(可以...

    此文已由作者温正湖授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。


    Primary(主)是MongoDB复制集中的最重要的角色,是能够接受客户端/Driver写请求的节点,(读请求也是默认路由到Primary节点)。在复制集中,与Primary相对应的有Secondary节点和Arbiter节点,分别表示从节点(可以接受读请求)和投票节点(仅用于投票选出新的Primary)。复制集是MongoDB的高可用框架,同时可以作为业务读写分离的一种方式。复制集提供了自动故障处理功能(当然还有其他功能,本文不展开),能够自动检测Primary节点是否宕机,进而选取新的Primary节点,并通过数据回追或数据回滚等方式实现复制集中数据一致。本文借助蜂巢MongoDB云服务的运行日志查看功能,来简要介绍Primary的选举过程。


    MongoDB提供了强大的SystemLog模块,相比MySQL,MongoDB的运行日志模块做得更为贴心,通过日志能够有效跟踪MongoDB内部是如何进行一个个操作的。下面的图都截取自蜂巢MongoDB云服务的运行日志模块,从中能够看到了一串的MongoDB选主日志,非常清晰明了。

    e262ff4f-0b11-4faa-8089-9b8d4d0f7394?imageView&thumbnail=980x0


    1、什么时候会发起选举?

    图中所示,该节点(我)发现在过去的10s中时间内,复制集中没有Primary,

    那么我怎么知道这段时间没有主呢,因为我每2s会给复制集中的其他节点发送心跳,

    1488252c-63d7-4b38-be12-b1585580b95c?imageView&thumbnail=980x0

    有些节点不回我

    e9a38e7c-cb05-4085-b5f5-6cdf686ec154?imageView&thumbnail=980x0

    在超时时间内(默认10s)我会一直发。

    除了心跳,我还会发送其他的命令,另外我还需要跟着Primary的opLog做复制,但是我发现没法再跟他做复制了,也找不到其他节点做复制

    1b5eb918-624f-4775-bd44-c24feb386ad1?imageView&thumbnail=980x0

    既然没有Primary。。。


    2、我能不能被选为Primary呢?

    我先试探性的问大家愿不愿意让我当Primary。于是我打算先发起 “dry election”,让人惊喜的是另一个节点竟然同意了,开心 :)。由于复制集中一共3个节点。除了自己外另一个节点也同意了,那么我就有资格当Primary;注意此时term 没有更新,还是0(看第一个图~~)。因为这个是非正式选举

    e9e89325-8f3d-4e7b-802e-d7c5360f4672?imageView&thumbnail=980x0


    3、既然这样,那我就发起正式选举吧

    结果当然是十拿九稳了,那么为什么要先有dry呢,为了保证选举成功率,相比正式选举,dry阶段检查的东西少,效率更高些。此时term已经自豪地更新为1。

    0941d693-881e-4f92-a53c-a01d0618dba7?imageView&thumbnail=980x0

    4、我果然被大家选为Primary

    一切尽在掌握中的感觉真爽!!


    5、那我就把自己的角色切换为Primary呗

    等等,这个时候我还不能马上接受客户端的写请求,因为我得看看自己的数据是不是最新的,怎么办呢,oplog里面的optime。看看大家的状态(数据新旧情况)

    a08f85b8-9abb-447b-a864-982086b3ffe1?imageView&thumbnail=980x0

    我等大家回复我:

    ce4b02d3-7097-45a7-8d0b-de3dc4be85d3?imageView&thumbnail=980x0

    好了,节点202回我了(他把他自己的rs.status()发给我, 看看在他的世界里这个复制集是什么情况),(200连不上),从这些信息我可以知道,我的数据是最新的。而且我从202知道200确实挂了。


    6、既然我的数据是最新的,那么我就不需要从其他节点拷贝数据了

    这里跟raft不一样,从raft的论文中,可以确定raft选为primary是必须要求数据最新的。但MongoDB选出的Primary,数据不一定要最新,只需要满足一个约定条件即可(oplog落后10s以内)。如果数据落后集群中的某个/些存活节点(这个情况一般出现在当前节点的priority比拥有更新数据的节点高的时候),在我对外提供写服务前,我先把这些数据从其他节点从抓过来,应用到我自己这里。但是我这个是有原则的,我不会那么贪婪,给我2s(catchUpTimeoutMillis)就好了。我能追上多少就追多少。如果时间到了,我还没有完全追上咋办呢,那也没有办法,让这些节点把没追上的数据回滚掉好了。


    7、现在我的数据是最新的了,我开始作为Primary对外提供写服务。你们把写请求发过来吧~~~

    也就是说,并不是成为Primary后马上就会提供写服务,而是会有个追数据的过程。我觉得这个特性如果大家么有正确理解,很容易出现问题。比如用户设置了writeconcern是majority,在主从切换的场景下,可能还未写到大多数节点的请求因为主挂了返回失败,但其实这个数据会被持久化到新主上。而严格的raft不会出现这个情况。

    以上用第一张图大体介绍了选举过程。然后每一点的仔细介绍时,我将MongoDB的SystemLog级别通过db.setLogLevel()从0设置为2,重演了一遍选举。让大家看到更多的细节。

    最后安利下,网易蜂巢MongoDB云服务已经重磅上线,蜂巢MongoDB由业界著名的数据库专家姜承尧亲自把关架构设计,免费提供售前技术支持。要知道姜大神的出台费可是业界最贵的 :),欢迎大家注册试用。有任何意见和建议,请随时提出。



    网易云免费体验馆,0成本体验20+款云产品! 

    更多网易技术、产品、运营经验分享请点击

    相关文章:
    【推荐】 流式处理框架storm浅析(上篇)
    【推荐】 视觉设计师的进化
    【推荐】 白木彰:具有普遍性的设计力

    转载于:https://www.cnblogs.com/163yun/p/9811527.html

    展开全文
  • 本人使用的是Win7的64位系统,前段时间有一个U盘不知道怎么回事,在其他的机器上都能显示,但是在自己的电脑上的计算机却显示不了盘符,进不了。反复的安装磁盘驱动吼再插入磁盘依旧如此,并且在设备管理器里wpd ...

            本人使用的是Win7的64位系统,前段时间有一个U盘不知道怎么回事,在其他的机器上都能显示,但是在自己的电脑上的计算机却显示不了盘符,进不了。反复的安装磁盘驱动吼再插入磁盘依旧如此,并且在设备管理器里wpd filesystem volume driver上面有一个黄色的感叹号。这个问题困扰了我很久,今天终于找到了解决办法。

          按照如下步骤进行操作:

          1.右键单击计算机,选择管理,进入管理页面

          2.右键单击设备管理器,你将会发现wpd filesystem volume driver上面有一个黄色的感叹号。

          3.点击磁盘驱动器,找到你不能打开的那个U盘对应的驱动,右键点击卸载。此时会发现wpd filesystem volume driver的警告会消失。

          4.回到计算机管理窗口,打开存储下的磁盘管理,此时你可以看到可移动磁盘的列表,拔插U盘,你会发现上面显示的可移动磁盘出现,但是它是没有驱动号的,所以你右击该可移动磁盘,选择更改驱动器号和路径,并且为该磁盘分配驱动器号(这里我分配的是I),这样这个盘被系统识别为I盘。

          5.返回设备管理器,拔插U盘,你会发现U盘已经能正常识别了。

                                                                                                                                                     达达的马蹄

                                                                                                                                                     2015年10月13日

    展开全文
  • 1 SparkContext哪一端生成的? Driver端即SparkContext (Driver 是一个统称,DAGSchedule ,TaskScheduler,BlockManager,ShuffeManager,...(以后从哪里数据,怎么计算) 4 调用 RDD 的算子(Transformation和Ac
  • dom读取xml文件的问题

    2009-07-20 13:12:18
    我想取出dataSource标签中driver url等值,写了一个类如下: File file = new File("data_config.xml"); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder ...
  • UVM寄存器模型

    2021-02-17 16:58:40
    为什么需要UVM寄存器模型 ...第一,我们需要在reference model里启动一个sequence,这个sequence会发送一个transaction给bus driver,把寄存器的出来。 第二,我们需要把出来的值传给reference mode
  • 大数据相关知识

    2019-01-27 16:29:56
    需要了解的内容: spark调优 hadoop2.0 基本原理 node manager、work 怎么启动一个hadoop集群 文件/数据传输到hdfs的过程 stage、job、task、driver、master、worker、... 文件(RDD格式/datafram...
  • 文章目录常见面试题jdbc连接数据库的步骤:mysql事务隔离级别:mvcc怎么解决幻读的:mysql索引的长度限制:为什么innodb必须要有主键,主键为什么要选用自增的整型?索引常见的索引模型InnoDB引擎普通索引和唯一索引...
  • 问题 1.java.lang.OutOfMemoryError: Java heap space 从报错信息看是堆溢出,为什么会堆溢出呢?logstash是读取mysql的...理解了这个,就想怎么分批,解决就在input输入插件 input { jdbc { jdbc_driver_li
  • DDT,即数据驱动测试 Data Driver Test,我曾经记录了一篇关于python的DDT框架(Excel+DDT数据驱动实例),那么java中的DDT是怎么样的呢?在java中,可以用testng的DataProvider和Excel实现。  首先建一个文档...
  • LPC基础教程-数据类型

    千次阅读 2011-03-30 23:37:00
    Lpc的Object是由零个或更多一些的被一个或一个以上函数操纵控制的变量组成的。在代码中函数排列的顺序是 不影响...在你本文时可能对编程一无所知,你可能不知道什么是 函数以及它是怎么调用的;或许你有了一些编程的
  • Oracle字符集碰到JAVA

    2015-09-28 14:16:41
    Oracle的字符集是US7ASCII,用JAVA写入中文的时候问题来了:无论是在sqlplus中还是从JAVA程序,中文不能正常显示。JAVA默认在内存中使用UNICODE编码,而数据库的字符集不能说变就变,还要存中文,怎么解决?  ...
  • 压缩包内包含文档内容: 相信我,如果你想了解PI是怎么使用的,英文官方文档吧。 PI JDBC Basics Learn How to Query PI.pdf PI-Data-Archive-2016-R2-Release-Notes.pdf PI-JDBC-Driver-2016-Administrator-Guide...
  • url = jdbc:odbc:driver={Microsoft Access Driver (*.mdb)};DBQ=" + mdbPath + ""; Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); con = DriverManager.getConnection(url); 要根据用户的选择取出数据库...
  • golang面试官:for select时,如果通道已经关闭会怎么样?如果只有一个case呢? 进阶 包管理 学go mod就够了! 优化 golang面试题:怎么避免内存逃逸? golang面试题:简单聊聊内存逃逸? 给大家丢脸了,...
  • 3、IIS下网站没有写入权限问题(站点设置问题)在windows XP+IIS 环境下ACCESS数据库只有权限的解决方法: 在站点文件夹上点右键--->共享和安全--->Web共享(安装IIS后会有此选项卡)--->设置共享位置---&...
  • Google Android SDK开发范例大全(完整版)

    热门讨论 2011-11-03 10:32:46
    如果您的家用电器由 Android 控制,并且有一个彩色触摸屏,会怎么样?如果电炉上有一个 Android UI,那么操控者甚至可以烹饪点什么东西。 在本文中,了解 Android 平台,以及如何将它用于移动和非移动应用程序。...
  • 二十三种设计模式【PDF版】

    热门讨论 2011-05-30 14:13:49
    翻译: 很多程序员在完这本书,宣布自己相当于经历了一次"主显节"(纪念那稣降生和受洗的双重节日),如果你从来没有 过这本书,你会在你的程序教育生涯里存在一个严重裂沟,所以你应该立即挽救弥补! 可以这么说:GoF ...
  • echo Please put a new disk into driver A pause goto begin 在这个例子中,驱动器 A 中磁盘上的所有文件均复制到d:back中。显示的注释提示您将另一张磁盘放入驱动器 A 时,pause 命令会使程序挂起,以便您更换...
  • 对于数据表的操作,他可以把结果转换成List,Array,Set等java集合,便于程序员操作; * 2.对于数据表的写操作,也变得很简单(只需写sql语句) * @author minuy * */ public abstract class DBUtil { //...
  • 我用的是CC3200,例程里只有读写8位地址函数,现要实现读取16位地址函数,请问怎么实现。 下面是8位地址函数: //**************************************************************************** // //! Invokes ...
  • m_strConnect.Format(TEXT("Driver={MySQL ODBC 8.0 Unicode Driver};UID=%s;PWD=%s;DataBase=%s;Persist Security Info=True;Server=%s;Port=%d;Option=3"), "hhh", "123456", "Test", "192.168.1.115", 3306...

空空如也

空空如也

1 2
收藏数 27
精华内容 10
关键字:

driver怎么读的