《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO

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 0;
}

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==0)
    {
        // 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 < 0 )
            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)< 0 )
            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)< 0 )
            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 0;
}

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,0,1,DEV_NAME))<0 )
    {
        printk("alloc_chrdev_region err.\r\n");
        return ret;
    }
    ret = cdev_add(&globalmem_dev.cdev,globalmem_dev.dev_no,1);
    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,0,GLOBALMEN_LEN);

    init_waitqueue_head(&globalmem_dev.r_wait_head);
    init_waitqueue_head(&globalmem_dev.w_wait_head);
    mutex_init(&globalmem_dev.mutex);

    return 0;
}

static void __exit globalmem_exit( void )
{
    printk("enter globalmem_exit()\r\n");
    unregister_chrdev_region(globalmem_dev.dev_no, 1);
    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 &
:[1] 8881
~$
success
nb

终端2:
sudo echo "success" > /dev/globalmem
bash: /dev/globalmem: 权限不够
:~$ sudo su                 // 必须切换到root,否则没有权限写
[sudo] 密码:
[email protected]***# echo ‘success‘ > /dev/globalmem
[email protected]***# 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=0;
    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 != 0 )
        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<0 )
    {
        printf("\r\nopen file err");
        exit(-1);
    }

    while(1)
    {
        FD_ZERO(&rd_fd_set);
        FD_ZERO(&wr_fd_set);
        FD_SET(fd,&rd_fd_set);
        FD_SET(fd,&wr_fd_set);

        select(fd+1,&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(0);
}
终端1执行应用程序:1.初始状态,只显示可写
Poll monitor:can be write
.....

2.执行命令1后,可读可写
Poll monitor:can be write
Poll monitor:can be read
....
3.执行命令2后,又变回可写,不可读了
Poll monitor:can be write
.....
终端2读写globalmem
# echo "test" > /dev/globalmem   // 命令1
# cat  /dev/globalmem         // 命令2
test

8.3 总结

时间: 2024-08-27 19:13:38

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

linux设备驱动开发详解 笔记

  在目录的 Makefile 中关于 RTC_DRV_S3C 的编译脚本为: obj -$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o 上述脚本意味着如果 RTC_DRV_S3C 配置选项被选择为"Y" 或"M",即 obj-$(CONFIG_RTC_ DRV_S3C)等同于 obj-y 或 obj-m 时,则编译 rtc-s3c.c,选" Y" 的情况直接会将生成的目标代码 直接连接到内核,为" M"

《Linux设备驱动开发详解(第3版)》海量更新总结

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2015.2.26 几乎完成初稿. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第1章 <Linux设备驱动概述及开发环境构建>[D]删除关于LDD6410开发板的介绍[F]更新新的Ubuntu虚拟机[N]添加关于QEMU模拟vexpress板的描述 第2章 <驱动设计的硬件基础> [N]增加关于SoC的介绍:[N]增加关于eFuse的内容:[D]删除ISA总线的内容了:[N]增加关于SP

《Linux设备驱动开发详解(第3版)》进展同步更新

本博实时更新<Linux设备驱动开发详解(第3版)>的最新进展. 2014.6.30 目前初步完成4-9章,相对于第2版,这几章主要的变更. [F]是修正或升级:[N]是新增知识点:[D]是删除的内容 第4章 <Linux内核模块>[F]改正关于模块使用非GPL license的问题:[F]修正关于__exit修饰函数的内存管理 第5章 <Linux文件系统与设备文件>[F]修正关于文件系统与块设备驱动关系图:[N]增加应用到驱动的file操作调用图:[N]增加通过ne

《Linux设备驱动开发详解:基于最新的Linux 4.0内核》china-pub预售

<Linux设备驱动开发详解:基于最新的Linux 4.0内核>china-pub今日上线进入预售阶段: http://product.china-pub.com/4733972 推荐序一 技术日新月异,产业斗转星移,滚滚红尘,消逝的事物太多,新事物的诞生也更迅猛.众多新生事物如灿烂烟花,转瞬即逝.当我们仰望星空时,在浩如烟海的专业名词中寻找,赫然发现,Linux的生命力之旺盛顽强,斗志之昂扬雄壮,令人称奇.它正以摧枯拉朽之势迅速占领包括服务器.云计算.消费电子.工业控制.仪器仪表.导航娱乐等

《Linux设备驱动开发详解:基于最新的Linux 4.0内核》china-pub 预售

<Linux设备驱动开发详解:基于最新的Linux 4.0内核>china-pub今日上线进入预售阶段: http://product.china-pub.com/4733972 推荐序一 技术日新月异,产业斗转星移,滚滚红尘,消逝的事物太多,新事物的诞生也更迅猛.众多新生事物如灿烂烟花,转瞬即逝.当我们仰望星空时,在浩如烟海的专业名词中寻找,赫然发现,Linux的生命力之旺盛顽强,斗志之昂扬雄壮,令人称奇.它正以摧枯拉朽之势迅速占领包括服务器.云计算.消费电子.工业控制.仪器仪表.导航娱乐等

《Linux设备驱动开发详解(基于最新4.0内核)》前言

Linux从未停歇脚步.Linus Torvalds,世界上最伟大的程序员之一,Linux内核的创始人,Git的缔造者,仍然在没日没夜的合并补丁,升级内核.做技术,从来没有终南捷径,拼的就是坐冷板凳的傻劲. 这是一个连阅读都被碎片化的时代,在这样一个时代,人们趋向于激进.浮躁.内心的不安宁使我们极难静下心来研究什么.我见过许许多多的Linux工程师,他们的简历书写着"精通"Linux内核,有多年的工作经验,而他们的"精通"却只是把某个寄存器从0改成1,从1改成0的不

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

14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx();  注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用. 网络设备接口层,net_device,统一接口名称,使上层独立于具体硬件. 设备驱动功能层,实现net_device的各成员 物理层 在整个以太网架构里,有两个数据结构非常重要,即sk_buff和net_device,后面两节有说明. 还有一些与内核交互的函数,需要掌握,如netif_start

LINUX设备驱动开发详解----第一篇随笔

1,软件的设计宗旨呢,是高内聚,低耦合.其意思是一个驱动程序里面,尽量是自己把事情都干完,别跟其他模块或驱动牵扯太多.不然出问题的时候,就不好排查,当然这样也利于移植,只要搞清楚了驱动程序里面的代码,那换个平台,也变得轻松. 2,驱动是沟通硬件和应用的桥梁.无操作系统下的驱动一般是由一个h文件和c文件组成.h文件里面是一些结构体的定义以及提供给外部调用的函数声明,c文件就是这些函数的具体实现.无操作系统的软件结构是这样的:一个无限循环里,对设备中断的检测或轮询,并处理. 应用工程师在写代码的时候

《Linux4.0设备驱动开发详解》笔记--第十五章:Linux I2C核心、总线与设备驱动

15.1 Linux I2C体系结构 I2C核心 I2C核心提供了I2C总线驱动和设备驱动的注册.注销的方法,I2C通信(Algorithm)方法上层的与具体适配器无关代码以及探测设备.检测设备地址的上层代码等 I2C总线驱动 是对I2C体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部 总线驱动包含I2C适配器数据结构i2c_adapter.I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数 I2C设备驱动 它是对I