null第二部分 Linux设备驱动程序第二部分 Linux设备驱动程序第一章 设备驱动简介第一章 设备驱动简介设备驱动程序的角色设备驱动程序的角色是内核的一部分,属于内核中的设备管理子系统
是应用程序和实际设备间的软件层
提供对硬件的基本操作,如open,read,write,ioctl,close等
只提供硬件操作机制,如何使用硬件(操作策略)应由应用决定,驱动不应该包含策略
驱动程序既可以直接编译到内核中(zImage),或者编译为可动态加载的模块(.ko文件,用insmod程序加载)Linux设备的分类Linux设备的分类字符( char ) 设备
-是一种可以按字节流来存取的设备
-实现 open, close, read, 和 write等系统调用
-文本控制台( /dev/console )和串口( /dev/ttyS0)、内存、Flash等
块(block)设备
-按整块数据存取(如512字节)
-如磁盘设备(/dev/hda)
-可以带有文件系统
网络设备
-负责网络数据包的发送和接收,如eth0第二章 内核模块基础第二章 内核模块基础模块代码结构模块代码结构hello world模块实例分析
hello.c初始化和退出函数初始化和退出函数初始化函数module_init()-由insmod调用
-注册设备,请求资源等
退出函数module_exit()-由rmmod调用
-取消设备注册、释放资源等
初始化中的错误处理(goto的使用)
int __init my_init_function(void)
{
int err;
/* registration takes a pointer and a name */
err = register_this(ptr1, "skull");
if (err)
goto fail_this;
err = register_that(ptr2, "skull");
if (err)
goto fail_that;
err = register_those(ptr3, "skull");
if (err)
goto fail_those;
return 0; /* success */
fail_those:
unregister_that(ptr2, "skull");
fail_that:
unregister_this(ptr1, "skull");
fail_this:
return err; /* propagate the error */
}模块的编译和加载模块的编译和加载模块编译Makefile分析
Makefile模块加载参数模块加载参数参数的值可由 insmod 或者 modprobe 在加载时指定
参数类型可以是bool,charp,int等
声明方式
module_param()
如
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
hellop.c
课后练习课后练习输入hello world模块例子代码,编译并加载模块
给hello world模块增加参数,重新编译并加载第三章 字符设备驱动程序第三章 字符设备驱动程序设备文件和设备号设备文件和设备号Linux上的设备操作都是通过设备文件进行
如
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 /dev/ttyS0
"c"表示字符设备,"b"表示块设备
主设备号标识设备相连的驱动,次设备号决定引用哪个设备设备号的分配和释放设备号的分配和释放静态分配
int register_chrdev_region(dev_t first, unsigned int count, char *name);
-first :起始设备编号(通常为0)
-count:请求的设备编号个数
-name:设备名
动态分配
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
-dev:内核分配的主次设备号
释放设备号
void unregister_chrdev_region(dev_t first, unsigned int count);
注意:一定要检查返回值,确保分配成功!关键数据结构关键数据结构include/linux/fs.h
struct file_operations (定义设备操作方法)
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
。。。
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
。。。
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
};
struct file(对应 每个打开的文件,在open时创建)
struct inode(内核内部表示文件的结构)
字符设备初始化和注册(1)字符设备初始化和注册(1)字符设备用cdev结构表示
cdev结构的初始化
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
或者
void cdev_init(struct cdev *cdev, struct file_operations *fops);
字符设备初始化和注册(2)字符设备初始化和注册(2)设备注册
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
-dev:cdev结构
-num:设备编号
-count:设备数(通常是1)
设备注销
void cdev_del(struct cdev *dev);
设备的file operation方法(1)设备的file operation方法(1)open方法(打开设备)
-初始化设备、分配资源等
int (*open)(struct inode *inode, struct file *filp);
release方法(释放设备)
-释放资源、关闭设备等
-并不是在应用程序每次调用close时都会调用release设备的file operation方法(2)设备的file operation方法(2)read方法(从设备读取数据)
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
count:请求传输的数据大小
buff:缓冲区
offp:正在存取的文件位置
返回值:等于count,完整读取
大于0但小于count,部分传输
等于0,已到达文件尾
小于0,出错
write方法(往设备写入数据)
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
返回值:等于count,完整写入
大于0但小于count,部分写入
等于0,没有写入
小于0,出错
设备的file operation方法(3)设备的file operation方法(3)用户空间和内核空间的数据传送
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
注意:需要检查返回值
设备的file operation方法(4)设备的file operation方法(4)llseek方法(用于设备定位)
-有些设备不能定位,如串口等字节流设备
-调用read、write时会更新文件当前位置指针
-如果设备不支持llseek,需要调用以下函数打开设备:
int nonseekable_open(struct inode *inode; struct file *filp);
同时file_operations 结构中设置 llseek 方法为 no_llseek设备的file operation方法(5)设备的file operation方法(5)ioctl方法(用于其他的设备控制操作,如获取参数、改变设置、硬件控制等)
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
-cmd:命令参数
-arg:指针或者数值
ioctl命令
-系统范围内唯一
-Documentation/ioctl-number.txt(内核使用的ioctl幻数)
-由4个字段组成
type-幻数
number-序号
direction-数据传送方向
size-数据大小设备的file operation方法(6)设备的file operation方法(6)定义ioctl命令的宏
_IO(type,nr)(没有参数的命令)
_IOR(type, nr, datatype)(读数据)
_IOW(type,nr,datatype)(写数据)
_IOWR(type,nr,datatype)(读写)
返回值:
对于无效的命令,通常返回-EINVAL
命令参数传递:
put_user(datum, ptr)
-返回datum变量的值给应用程序
get_user(local, ptr)
-从应用程序获取参数值保存在local变量中
设备的file operation方法(7)设备的file operation方法(7)scull的ioctl命令 分析main.c
应用程序的调用方法(数值或指针方式):
int quantum;
ioctl(fd,SCULL_IOCSQUANTUM, &quantum); ioctl(fd,SCULL_IOCTQUANTUM, quantum); ioctl(fd,SCULL_IOCGQUANTUM, &quantum); quantum = ioctl(fd,SCULL_IOCQQUANTUM); 实例分析实例分析scull设备驱动代码分析
main.c课后练习课后练习输入scull驱动代码,编译并加载,使用cp、dd、cat等命令对scull设备进行读写操作第四章 内核时间和定时器第四章 内核时间和定时器内核时间单位内核时间单位jiffies
-时钟滴答计数器
-两个jiffies的间隔一般为10ms(1/HZ,HZ=100,即1秒产生的时钟滴答数)
-volatile类型,如
unsigned long j, stamp_1, stamp_half, stamp_n;
j = jiffies; (当前时间)
stamp_1 = j + HZ;(1秒)
stamp_half = j + HZ/2;(半秒)
stamp_n = j + n * HZ / 1000; (n毫秒)延时函数(1)延时函数(1)忙等待
while (time_before(jiffies, j1))
cpu_relax();
-浪费CPU资源,降低性能
释放CPU
while (time_before(jiffies, j1))
schedule();
-延时不准确,可能大于预期的值
延时函数(2)延时函数(2)超时
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);-jiffies为单位
短延时(忙等待)
void ndelay(unsigned long nsecs);-纳秒
void udelay(unsigned long usecs);-微秒
void mdelay(unsigned long msecs);-毫秒
jit模块代码分析
内核定时器(1)内核定时器(1)调度一个函数在将来一个特定的时间执行,如查询设备、关闭硬件等
定时器函数是异步执行的,属于软中断类型
定时器函数的一些限制
-不允许存取用户空间
-不能存取 current 指针
-不能进行睡眠或者调度. 不能调用 schedule 或者某种 wait_event, 也不能调用任何其他可能睡眠的函数. 例如kmalloc
定时器函数执行后可以再次注册内核定时器(2)内核定时器(2)
定时器API:
#include
struct timer_list
{
/* ... */
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
};
void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
-expires:定时器将要运行的jiffies值
-function:定时器到期时执行的函数
-data:传递给function的参数,可以使指针
jit模块代码分析
课后练习课后练习输入jit驱动代码,编译并加载,读取/proc目录下的相应文件,观察不同延时方法的表现
第五章 并发和竞态第五章 并发和竞态什么是并发和竞态什么是并发和竞态CPU的多处理特性,导致多个线程同时执行,如内核抢占、中断、异步执行(定时器)、SMP(多处理器)等
资源共享容易导致竞态
scull的问题分析-内存泄露如何避免竞态如何避免竞态用内核提供的并发控制原语(信号量、锁定等)
减少资源共享(如全局变量等)
信号量和互斥
-临界区(操作共享资源)
-信号量是一个整数,一个进程只有在信号量大于0时才能进入临界区,同时信号量减1,小于0时需要等待(休眠)
-信号量初始值为1时就成为互斥并发控制(1)-linux信号量并发控制(1)-linux信号量初始化
void sema_init(struct semaphore *sem, int val);
-val:信号量初始值
互斥体初始化
编译时:
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);-初始处于锁定状态
运行时:
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);并发控制(2)-自旋锁并发控制(2)-自旋锁特性:
-可以在不能休眠的代码中使用
-提高性能
-主要用于可抢占内核和多CPU系统
使用规则:
-拥有锁时不能休眠(不能调用任何可能导致休眠的函数,如kmalloc,copy_from_user等)
-持有自旋锁的时间应尽可能短,否则内核延迟将增加,高优先级进程将被迫长时间等待,影响性能自旋锁API自旋锁API初始化:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED; (编译时)
void spin_lock_init(spinlock_t *lock); (运行时)
获取自旋锁:
void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);-禁止中断,同时保存当前中断允许状态
void spin_lock_irq(spinlock_t *lock);-禁止中断
void spin_lock_bh(spinlock_t *lock)-禁止软中断
释放自旋锁:
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
判断能否获得自旋锁,同时不会阻塞:
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
第六章 内存分配第六章 内存分配kmalloc函数(1)kmalloc函数(1)和malloc相似
有可能阻塞
不对所分配的内存区段清零
所分配的内存区段在物理上是连续的kmalloc函数(2)kmalloc函数(2)#include
void *kmalloc(size_t size, int flags);
-s ize:需要分配的内存大小
-flags:标志
-GFP_KERNEL(代表进程分配,可能导致休眠,最常用)
-GFP_ATOMIC(在中断服务程序、定时器函数环境中使用,不会导致休眠)kmalloc函数(3)kmalloc函数(3)只能分配预定义的,固定大小的字节数
实际分配的大小可能大于请求的字节数
kmalloc能分配的最小字节数是32或64
不要分配太大的内存(大于128K)kfree函数kfree函数释放由kmalloc分配的内存
void kfree(void *obj);
obj:kmalloc返回的指针
IO内存(1)IO内存(1)通常指外设的寄存器或设备内存,如显存或网卡缓冲区等
映射到内存地址空间。也是通过CPU地址总线和数据总线读写
需要将IO内存的物理地址映射到内核的虚拟地址(ioremap)
不要直接使用指针访问IO内存,应使用内核提供的读写函数(可读性、可移植性好,经过优化)IO内存(2)IO内存(2)IO内存的分配和映射
-请求分配IO内存区域
#include
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
start:起始地址
len:长度
成功返回非NULL指针
-释放IO内存区域
void release_mem_region(unsigned long start, unsigned long len); IO内存(3)IO内存(3)将设备的IO地址(如寄存器地址等)映射到内核的虚拟地址空间
#include
void * ioremap(unsigned long offset, unsigned long size);
-offset:设备的IO地址(物理地址)
-size:映射范围
由于体系结构差异,不能直接操作返回的指针,应使用内核提供的IO操作函数
取消ioremap所做的映射
void iounmap(void *addr);
-addr:ioremap返回的内核虚拟地址IO内存(4)IO内存(4)读IO内存
unsigned int ioread8(void *addr);-8位
unsigned int ioread16(void *addr);-16位
unsigned int ioread32(void *addr);-32位
addr:ioremap返回的指针
写IO内存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
连续读写
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
buf:数据缓冲区
count:数据大小
读写整块IO内存
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);第七章 中断处理第七章 中断处理设备通讯的三种方式设备通讯的三种方式轮询
中断
DMA(直接内存存取)什么是中断什么是中断中断是外设给CPU的信号,可以临时打断CPU执行的代码,转而执行中断处理程序
一个中断通常和一个处理程序关联
中断具有优先级,高优先级的中断可以嵌套低优先级的中断
在CPU相应中断时,同级的中断会被自动屏蔽
中断处理程序时异步执行的,需要注意防止竞态
中断处理程序必须是原子执行的,不能进入休眠状态中断处理程序(1)中断处理程序(1)注册和释放
#include
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long flags,
const char *dev_name,
void *dev_id);
irq:请求的中断号(可以参考CPU数据手册)
handler:和irq相关联的中断处理程序
flags:中断标志,通常取SA_INTERRUPT
dev_name:中断的所有者
dev_id:私有数据,如不使用,可以设为NULL
void free_irq(unsigned int irq, void *dev_id);
通常应该在打开设备的时候请求中断,而不是在模块初始化时,防止没有使用设备而占用中断资源。
/proc/interrupts可以显示系统中断的状态中断处理程序(2)中断处理程序(2)运行限制:
-不能和用户空间传递数据
-不能等待任何事件
-分配内存时应使用GFP_ATOMIC参数
-不能给信号量加锁
-不能执行调度程序
中断处理程序的主要工作是响应中断(设置中断响应标志),将设备的数据读入驱动的缓冲区,同时唤醒等待数据的用户进程
中断处理程序的返回值
-IRQ_HANDLED:已处理
-IRQ_NONE:未处理,或者不是本设备产生的中断
short模块代码分析前半部和后半部(1)前半部和后半部(1)当中断处理程序要做较长时间的处理时,应分为两部分
前半部执行时关闭中断,因此执行过程要尽可能短(request_irq注册的处理程序)
后半部由前半部调度,在推后的更安全的时间执行(此时可以允许中断)
典型的处理过程如前半部只是获取设备数据到缓冲,然后直接退出,数据的处理、进程唤醒等耗时的操作由后半部执行前半部和后半部(2)前半部和后半部(2)内核提供的后半部处理机制
tasklet
-较快,但必须是原子执行的
tasklet的声明:
DECLARE_TASKLET(name, function, data);
name:tasklet名称
function:tasklet被调度时执行的函数
data:指针参数
如:
void short_do_tasklet(unsigned long);
DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);
tasklet的调度(一般有前半部调用)
tasklet_schedule(&short_tasklet);前半部和后半部(3)前半部和后半部(3)工作队列
-运行周期较长,但允许休眠
工作队列的声明:
static struct work_struct short_wq;
INIT_WORK(&short_wq, (void (*)(void *)) short_do_tasklet, NULL);
工作队列的调度:
schedule_work(&short_wq);
short模块代码分析第八章 块设备驱动程序第八章 块设备驱动程序块设备的特性块设备的特性可以随机读写固定大小的数据块
可以提高性能
块大小通常是4096字节
内核使用的扇区大小是512字节块设备的注册和注册块设备的注册和注册块设备的注册
int register_blkdev(unsigned int major, const char *name);
major:主设备号,如为0,由内核动态分配
name:设备名
块设备的注销
int unregister_blkdev(unsigned int major, const char *name); 块设备的操作块设备的操作#include
struct block_device_operations
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
大部分的块设备ioctl命令都有内核处理,驱动实现的较少
int (*media_changed) (struct gendisk *gd);
int (*revalidate_disk) (struct gendisk *gd);
对包含可移动介质的块设备(如光驱等),用来判断设备介质是否改变并作出响应
和字符驱动的主要区别是没有读写函数gendisk结构gendisk结构内核用来显示磁盘驱动器或分区
主要成员
struct block_device_operations *fops;
设备操作集合.
struct request_queue *queue;
设备I/O 请求队列结构
分配和初始化
struct gendisk *alloc_disk(int minors);
void add_disk(struct gendisk *gd);
释放
void del_gendisk(struct gendisk *gd);
sbull驱动模块初始化函数分析块设备操作函数块设备操作函数open和close
-设置驱动和硬件的状态. 包括起停磁盘, 加锁一个可移出设备的门, 分配 DMA 缓冲等等
-不一定由应用程序调用,可能由内核直接调用,如mount等
ioctl
-大部分ioctl由内核处理,驱动处理的很少
sbull模块设备操作代码分析I/O请求I/O请求I/O请求的处理
-在内核认为需要启动对设备读写的时候调用
-请求队列包含当前需要处理的请求,由内核对所有I/O请求进行调度(合并或重排等,性能考虑)后将请求加入队列
请求队列的初始化和清除
#include
request_queue_t blk_init_queue(request_fn_proc *request, spinlock_t *lock);
request:请求处理函数
void blk_cleanup_queue(request_queue_t *);
从队列中获取请求
struct request *elv_next_request(request_queue_t *queue); (不删除请求)
从队列中删除请求
void blkdev_dequeue_request(struct request *req);
通知
关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知
内核请求已处理
void end_request(struct request *req, int success);
sucess:指示请求是否成功完成请求的结构(1)请求的结构(1)一个request可能包含多个bio,一个bio可能包含多个bio_vec(段)
bio结构图
请求的结构(2)请求的结构(2)请求队列图
请求的结构(3)请求的结构(3)遍历request中的bio
rq_for_each_bio(bio, request)
遍历bio中的段
bio_for_each_segment(bvec, bio, segno);
请求完成函数请求完成函数int end_that_request_first(struct request *req, int success, int count);
success:驱动是否完成请求的扇区的传送
count:完成传送的扇区数
-告知内核驱动已完成count个扇区传送
void end_that_request_last(struct request *req);
-通知等待请求完成的进程,同时释放request结构
sbull模块代码分析第九章 网络设备驱动程序第九章 网络设备驱动程序网络设备的特点网络设备的特点将接口注册到内核中,供内核在需要时调用
没有设备文件(没有read,write等调用)
异步接收数据,需要将数据推送给内核
驱动和协议相互独立
需要支持设置网络地址, 修改发送参数, 以及维护流量和错误统计等操作net_device结构net_device结构分配
#include
struct net_device *alloc_netdev(int sizeof_priv,
const char *name,
void (*setup)(struct net_device *));
sizeof_priv :"私有数据"区的大小
name:接口名(如eth0等)
setup:初始化函数的指针, 用来设置 net_device 结构的剩余部分(snull的net_device初始化函数分析)
如:
snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d",
snull_init);
对于以太网,可以简化为:
#include
struct net_device *alloc_etherdev(int sizeof_priv);
默认使用eth%d 作为name参数. ether_setup()为初始化函数。
释放
void free_netdev(struct net_device *dev); 网络设备的注册和注销网络设备的注册和注销int register_netdev(struct net_device *dev);
void unregister_netdev(struct net_device *dev); 网络接口的打开和关闭(1)网络接口的打开和关闭(1)open和close接口由ifconfig调用
open时所做的处理:
-申请必要的资源(中断、I/O地址空间等)
-设置硬件地址(MAC地址)
启动发送队列
void netif_start_queue(struct net_device *dev); 网络接口的打开和关闭(2)网络接口的打开和关闭(2)close所做的处理:
-释放所申请到的资源
-停止发送队列
-void netif_stop_queue(struct net_device *dev); 数据包的发送数据包的发送sk_buff结构
#include
包含要发送的数据包
sk_buff的分配和释放
struct sk_buff *dev_alloc_skb(unsigned int len);
(使用GFP_ATOMIC标志调用kmalloc,可以在中断处理函数中使用)
void dev_kfree_skb(struct sk_buff *skb); 发送队列的操作发送队列的操作发送函数
hard_start_stransmit()
停止发送
netif_stop_queue(struct net_device *dev);
重启发送
netif_wake_queue(struct net_device *dev);
完全停止发送
void netif_tx_disable(struct net_device *dev);
(确保返回时没有数据包在发送 )接收数据包流程接收数据包流程中断方式
分配一个缓存区来保存报文(dev_alloc_skb)
调用 memcpy将报文数据拷贝到缓存区
更新数据包统计计数
调用netif_rx将 skb提交给上层中断处理函数中断处理函数通过网卡中的中断状态寄存器判断是接收还是发送中断,或是错误指示、状态改变等
snull中断处理函数分析驱动实例练习
-毫秒级精度秒表驱动实例练习
-毫秒级精度秒表功能描述功能描述液晶显示秒表计数(分:秒:毫秒),开发板#1键用来启动和停止秒表,#2键用于复位秒表。实现要点实现要点秒表计数采用内核定时器函数
按键检测采用中断方式
应用程序读取/proc下的文件(如/proc/ms_clock),或/dev下的设备文件如(/dev/ms_clock),不断刷新当前秒表计数程序大致流程程序大致流程模块初始化部分:
-注册按键中断处理程序
-初始化定时器
-创建/proc/ms_clock文件,包含分钟、秒、毫秒计数值,初始化为00 00 00(/proc文件的创建可以参考《linux设备驱动程序》中的“内核时间”部分的例子)
-如使用/dev/ms_clock设备,需按字符设备驱动要求注册设备
第一次检测到按键时,在中断处理程序中启动定时器
每毫秒更新/proc/clock_ms文件内容
再次检测到按键时,在中断处理程序停止定时器
应用程序循环读取/proc/clock_ms文件内容,刷新当前秒表显示项目实战
-嵌入式Linux IP电话终端项目实战
-嵌入式Linux IP电话终端项目需求项目需求硬件平台:
-S3C2410开发板
软件功能需求:
-基于SIP协议(linphone开源协议栈)
-
设计
领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计
一个基于Qt的GUI界面,包含呼出、接听、挂断,可以输入SIP地址,地址簿管理功能
-板上的四个按键分别用于呼出(接听)、挂断、增大音量、减小音量
-可扩展视频功能总体要求总体要求以小组为单位,小组长为项目经理,项目完成时间两个月
需提交的项目文档
-项目
计划
项目进度计划表范例计划下载计划下载计划下载课程教学计划下载
书(包含进度计划、关键时间点、任务分工等)
-系统概要设计
-系统详细设计
-系统测试
报告
软件系统测试报告下载sgs报告如何下载关于路面塌陷情况报告535n,sgs报告怎么下载竣工报告下载
-使用说明
项目计划和设计阶段都需要进行评审项目实现要点项目实现要点linphone开源SIP协议栈的移植
linphone API的调用
Qt图形前端设计
声卡驱动和应用开发
按键驱动程序开发项目组成员分工与
职责
岗位职责下载项目部各岗位职责下载项目部各岗位职责下载建筑公司岗位职责下载社工督导职责.docx
项目组成员分工与职责项目经理
-负责项目进度计划安排,任务分工,项目进度监控等
(提交项目计划书)
应用开发人员
-Qt界面开发
-linphone协议栈移植
驱动开发人员
-声卡驱动和应用开发
-按键驱动开发
(提交概要设计、详细设计)
系统集成
-功能模块整合
-文件系统制作和发布
系统测试
-程序实现是否和需求吻合
(提交测试报告)参考资料参考资料www.linphone.org