linux高级字符设备驱动以及实例(TQ2440)

Ioctl相关:

应用程序即用户空间中,ioctl 原型如下:int ioctl(int fd,unsignedlong cmd,...)

设备驱动方法中:int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)

在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。ioctl 命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小。Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数。

定义 ioctl 命令的正确方法是使用 4 个位段, 这个列表中介绍的符号定义在<linux/ioctl.h>中:

Type
幻数(类型): 表明哪个设备的命令,在参考了 ioctlnumber.txt之后选出,8 位宽。

Number
序号,表明设备命令中的第几个,8 位宽。

Direction
数据传送的方向,可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE。 数据传送是从应用程序的观点来看待的,_IOC_READ 意思是从设备读。

Size
用户数据的大小。(13/14位宽,视处理器而定)

#define _IOC(dir,type,nr,size) ((unsigned int) (((dir)  << _IOC_DIRSHIFT) | ((type) << _IOC_TYPESHIFT) | ((nr)   << _IOC_NRSHIFT) | ((size) << _IOC_SIZESHIFT)))

内核提供了下列宏来帮助定义命令: 

/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)    只有幻数和序号的命令
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))    读命令
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))  写命令
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))   双向传送,type 和 number 成员作为参数被传递。。。到底是读还是写。。。

以上可以看出幻数和序号是必须的,幻数用来区分不同的设备驱动,序号用来区分不同的命令。

但是实际上应用程序中的cmd就是设备方法中的cmd,只要不担心和其他冲突,完全可以不用管以上的什么幻数和序号这种规则,只要对应好就好。设备方法中的arg就是应用程序中的第三个参数。

以下是和create numbers相对应的decode

/* used to decode them.. */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)   提取方向_IOC_NONE、_IOC_READ或者_IOC_WRITE,占2bit或者3bit
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)   提取类型(幻数),自己DEFINE,参考ioctl-number.txt未被定义的,定义后基本保持不变,占8bit
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)   提取序号,自己定,占8bit
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)   提取尺寸,自己定,占13bit或者14bit

以上总共32bit。

使用范例:

#define MEM_IOC_MAGIC ‘m’ //定义幻数
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC, 0,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)

Ioctl函数实现(返回值)
Ioctl函数的实现通常是根据命令执行的一个switch语句。但是,当命令号不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)(在驱动程序中自己编写)。

如何使用Ioctl中的参数:如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查。

参数检查函数:

int access_ok(int type, const void *addr, unsigned long size);

第一个参数是 VERIFY_READ 或者 VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr 参数是要操作的用户内存地址,size 是操作的长度。如果 ioctl 需要从用户空间读一个整数,那么size参数等于 sizeof(int)。access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题),如果该函数返回失败, 则Ioctl应当返回–EFAULT 。

以下函数用于读写用户空间数据,其中__get_user和__put_user需要上述检查函数调用后再调用,其它函数自带检查:

copy_from_user 
copy_to_user 
get_user
put_user
__get_user
__put_user

poll应用和相关知识点:

内核等待队列:

在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当唤醒进程时,从等待等列中取出进程。

Linux 2.6内核提供了如下关于等待队列的操作:
1、定义等待队列
wait_queue_head_t my_queue
2、初始化等待队列
init_waitqueue_head(&my_queue)
3、定义并初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)

4、有条件睡眠

wait_event(queue,condition)

