`
dawuafang
  • 浏览: 1105047 次
文章分类
社区版块
存档分类
最新评论

Linux驱动学习4(字符设备驱动初步学习)

 
阅读更多
一、字符设备驱动程序基础:

1、主设备号和次设备号(二者一起为设备号):
一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。
次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。linux内核中,设备号用dev_t来描述。


2.在 linux 2.6.28中定义如下:
typedef u_long dev_t;
在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。


3.可以使用下列宏从dev_t中获得主次设备号:                   
MAJOR(dev_t dev);                             
MINOR(dev_t dev);



4.也可以使用下列宏通过主次设备号生成dev_t:
MKDEV(int major,int minor);

5.看如下实现代码
#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)
#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))



6、分配设备号(两种方法):
(1)静态申请:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
用于分配已经指定的设备号,from在这里是我们定义了的,而count是从from开始,连续分配count个设备号,这里的name表示对应的设备名
这个设备名将会在 /proc/devices和sysfs中出现
(2)动态分配:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
用于动态分配设备号,其中baseminor次设备号一般指定为0,系统动态分配完了的设备号将会存在dev中


7、注销设备号:
void unregister_chrdev_region(dev_t from, unsigned count);
注销设备号,和上述提到的注册设备号相反。


8、设备文件操作函数
struct file_operations ***_ops={
 .owner =  THIS_MODULE,
 .llseek =  ***_llseek,
 .read =  ***_read,
 .write =  ***_write,
 .ioctl =  ***_ioctl,
 .open =  ***_open,
 .release = ***_release, 
 。。。  。。。
};




struct module *owner;
 /*第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针.
 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 
THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。*/




loff_t (*llseek) (struct file * filp , loff_t  p,  int  orig);
/*(指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位
的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).*/


ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t *  p);
/*(指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址),
参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值)
这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败.
 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).*/


ssize_t (*aio_read)(struct kiocb *  , char __user *  buffer, size_t  size ,  loff_t   p);
/*可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的,
异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。
异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体);
初始化一个异步读 -- 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).
(有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)*/


ssize_t (*write) (struct file *  filp, const char __user *   buffer, size_t  count, loff_t * ppos);
/*(参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,
ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界)
发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
(注:这个操作和上面的对文件进行读的操作均为阻塞操作)*/


ssize_t (*aio_write)(struct kiocb *, const char __user *  buffer, size_t  count, loff_t * ppos);
/*初始化设备上的一个异步写.参数类型同aio_read()函数;*/


int (*readdir) (struct file *  filp, void *, filldir_t);
/*对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.*/


unsigned int (*poll) (struct file *, struct poll_table_struct *);
/*(这是一个设备驱动中的轮询函数,第一个参数为file结构指针,第二个为轮询表指针)
这个函数返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位“或”结果。
每个宏都表明设备的一种状态,如:POLLIN(定义为0x0001)意味着设备可以无阻塞的读,POLLOUT(定义为0x0004)意味着设备可以无阻塞的写。
(poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞.
 poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 
如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写.
(这里通常将设备看作一个文件进行相关的操作,而轮询操作的取值直接关系到设备的响应情况,可以是阻塞操作结果,同时也可以是非阻塞操作结果)*/


int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
/*(inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.
cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.
如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的.
因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.)
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.
 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.*/


int (*mmap) (struct file *, struct vm_area_struct *);
/*mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
(如果想对这个函数有个彻底的了解,那么请看有关“进程地址空间”介绍的书籍)*/


int (*open) (struct inode * inode , struct file *  filp ) ;
/*(inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构;
但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息)
 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.
与open()函数对应的是release()函数。*/


int (*flush) (struct file *);
/*flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作.
这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用;
 SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.*/


int (*release) (struct inode *, struct file *);
/*release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:
void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。
    在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.*/


int(*synch)(struct file *,struct dentry *,int datasync);
//刷新待处理的数据,允许进程把所有的脏缓冲区刷新到磁盘。




int (*aio_fsync)(struct kiocb *, int);
 /*这是 fsync 方法的异步版本.所谓的fsync方法是一个系统调用函数。系统调用fsync
把文件所指定的文件的所有脏缓冲区写到磁盘中(如果需要,还包括存有索引节点的缓冲区)。
相应的服务例程获得文件对象的地址,并随后调用fsync方法。通常这个方法以调用函数__writeback_single_inode()结束,
这个函数把与被选中的索引节点相关的脏页和索引节点本身都写回磁盘。*/


int (*fasync) (int, struct file *, int);
//这个函数是系统支持异步通知的设备驱动,下面是这个函数的模板:


static int ***_fasync(int fd,struct file *filp,int mode)
{
    struct ***_dev * dev=filp->private_data;
    return fasync_helper(fd,filp,mode,&dev->async_queue);//第四个参数为 fasync_struct结构体指针的指针。
//这个函数是用来处理FASYNC标志的函数。(FASYNC:表示兼容BSD的fcntl同步操作)当这个标志改变时,驱动程序中的fasync()函数将得到执行。
}
/*此操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述.
这个成员可以是NULL 如果驱动不支持异步通知.*/


int (*lock) (struct file *, int, struct file_lock *);
//lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.


ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
/*这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作;
 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).*/


ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
/*这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个.
例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.*/


ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
/*sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.*/


unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
/*这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中.
这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]*/


int (*check_flags)(int)
//这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.


int (*dir_notify)(struct file *, unsigned long);
//这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.




二、字符驱动程序设计
1.为某设备申请设备号;
此时的结果是:得到设备名为name,对应的设备号dev_num。(这里只讨论分配一个设备号的情况)


2.为设备结构体申请内存并且初始化该部分内存
此时的结果是:获得了一个设备结构体的指针:mem_op_devp(该指针已经进行了初始化)


3.向内核注册上述申请到的设备结构体指针 mem_op_devp
此时的结果是:
初始化了设备结构体中的变量,对结构体中的变量加以填充,对其中嵌入的结构体也进行相应的初始化
将结构体设备mem_op_devp -> cdev添加入内核,执行此步骤后,系统调用将生效。


4.设备操作的实现
特别注意:驱动程序应用程序的数据交换:
 驱动程序和应用程序的数据交换是非常重要的。file_operations中的read()和write()函数,就是用来在驱动程序和应用程序间交换数据的。
通过数据交换,驱动程序和应用程序可以彼此了解对方的情况。但是驱动程序和应用程序属于不同的地址空间。驱动程序不能直接访问应用程
序的地址空间;同样应用程序也不能直接访问驱动程序的地址空间,否则会破坏彼此空间中的数据,从而造成系统崩溃,或者数据损坏。安全
的方法是使用内核提供的专用函数,完成数据在应用程序空间和驱动程序空间的交换。这些函数对用户程序传过来的指针进行了严格的检查和
必要的转换,从而保证用户程序与驱动程序交换数据的安全性。这些函数有:


unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
put_user(local,user);
get_user(local,user);


3.设备注销:void cdev_del(struct cdev *p);


注意:
/*1. 初始化cdev,绑定设备和文件操作函数*/
  cdev_init(&dev->cdev, &mem_op_fops);
  dev->cdev.owner = THIS_MODULE;
  /*2. 为文件操作提供具体实现方法*/
  dev->cdev.ops = &mem_op_fops;
  /*3. 添加该cdev至内核*/
注意:在cdev_init中也有  dev->cdev.ops = &mem_op_fops;类似的操作,为什么呢?
我认为  dev->cdev.ops = &mem_op_fops;只是为了出现动态给cdev分配内存的情况,因为动态分配的时候是不存在
dev->cdev.ops = &mem_op_fops;这个操作的。




下面为一个简单字符设备的例子:
源文件 mem_pool.c
/*======================================================================
    A mem_pool driver as an example of char device drivers  


======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>


#define mem_pool_SIZE	0x2000	/*全局内存最大8K字节*/
#define MEM_CLEAR 0x1  		/*清0全局内存*/
#define mem_pool_MAJOR 	250    	/*预设的mem_pool的主设备号*/
	
static mem_pool_major = mem_pool_MAJOR;
/*mem_pool设备结构体*/
struct mem_pool_dev                                     
{                                                        
  struct cdev cdev; 		    /*cdev结构体*/                       
  unsigned char mem[mem_pool_SIZE]; /*全局内存*/        
};
/*设备结构体指针*/
struct mem_pool_dev *mem_pool_devp; 
/*文件打开函数*/
int mem_pool_open(struct inode *inode, struct file *filp)
{
  /*将设备结构体指针赋值给文件私有数据指针*/
  filp->private_data = mem_pool_devp;
  return 0;
}
/*文件释放函数*/
int mem_pool_release(struct inode *inode, struct file *filp)
{
  return 0;
}


/* ioctl设备控制函数 */
static int mem_pool_ioctl(struct inode *inodep, struct file *filp, unsigned
  int cmd, unsigned long arg)
{
	/*获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data;


  switch (cmd)
  {
    case MEM_CLEAR:
      memset(dev->mem, 0, mem_pool_SIZE);      
      printk( "mem_pool is set to zero\n");
      break;


    default:
      return  - EINVAL;
  }
  return 0;
}


/*读函数*/
static ssize_t mem_pool_read(struct file *filp, char __user *buf, size_t size,
  loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data; 


  /*2. 分析和获取有效的写长度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;


  /*3. 内核空间->用户空间*/
  if (copy_to_user(buf, (void*)(dev->mem + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
    
    printk( "read %d bytes(s) from %d\n", count, p);
  }


  return ret;
}


/*写函数*/
static ssize_t mem_pool_write(struct file *filp, const char __user *buf,
  size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 获得设备结构体指针*/
  struct mem_pool_dev *dev = filp->private_data; 
  
  /*2. 分析和获取有效的写长度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
    
  /*3. 用户空间->内核空间*/
  if (copy_from_user(dev->mem + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk( "written %d bytes(s) from %d\n", count, p);
  }


  return ret;
}


/* seek文件定位函数 */
static loff_t mem_pool_llseek(struct file *filp, loff_t offset, int orig)
{
  loff_t ret = 0;
  switch (orig)
  {
    case 0:   /*相对文件开始位置偏移*/
      if (offset < 0)
      {
        ret =  - EINVAL;
        break;
      }
      if ((unsigned int)offset > mem_pool_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos = (unsigned int)offset;
      ret = filp->f_pos;
      break;
    case 1:   /*相对文件当前位置偏移*/
      if ((filp->f_pos + offset) > mem_pool_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      if ((filp->f_pos + offset) < 0)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos += offset;
      ret = filp->f_pos;
      break;
    default:
      ret =  - EINVAL;
      break;
  }
  return ret;
}


/*文件操作结构体*/
static const struct file_operations mem_pool_fops =
{
  .owner = THIS_MODULE,
  .llseek = mem_pool_llseek,
  .read = mem_pool_read,
  .write = mem_pool_write,
  .ioctl = mem_pool_ioctl,
  .open = mem_pool_open,
  .release = mem_pool_release,
};


/*初始化并注册cdev*/
static void mem_pool_setup_cdev(struct mem_pool_dev *dev, dev_t devno)
{
  int err;
  /*1. 初始化cdev,绑定设备和文件操作函数*/
  cdev_init(&dev->cdev, &mem_pool_fops);
  dev->cdev.owner = THIS_MODULE;
  /*2. 为文件操作提供具体实现方法*/
  dev->cdev.ops = &mem_pool_fops;
  /*3. 添加该cdev至内核*/
  err = cdev_add(&dev->cdev, devno, 1);
  if (err)
    printk( "Error %d adding LED%d", err, devno);
}


/*设备驱动模块加载函数*/
int mem_pool_init(void)
{
  int result;
  dev_t devno = MKDEV(mem_pool_major, 0);


  printk( "mem_pool_init !\n");
  /*1. 申请设备号*/
  if (mem_pool_major)
    result = register_chrdev_region(devno, 1, "mem_pool");
  else  
  {
	/*2. 动态申请设备号 */  	
    result = alloc_chrdev_region(&devno, 0, 1, "mem_pool");
    mem_pool_major = MAJOR(devno);
  }  
  if (result < 0)
    return result;
    
  /*3. 动态申请设备结构体的内存*/
  mem_pool_devp = kmalloc(sizeof(struct mem_pool_dev), GFP_KERNEL);
  /*4. 申请失败*/
  if (!mem_pool_devp)    
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  /*5. 内存初始化*/
  memset(mem_pool_devp, 0, sizeof(struct mem_pool_dev));
  /*6. 注册初始化设备*/
  mem_pool_setup_cdev(mem_pool_devp, devno);
  return 0;


  fail_malloc: unregister_chrdev_region(devno, 1);
  return result;
}


/*模块卸载函数*/
void mem_pool_exit(void)
{
  printk( "mem_pool_exit !\n");
	/*1. 注销cdev*/
  cdev_del(&mem_pool_devp->cdev);
	/*2. 释放设备结构体内存*/   
  kfree(mem_pool_devp);     
  /*3. 释放设备号*/
  unregister_chrdev_region(MKDEV(mem_pool_major, 0), 1); 
}


MODULE_AUTHOR("xb Deng");
MODULE_LICENSE("Dual BSD/GPL");


module_param(mem_pool_major, int, S_IRUGO);


module_init(mem_pool_init);
module_exit(mem_pool_exit);




对应此文件的makefile


obj-m:=mem_pool.o
KDIR:=/lib/modules/2.6.32-38-generic/build
PWD:=$(shell pwd)


default:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.o Module* module* *.mod.c 



关于makefile文件的解释
1.obj-m:=mem_pool.o用于指定目标模块
2.KDIR:=/lib/modules/2.6.32-38-generic/build 这是内核源文件的编译目录
3. $(MAKE) -C $(KDIR) M=$(PWD) modules 表示先进入到KDIR目录下执行makefile 然后会转到当前路径执行makefile
从而实现把内核和模块链接起来




对应的测试app


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>


int main(void)
{
	int fd,i;
	int data;
	fd = open("/sys/module/mem_pool", O_RDONLY);/*打开/dev/second设备文件*/
	if (fd < 0)
	{	
		printf("open /dev/borytest error\n");
	} else 
	{
		printf("open /dev/borytest success\n");
	}
	getchar();
	close(fd);
}



注意此时在/dev下面没有生成设备节点,所以不能直接操作



分享到:
评论

相关推荐

    Linux下支持阻塞操作的字符设备驱动

    Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动Linux下支持阻塞操作的字符设备驱动...

    Linux字符设备驱动总结

    linux 字符设备驱动 字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘,打印机等。请注意,以字符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2个字节。  在...

    linux字符设备驱动实例

    一个虚拟的linux字符设备驱动实例,包括对/sys, 及/dev下设备文件的自动生成

    Linux设备驱动程序学习(1)-字符设备驱动程序 - Linux设备驱动程序

    Linux设备驱动程序学习(1)-字符设备驱动程序 - Linux设备驱动程序

    linux字符设备驱动模型

    linux字符设备驱动模型

    嵌入式Linux下字符型设备驱动程序的开发

    嵌入式Linux下字符型设备驱动程序的开发,驱动开发入门首选~

    最简单的linux字符设备驱动

    一个最简单的字符设备驱动程序,包括LDD第三版前三章的内容。 关键是书中并未讲的太细,关于mknod以及如何自己写一个程序使用自己的驱动,我的代码中有详细的过程,也在blog中写明了驱动模块的思路以及常见问题的...

    基于linux系统的字符设备驱动研究与设计.pdf

    基于linux系统的字符设备驱动研究与设计.pdf

    Linux字符设备驱动实验代码

    简单的字符设备的驱动程序,并对所编写的设备驱动程序进行测试,了解Linux操作系统如何管理字符设备。由于网上许多资源不完整,本资源整合了许多内容。包括驱动程序memdev.c,memdev.h,app-mem.c,MakeFile文件。...

    s3c2440基于linux的gpio led字符设备驱动实践

    s3c2440基于linux的gpio led字符设备驱动实践

    linux字符设备驱动程序学习笔记

    详细介绍了linux字符设备驱动程序,对各个名词做了自己的理解,在学习中的笔记,有错误还请海涵

    Linux字符设备驱动实现

    编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序。可以有一个读,一个写进程共享该字符设备,进行聊天;也可以由多个读和多个写进程共享该字符设备,进行聊天

    Linux设备驱动程序学习

    ·Linux设备驱动程序学习(4)-高级字符驱动程序操作[(1)ioctl and llseek] ·Linux设备驱动程序学习(5)-高级字符驱动程序操作[(2)阻塞型I/O和休眠] ·Linux设备驱动程序学习(6)-高级字符驱动程序操作...

    Linux 驱动学习笔记pdf文档

    ·Linux设备驱动程序学习(4)-高级字符驱动程序操作[(1)ioctl and llseek] ·Linux设备驱动程序学习(5)-高级字符驱动程序操作[(2)阻塞型I/O和休眠] ·Linux设备驱动程序学习(6)-高级字符驱动程序操作...

    Linux字符设备驱动(转载)

    概括的说,字符设备驱动主要要做三件事:1、定义一个结构体static struct file_operations变量,其内定义一些设备的打开、关闭、读、写、控制函数;2、在结构体外分别实现结构体中定义的这些函数;3、向内核中注册或...

    LINUX驱动开发 字符驱动实例

    LINUX驱动开发 字符驱动实例LINUX驱动开发 字符驱动实例

    Linux增加字符设备驱动实验

    Linux增加字符设备驱动实验,可以打印出一个hello world

    Linux设备驱动程序学习(1)-字符设备驱动程序

    主要通过介绍字符设备scull(Simple Character Utility for Loading Localities,区域装载的简单字符工具)的驱动程序编写,来学习Linux设备驱动的基本知识。scull可以为真正的设备驱动程序提供样板。

    基于Linux字符设备驱动程序的设计与实现

    Linux 设备驱动程序是为特定的硬件提供给用户程序的 一组标准化接口,它隐藏了设备工作的细节。Linux 系统下 驱动程序是运行在内核态的,是和内核连接在一起的程序。 如果运行在用户态的应用程序想控制硬件设备,...

Global site tag (gtag.js) - Google Analytics