安卓序列槽的實作,需要底層C++配合,不過這次我們根據framework中的思想,直接用API修改提供給JAVA層調用,這個就比較簡單了。
DEV項目需要,要實作在Android中實作序列槽的收發功能,有幾種方法可以參考使用。
1. 标準的Android HAL層思想,把序列槽的功能加入framework的API中(類似于android中sensor的實作)
a. 確定驅動層中基于tty的序列槽驅動可以正常read、write、poll資料,當然了,也可以自己寫一個字元驅動來實作序列槽的讀寫功能。
b. 在BSP的HAL層中添加序列槽讀寫功能的回調函數(linux 應用層 c/c++)
c. Android framework中添加jni層,解析HAL中生成的module,然後對回調函數進行封裝,生成.so庫,提供給java層。
d. 添加遠端調用接口,使用aidl在framework中添加遠端調用
e. 添加serviceManagement
2. 繞過HAL,直接使用JNI來完成讀寫等回調函數,之後同1 。
3. 繞過android系統,直接編寫jni庫,在應用程式中直接調用jni接口,完成序列槽的收發。
------------------------------------------------------------------------------
以上都是可用的方法,這裡我采用最簡單的第三種方法,其中第一種方法最繁瑣,但也是android最标準的方法,之後我會在can bus的移植中使用(先打個啞謎^0^),OK 廢話不多說,開始碼代碼,工作!
首先是驅動層,我使用的是fsl的開發闆,這邊freescale已經幫我們實作了驅動,可以在/dev/下發現ttymxc0,ttymxc1.。。。這些就是CPU上各個序列槽的驅動檔案,可以嘗試echo "123" > /dev/mxctty0 之後可以看到序列槽終端上會列印出“123”。
但是,我們做驅動的不能就這樣拿着别人的東西就用,咱要分析,要學習,要膜拜,要抄襲,要。。。貌似我最喜歡幹這種事情了,好吧,這裡我自己照着Linux裝置驅動詳解這書寫了一個虛拟的字元驅動,當做我們的序列槽吧。
提供了跟序列槽同樣的功能,這個驅動中我使用阻塞的方式來讀寫資料,一邊看書,一邊學習,一邊自己寫代碼,一邊學習jni,一邊學習android的架構,何樂而不為呢?
首先,我們要注冊一個字元驅動,然後初始化等待隊列,初始化信号量,初始化變量,給結構體配置設定記憶體空間,老一套了。。。是個寫驅動的都知道要幹這些事情。
/*裝置驅動子產品加載函數*/
int globalfifo_init(void)
{
int result;
globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev) ,GFP_KERNEL);
if(!globalfifo_devp) {
result = -ENOMEM;
}
memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
globalfifo_devp->mdev = mdev_struct;
result = misc_register(&(globalfifo_devp->mdev));
if(result<0)
return result;
init_MUTEX(&globalfifo_devp->sem); /*初始化信号量*/
init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化讀等待隊列頭*/
init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化寫等待隊列頭*/
return 0;
}
看到沒,這裡使用了miscdevice驅動,這個簡單容易實作,HOHO~~偷懶了。這裡給我們的全局結構體配置設定了記憶體空間,然後把結構體操作函數挂到我們的全局結構體變量中,最後注冊這個miscdevice驅動。
/*globalfifo裝置結構體*/
struct globalfifo_dev
{
// struct cdev cdev; /*cdev結構體*/
struct miscdevice mdev;
unsigned int current_len; /*fifo有效資料長度*/
unsigned char mem[GLOBALFIFO_SIZE]; /*全局記憶體*/
struct semaphore sem; /*并發控制用的信号量*/
wait_queue_head_t r_wait; /*阻塞讀用的等待隊列頭*/
wait_queue_head_t w_wait; /*阻塞寫用的等待隊列頭*/
};
view plain
看到我們的globalfifo結構體的定義了吧,這裡,就是這裡,是以在init函數中,我們要初始化信号量,初始化讀寫等待隊列頭。要不咱先來講講這裡的阻塞的概念吧。
顧名思義,就是堵在那邊不動了,其實是真的不動了,利用等待隊列實作裝置的阻塞,當使用者程序通路系統資源的時候,當這個資源不能被通路,我們又不想讓之後的事情繼續發生,這樣的話我們就可以阻塞在那邊,放心,我們可以讓該程序進入休眠,這樣的話就不會浪費CPU的資源了,然而等到這個資源可以通路的時候,我們就可以喚醒該阻塞的程序,繼續讓他執行下去,如果沒有地方喚醒他,那他就真的“堵死”在那邊了。
簡單的介紹了下,接下來看看我們要實作哪些功能函數 plaincopy
/*檔案操作結構體*/
static const struct file_operations globalfifo_fops =
{
.owner = THIS_MODULE,
.read = globalfifo_read,
.write = globalfifo_write,
.ioctl = globalfifo_ioctl,
.poll = globalfifo_poll,
.open = globalfifo_open,
.release = globalfifo_release,
};
咱有讀,寫,打開。。。。等函數,繼續往下分析。
struct globalfifo_dev *globalfifo_devp; /*裝置結構體指針*/
/*檔案打開函數*/
int globalfifo_open(struct inode *inode, struct file *filp)
{
/*将裝置結構體指針指派給檔案私有資料指針*/
filp->private_data = globalfifo_devp;
return 0;
}
/*檔案釋放函數*/
int globalfifo_release(struct inode *inode, struct file *filp)
{
return 0;
}
open和release函數沒什麼好說的了,其實這裡還是蠻有講究的,比如說這個裝置我們隻能讓一個使用者進行通路,那我們可以再open函數裡面做點手腳,一般我們讀核心驅動模型的時候都會看到很多時候在open函數中都會設計引用計數自加1,這樣的話可以更好的管控我們裝置被打開次數。
但是這裡我們沒做什麼,我們隻是把我們的全局結構體變量指派給了這裡filp的一個私有成員變量中,這樣的話我們可以再每一個功能函數中取出這個私有成員,有利于代碼的可讀性,release就不講了。
/*globalfifo讀函數*/
static ssize_t globalfifo_read(struct file *filp, char __user *buf, size_t count,
loff_t *ppos)
{
int ret;
struct globalfifo_dev *dev = filp->private_data; //獲得裝置結構體指針
DECLARE_WAITQUEUE(wait, current); //定義等待隊列
down(&dev->sem); //獲得信号量
add_wait_queue(&dev->r_wait, &wait); //進入讀等待隊列頭
/* 等待FIFO非空 */
while (dev->current_len == 0)
{
if (filp->f_flags &O_NONBLOCK)
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改變程序狀态為睡眠
up(&dev->sem);
schedule(); //排程其他程序執行
if (signal_pending(current))
//如果是因為信号喚醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
/* 拷貝到使用者空間 */
if (count > dev->current_len)
count = dev->current_len;
if (copy_to_user(buf, dev->mem, count))
{
ret = - EFAULT;
goto out;
}
else
{
memcpy(dev->mem, dev->mem + count, dev->current_len - count); //fifo資料前移
dev->current_len -= count; //有效資料長度減少
printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->w_wait); //喚醒寫等待隊列
ret = count;
}
out:
up(&dev->sem); //釋放信号量
out2:
remove_wait_queue(&dev->w_wait, &wait); //從附屬的等待隊列頭移除
set_current_state(TASK_RUNNING);
return ret;
}
然後這就是我們的讀函數,在進行讀之前,我們把等待隊列加進我們的隊列連結清單中,然後檢查我們的buff是否為空,如果為空的話,那就沒什麼好讀的了,是以我們讓進城休眠,當有貨給我們讀了,再喚醒我們的隊列。
首先是把目前進城加入等待隊列中add_wait_queue(&dev->r_wait, &wait);
沒東西讀的時候,使程序睡眠,在排程到别的任務去ew plaincopy
/* 等待FIFO非空 */
while (dev->current_len == 0)
{
if (filp->f_flags &O_NONBLOCK)
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改變程序狀态為睡眠
up(&dev->sem);
schedule(); //排程其他程序執行
if (signal_pending(current))
//如果是因為信号喚醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem);
}
這段代碼比較關鍵,與寫函數中一樣,當我們的buff被寫滿時,我們也會發生阻塞。
/*globalfifo寫操作*/
static ssize_t globalfifo_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct globalfifo_dev *dev = filp->private_data; //獲得裝置結構體指針
int ret;
DECLARE_WAITQUEUE(wait, current); //定義等待隊列
down(&dev->sem); //擷取信号量
add_wait_queue(&dev->w_wait, &wait); //進入寫等待隊列頭
/* 等待FIFO非滿 */
while (dev->current_len == GLOBALFIFO_SIZE)
{
if (filp->f_flags &O_NONBLOCK)
//如果是非阻塞通路
{
ret = - EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); //改變程序狀态為睡眠
up(&dev->sem);
schedule(); //排程其他程序執行
if (signal_pending(current))
//如果是因為信号喚醒
{
ret = - ERESTARTSYS;
goto out2;
}
down(&dev->sem); //獲得信号量
}
/*從使用者空間拷貝到核心空間*/
if (count > GLOBALFIFO_SIZE - dev->current_len)
count = GLOBALFIFO_SIZE - dev->current_len;
if (copy_from_user(dev->mem + dev->current_len, buf, count))
{
ret = - EFAULT;
goto out;
}
else
{
dev->current_len += count;
printk(KERN_INFO "written %d bytes(s),current_len:%d\n", count, dev->current_len);
wake_up_interruptible(&dev->r_wait); //喚醒讀等待隊列
寫函數最後會喚醒我們的等待隊列,因為寫進去東西了,就可以去讀了,就是這樣,這部分跟我們的序列槽收發相同。
别的功能我就不說了,OK,驅動完成之後,我們加載進去,然後進行測試下。
首先我們去cat /dev/globalfifo
發生阻塞,一直停在那,這時候我們再打開一個終端,去寫資料
echo "123" > /dev/globalfifo
寫完之後,我們立馬會發現之前的cat有東西出來了,每次都會把資料全部讀出來。
==================================================
下面是我們的jni,首先咱要明确我們做的事情,打開裝置,讀裝置,最後不用的話就關閉裝置,是以我們至少要實作這3個API,
#define FIFO_CLEAR 0x01
#define BUFFER_LEN 20
#define GLOBALFIFO_PATH "/dev/globalfifo"
int globalfifo_fd = -1;
JNIEXPORT jint JNICALL
Java_com_liujun_globalfifo_init(JNIEnv *env, jobject obj)
{
globalfifo_fd = open(GLOBALFIFO_PATH, O_RDONLY); // | O_NOBLOCK
if(globalfifo_fd != -1)
{
__android_log_print(ANDROID_LOG_INFO,"JNI","open device done.");
//clear the buff
if(ioctl(globalfifo_fd, FIFO_CLEAR, 0) < 0)
__android_log_print(ANDROID_LOG_INFO,"JNI","clear buff error!");
} else
__android_log_print(ANDROID_LOG_INFO,"JNI","open device error!");
}
這是我們的初始化函數,定義了一個全局的檔案描述符,init函數隻做了open的動作。
JNIEXPORT jstring JNICALL
Java_com_liujun_globalfifo_read(JNIEnv *env, jobject obj)
{
int nread = 0;
char buff[512] = "";
__android_log_print(ANDROID_LOG_INFO,"JNI","read !");
nread = read(globalfifo_fd, buff, sizeof(buff));
if(nread != -1)
__android_log_print(ANDROID_LOG_INFO,"JNI","===> %s", buff);
return (*env)->NewStringUTF(env, buff);
}
這個API封裝了read函數,然後把讀到的buff轉換成為java中能識别的string,最後傳回到java層的是string類型的字元串。
JNIEXPORT jint JNICALL
Java_com_liujun_globalfifo_exit(JNIEnv *env, jobject obj)
{
close(globalfifo_fd);
__android_log_print(ANDROID_LOG_INFO,"JNI","close done!");
return 0;
}
最後這是我們的exit函數,調用了close來關閉我們的裝置。然後編寫Android.mk檔案,最後編譯,生成globalfifo庫
===========================================
接下來我們建立一個Android 工程,導入jni庫并且定義native API
public native int init();
public native String read();
public native int exit();
static {
System.loadLibrary("globalfifo");
}
然後在适當的地方調用。設定3個按鍵,先試打開,然後read,按下read按鍵的時候開啟一個thread去讀資料。
public class MyThread implements Runnable{
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(100);//
string = read();
Message message=new Message();
message.what=1;
handler.sendMessage(message);//發送消息
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class MyButtonListener implements OnClickListener{
public void onClick(View v) {
if(v.getId() == R.id.start ){
init();
}
if(v.getId() == R.id.read) {
//string = read();
//Toast.makeText(mContext, string, Toast.LENGTH_SHORT).show();
new Thread(new MyThread()).start();
}
if(v.getId() == R.id.close) {
exit();
}
}
}
安裝apk,然後運作程式,點選open,然後點選read,使用adb shell進入系統,然後往裡面寫東西
echo "Jay Zhang" > dev/globalfifo