当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
wait_event_interruptible(queue,condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。

int wait_event_killable(wait_queue_t queue, condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。

5、无条件睡眠(老本,建议不再使用)
sleep_on(wait_queue_head_t*q)
让进程进入不可中断的睡眠,并把它放入等待队列q。
interruptible_sleep_on(wait_queue_head_t *q)
让进程进入可中断的睡眠,并把它放入等待队列q。

6、从等待队列中唤醒进程
wake_up(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。
wake_up_interruptible(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程。

可能会有多个进程使用同一个等待队列,也就是这些进程都在同一个等待队列中,等待同一个资源,这个资源可以被使用后会唤醒等待队列中的所有进程,至于是哪个进程先调用,那就看优先级的高低,(当然等待队列中的进程也可能会被各种中断唤醒)。

阻塞型字符设备驱动:

阻塞方式:

在阻塞型驱动程序中,Read实现方式如下:
如果进程调用read,但设备没有数据或数据不足,进程阻塞。当新数据到达后,唤醒被阻塞进程。

在阻塞型驱动程序中,Write实现方式如下:
如果进程调用了write,但设备没有足够的空间供其写入数据,进程阻塞。当设备中的数据被读走后,缓冲区中空出部分空间,则唤醒进程。

非阻塞方式:

阻塞方式是文件读写操作的默认方式,但应用程序员可通过使用O_NONBLOCK标志来人为的设置读写操作为非阻塞方式(该标志定义在<linux/fcntl.h>中,在打开文件时指定)。如果设置了O_NONBLOCK标志,read和write的行为是不同的。如果进程在没有数据就绪时调用了read,或者在缓冲区没有空间时调用了write,系统只是简单地返回-EAGAIN,而不会阻塞进程。

在内核驱动的设备方法中会对于是否是O_NONBLOCK在决定是否让其阻塞(读取filp->f_flags的值)。

select系统调用:

Select系统调用用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程。

int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct  timeval *timeout)

Maxfd:文件描述符的范围,比待检测的最大文件描述符大1
Readfds:被读监控的文件描述符集
Writefds:被写监控的文件描述符集
Exceptfds:被异常监控的文件描述符集
Timeout:定时器

Timeout取不同的值,该调用有不同的表现:
Timeout值为0,不管是否有文件满足要求,都立刻返回,无文件满足要求返回0,有文件满足要求返回一个正值。
Timeout为NULL,select将阻塞进程,直到某个文件满足要求
Timeout 值为正整数,就是等待 的 最长 时 间 , 即select在timeout时间内阻塞进程。

Select调用返回时,返回值有如下情况:
1 . 正常情况下返回满足要求的文件描述符个数;
2. 经过了timeout等待后仍无文件满足要求,返回值为0;
3. 如果select被某个信号中断,它将返回-1并设置errno为EINTR。
4. 如果出错,返回-1并设置相应的errno。

Select系统调用(使用方法):
1. 将要监控的文件添加到文件描述符集
2. 调用Select开始监控
3. 判断文件是否发生变化

系统提供了4个宏对描述符集进行操作:
#include <sys/select.h> 
void FD_SET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
void FD_ISSET(int fd, fd_set *fdset)
宏FD_SET将文件描述符fd添加到文件描述符集fdset中;
宏FD_CLR从文件描述符集fdset中清除文件描述符fd;
宏FD_ZERO清空文件描述符集fdset;
在调用select后使用FD_ISSET来检测文件描述符集fdset中的文件fd发生了变化。
例程:

FD_ZERO(&fds);//清空集合
FD_SET(fd1,&fds);//设置描述符
FD_SET(fd2,&fds);//设置描述符
maxfdp=fd1+1; //描述符最大值加1,假设fd1>fd2
switch(select(maxfdp,&fds,NULL,NULL,&timeout)) 
case -1: exit(-1);break;//select错误,退出程序
case 0:break; 
default: 
if(FD_ISSET(fd1,&fds))//测试fd1是否可读

Poll方法
应用程序常常使用select系统调用,它可能会阻塞进程。这个调用由驱动的 poll 方法实现,原型为:
unsignedint (*poll)(struct file *filp,poll_table *wait)

Poll设备方法负责完成:
1.  使用poll_wait将等待队列添加到poll_table中。
2.  返回描述设备是否可读或可写的掩码。

位掩码
POLLIN 设备可读
POLLRDNORM 数据可读
POLLOUT 设备可写
POLLWRNORM 数据可写
设备可读通常返回(POLLIN|POLLRDNORM)
设备可写通常返回(POLLOUT|POLLWRNORM)
范例
static unsigned int mem_poll(struct file *filp,poll_table *wait)
{
struct scull_pipe *dev=filp->private_data;
unsigned int mask=0;
/* 把进程添加到等待队列 */
poll_wait(filp,&dev->inq,wait);
/*返回掩码*/
if(有数据可读) 
mask = POLLIN |POLLRDNORM;/*设备可读*/
return mask;
}
工作原理
Poll方法只是做一个登记,真正的阻塞发生在select.c 中的 do_select函数。

自动创建节点文件:

自动创建(2.4内核)
devfs_register (devfs_handle_t dir,const char *name,unsignedint flags,unsignedint major,unsignedint minor,umode_t mode,void *ops, void *info)
在指定的目录中创建设备文件。dir:目录名,为空表示在/dev/目录下创建;name:文件名;flags:创建标志;major:主设备号;minor:次设备号;
mode:创建模式;ops:操作函数集;info:通常为空

自动创建(2.6内核)
从Linux 2.6.13开始,devfs不复存在,udev成为devfs的替代。相比devfs,udev(mdev)存在于应用层。利用udev(mdev)来实现设备文件的自动创建很简单,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 device_create创建对应的设备。

例:
struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);
当驱动被加载时,udev( mdev)就会自动在/dev下创建my_device设备文件。

在加载驱动模块的时候之前要运行 mdev -s ,这是busybox集成的命令。这样就可以自动创建了,当然不管设备号是静态申请还是动态申请都是可以的。

关于device_create函数在2.6内核中变化较大,

如2.6.29这样struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...),

如2.25.8这样struct device *device_create(struct class *class, struct device *parent,dev_t devt, const char *fmt, ...)。

mmap的设备方法:

用户空间函数:

mmap函数:

void* mmap ( void * addr, size_tlen, int prot, int flags , int fd, off_t offset);//内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。

addr:指定映射的起始地址, 通常设为NULL, 由系统指定。
length:映射到内存的文件长度。
prot:映射区的保护方式, 可以是: 
PROT_EXEC: 映射区可被执行
PROT_READ: 映射区可被读取
PROT_WRITE: 映射区可被写入

flags: 映射区的特性, 可以是: 
MAP_SHARED:写入映射区的数据会复制回文件,  且允许其他映射该文件的进程共享。
MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回原文件。

fd: 由open返回的文件描述符, 代表要映射的文件。
offset:以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。

解除映射munmap:

int munmap(void *start,size_tlength);

功能:
取消参数start所指向的映射内存,参数length表示欲取消的内存大小。
返回值:
解除成功返回0,否则返回-1,错误原因存于errno中。

关于虚拟内存区域的介绍:

虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。一个进程的内存映象由下面几部分组成:程序代码、数据、BSS和栈区域,以及内存映射的区域。

一个进程的内存区域可以通过/proc/pid/maps 查看:
08048000-0804f000 r-xp 00000000 08:01 573748 /sbin/rpc.statd   #text
0804f000-08050000 rw-p 00007000 08:01 573748 /sbin/rpc.statd  #data
08050000-08055000 rwxp 00000000 00:00 0                                       #bss
040000000-40015000 r-xp 00000000 08:01 933965/lib/ld2.3.2.so  #text
40015000-40016000 rw-p 00014000 08:01 933965 /lib/ld-2.3.2.so #data

每一行的域为:
start end permoffset major:minor inode
Start: 该区域起始虚拟地址
End:   该区域结束虚拟地址
Perm:  读、写和执行权限;表示对这个区域,允许进程做什么。这个域的最后一个字符要么是p表示私有的,要么是s表示共享的。
Offset: 被映射部分在文件中的起始地址
Major、minor:主次设备号
Inode:索引结点

vm_area_struct

Linux内核使用结构vm_area_struct(<linux/mm_types.h>)来描述虚拟内存区域,其中几个主要成员如下:
unsignedlong vm_start虚拟内存区域起始地址
unsignedlong vm_end虚拟内存区域结束地址

unsignedlong vm_flags该区域的标记。如:VM_IO和VM_RESERVED。VM_IO 将该 VMA 标记为内存映射的IO区域 ,VM_IO会阻止系统将该区域包含在进程的存放转存(core  dump)中,VM_RESERVED标志内存区域不能被换出。

mmap设备操作:映射一个设备是指把用户空间的一段地址关联到设备内存上。当程序读写这段用户空间的地址时,它实际上是在访问设备。

mmap设备方法需要完成什么功能?
mmap方法是file_oprations结构的成员,在mmap系统调用发出时被调用。在此之前,内核已经完成了很多工作。mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表。
int (*mmap) (struct file *, struct vm_area_struct *)

mmap如何完成页表的建立?
方法一、使用remap_pfn_range一次建立所有页表;
方法二、使用nopage VMA方法每次建立一个页表。

构造页表的工作可由remap_pfn_range函数完成,原型如下:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsignedlong size, pgprot_t prot)

vma: 虚拟内存区域指针
virt_addr: 虚拟地址的起始值
pfn:要映射的物理地址所在的物理页帧号,可将物理地址>>PAGE_SHIFT得到。
size:要映射的区域的大小。
prot:VMA的保护属性。

mmap设备操作
int memdev_mmap(struct file*filp, struct vm_area_struct *vma)
{
Vma->vm_flags |= VM_IO;
Vma->vm_flags |= VM_RESERVED;
if (remap_pfn_range(vma, vma->vm_start, (dev- >data)>> PAGE_SHIFT, size, vma->vm_page_prot))return  -EAGAIN;
return 0;
} 相关链接LED控制实例


寄存器与内存的区别:
寄存器和 RAM 的主要不同在于寄存器操作有副作用(side effect 或 边际效果):
读取某个地址时可能导致该地址内容发生变化,比如很多设备的中断状态寄存器只要一读取,便自动清零。

内存与I/O
在X86处器中存在I/O空间的概念,I/O空间是相对内存空间而言的,他们是彼此独立的地址空间,在32位的x86系统中,I/O空间大小为64K,内存空间大小为4G。

X86
支持内存空间、IO空间
ARM
只支持内存空间
MIPS
只支持内存空间
PowerPC
只支持内存空间

IO端口:
当一个寄存器或内存位于IO空间时,称其
为IO端口。
IO内存:
当一个寄存器或内存位于内存空间时,称
其为IO内存。

操作I/O端口:

对I/O端口的操作需按如下步骤完成:
1. 申请
2. 访问
3. 释放

申请I/O端口:

内核提供了一套函数来允许驱动申请它需要的I/O端口,其中核心的函数是:
struct resource*request_region(unsignedlong first, unsignedlong n, const char *name)
这个函数告诉内核,你要使用从 first 开始的n个端口,name参数是设备的名字。如果申请成功,返回非 NULL,申请失败,返回 NULL。

系统中端口的分配情况记录在/proc/ioports 中(展示)。如果不能分配需要的端口,可以来这里查看谁在使用。

访问I/O端口:

I/O端口可分为8-位, 16-位, 和 32-位端口。Linux 内核头文件(体系依赖的头文件 <asm/io.h>) 定义了下列内联函数来访问 I/O 端口:
• unsigned inb(unsigned port) 读字节端口( 8 位宽 )
• void outb(unsignedchar byte, unsigned port) 写字节端口( 8 位宽 )。

unsigned inw (unsigned port)
void outw(unsignedshort word, unsigned port)
读写 16-位 端口。
unsigned inl(unsigned port)
void outl(unsigned longword, unsigned port)
读写 32-位 端口。

释放I/O端口:
当用完一组 I/O 端口(通常在驱动卸载时),应使用如下函数把它们返还给系统:
void release_region(unsignedlong start, unsignedlong n)

操作I/O内存:
对I/O内存的操作需按如下步骤完成:
1. 申请
2. 映射
3. 访问
4. 释放

申请I/O内存:
内核提供了一套函数来允许驱动申请它需要的I/O内存,其中核心的函数是:
struct resource*request_mem_region(unsigned long start, unsignedlonglen, char *name)
这个函数申请一个从start 开始,长度为len 字节的内存区。如果成功,返回非NULL;否则返回NULL,所有已经在使用的I/O内存在/proc/iomem 中列出。

映射I/O内存:
在访问I/O内存之前, 必须进行物理地址到虚拟地址的映射,ioremap 函数具有此功能:
void*ioremap(unsigned long phys_addr, unsignedlong size)

访问I/O内存:
访问 I/O 内存的正确方法是通过一系列内核提供的函数:
从 I/O 内存读, 使用下列之一:
unsigned ioread8(void *addr)
unsigned ioread16(void *addr)
unsigned ioread32(void *addr)
写I/O 内存, 使用下列之一:
void iowrite8(u8 value, void *addr)
void iowrite16(u16 value, void *addr)
void iowrite32(u32 value, void *addr)

老版本的 I/O 内存访问函数:
从 I/O 内存读, 使用下列之一:
unsigned readb(address)
unsigned readw(address)
unsigned readl(address)
写I/O 内存, 使用下列之一:
unsigned writeb(unsigned value, address)
unsigned writew(unsigned value, address)
unsigned writel(unsigned value, address)

释放I/O内存:
I/O内存不再需要使用时应当释放,步骤如下:
1. void iounmap(void * addr)
2. void release_mem_region(unsigned long start, unsignedlonglen)

混杂设备驱动:

定义
在Linux系统中,存在一类字符设备,它们共享一个主设备号(10),但次设备号不同,我们称这类设备为混杂设备(miscdevice)。所有的混杂设备形成一个链表,对设备访问时内核根据次设备号查找到相应的miscdevice设备。

设备描述
Linux内核使用struct miscdevice来描述一个混杂设备。

struct miscdevice{
int minor;/* 次设备号*/
const char *name;/* 设备名*/
const struct file_operations *fops;/*文件操作*/
struct list_head list; 
struct device *parent;
struct device *this_device;
};

设备注册
Linux内核使用misc_register函数来注册一个混杂设备驱动。
int misc_register(struct miscdevice* misc)

综合实例:

使用MMAP完成I/O的控制

开发环境:

Cent OS 6.4、TQ2440开发板、内核版本linux-2.6.29

硬件描述:

LED所在端口分别为GPB5、6、7、8,GPBDAT物理地址为0x56000014


内核模块程序:

#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>

#define DEVICE_NAME "leds"

static unsigned long led_table [] = {
	S3C2410_GPB5,
	S3C2410_GPB6,
	S3C2410_GPB7,
	S3C2410_GPB8,
};

static unsigned int led_cfg_table [] = {
	S3C2410_GPB5_OUTP,
	S3C2410_GPB6_OUTP,
	S3C2410_GPB7_OUTP,
	S3C2410_GPB8_OUTP,
};
/*文件释放函数
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
};*/
static int sbc2440_leds_mmap (struct file *file, struct vm_area_struct *vma)
{
	vma->vm_flags |= VM_IO;
	vma->vm_flags |= VM_RESERVED;
	if (remap_pfn_range(vma, vma->vm_start, 0x56000000>> PAGE_SHIFT, 100, vma->vm_page_prot))
	{
		printk(KERN_INFO "the mmap had failed");
		return  -EAGAIN;
	}
	return 0;
}

static struct file_operations dev_fops = {
	.owner	=	THIS_MODULE,
	//.release = mem_release,
	.mmap = sbc2440_leds_mmap,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &dev_fops,
};

static int __init dev_init(void)
{
	int ret;

	int i;
	
	for (i = 0; i < 4; i++) {
		s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
		s3c2410_gpio_setpin(led_table[i], 0);//初始化IO端口设置输出
	}

	ret = misc_register(&misc); 
	printk (DEVICE_NAME"\tinitialized\n");

	return ret;
}

static void __exit dev_exit(void)
{
	misc_deregister(&misc);
}

module_init(dev_init);
module_exit(dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zbffff");
MODULE_DESCRIPTION("leds");

应用程序:

#include <stdio.h>
#include <stdlib.h>//包含exit、malloc、free
#include <string.h>//包含memset
#include <unistd.h>
#include <sys/mman.h>

int main(int argc, char **argv)
{
	int i;
	int led[4],leds=0;
	int fd;
	char *start;
	char *buf;
	if (argc != 5)
	{
		fprintf(stderr, "must be 4 nums\n");
		exit(1);
	}
	for(i=0;i<=3;i++)
	{
		if((sscanf(argv[i+1], "%d", &led[i]) != 1)||(led[i]!=1&&led[i]!=0))
		{
			fprintf(stderr, "num only can be 1 or 0");
			exit(1);	
		}
		led[i]=1-led[i];
	}
	/*打开文件*/
	fd = open("/dev/leds",2);//或者O_RDWR,不过需要#include<fcntl.h>

	start=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	
	//printf("buf1 num20 is %x\n",start[20]);
	start[20]=start[20]&~(1<<5)&~(1<<6)&~(1<<7)|led[0]<<5|led[1]<<6|led[2]<<7;
	start[21]=start[21]&~(1<<0)|led[3];
	//printf("buf2 num20 is %x\n",start[20]);
       
	munmap(start,100); /*解除映射*/
	free(buf);
	close(fd);  
	return 0;	
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值