Android 驅動和系統開發 1. 一個簡單的例子(原創)

首先,邊學習邊記錄點自己的代碼,希望看了我寫的代碼覺得不怎麼樣的,多多提出來,讓我也學習學習,我一定會虛心接受大家的指導。

這裏我們是來學習android 驅動和android系統框架的,這裏我只針對於整個android設備驅動的一個流程,從上到下的調用,而且在這裏我們去使用android源碼環境,原因是我使用的電腦比較破,編譯android會掛,而且android BSP太大了,git下來很麻煩,所以這裏我只是選用了ubuntu的環境來學習android 底層和系統層的開發,那麼有人會問,沒有源代碼,如何去學習呢,又如何來演示出來呢?不急,慢慢道來,首先還是來談一下android的底層和系統層,下圖供參考,基本每個設計android的工程師都知道這張圖


android使用的是Linux 內核,雖然說稍微改了點東西,增加了些移動嵌入式設備的特性,比如說early suspend、進程間通信(bind)、特有的log機制(logcat)等,但是Linux主流的一些東西都沒有改變,所以,這裏我們還是使用Linux下得驅動作爲底層來學習,具體的android的特性,之後的博客中會做深入。

Linux內核驅動之上應該是android的HAL層,也就是傳說中得硬件抽象層,我把這玩意一直理解成是Linux 應用層,雖然,有的廠家的代碼寫的比較龐大,比較好得還是使用C++的類來封裝,典型的設備是傳感器模塊,一般都會有一個HAL層來銜接,這裏我們使用Linux 的應用層編程 c/c++來實現調用驅動來收發數據等操作。

在上面應該是JNI層啦,在我們的學習中,JNI的基礎知識在我的另外一個博客專欄中有提到,在這裏我們也是使用JNI來實現接口。

然後是framework,我們還是使用java代碼封裝JNI,然後寫一些java的測試代碼,當然了,沒有android的UI,對於java的UI編程,我也是沒玩過,對於java我只是一個初學者,寫測試代碼還湊合,一般都是一邊google一邊寫的 ^0^

接下來就開始行動吧,首先是我們的驅動部分,這些代碼都是我自己寫的,所以難免會有很多寫的不對的地方,希望大家覺得我哪邊寫的不好的多提些意見,畢竟我也只是一個小菜鳥。

這個驅動完成的主要任務是從內核空間向用戶空間發送一個座標信息(模擬鼠標的動作),當然了,我們的驅動是虛擬的,當然不會主動的向用戶空間poll數據,所以,我們要先自己往文件系統中寫數據,然後驅動會通過input 子系統向user space發送數據。

下面是我驅動的一個結構圖,一般我寫個驅動都會先考慮很多,然後再着手去做,三思而後行嘛,驅動的整體架構如果出了問題,到最後是很難再修正的,只有重新寫。


可以看到,我們的思路很清晰,初始化的時候開一個線程,注意這個線程是一個死循環,但是在循環當中有一個試圖獲得信號量的動作,如果得不到,就會進入休眠,如下是thread的代碼:

static int work_thread(void *data)
{
	int x, y, ret;
	struct virtual_dev *pvdev = (struct virtual_dev *)data;
	struct semaphore sema = pvdev->sem;
	// poll the data into user space
	printk(KERN_INFO "work thread running!!");
	
	while(pvdev->running) {
		do{
			ret = down_interruptible(&sema);
		} while(ret == -EINTR);
		//printk("done!\n");
		//poll the x and y data into user space
		x = pvdev->x;
		y = pvdev->y;
		input_report_abs(pvdev->input, ABS_X, x);
		input_report_abs(pvdev->input, ABS_Y, y);
		input_sync(pvdev->input);
		printk("position: %d | %d\n", x, y);	
	}
	return 0;
}

喚醒這個線程的地方,就是調用up的地方:

static ssize_t write_position(struct device *dev,
		struct device_attribute *attr, const char *buffer, ssize_t count)
{
	int x,y;
	sscanf(buffer, "%d%d", &x, &y);
	vdev->x = x;
	vdev->y = y;
	//do something with x and y ===> poll the data;
	up(&vdev->sem);
	return count;
}

可以看到,這個write_position是被註冊當position文件被執行寫操作的時候執行的。

/* attach the sysfs */
DEVICE_ATTR(position, 0666, read_position, write_position);
DEVICE_ATTR(color, 0666, read_color, write_color);
DEVICE_ATTR(bcolor, 0666, read_bcolor, write_bcolor);
相信看了結構圖就知道我們的代碼是如何寫的吧,這個驅動比較簡單,看下完整的代碼

/*
 *	This driver is named virtual touch, which can send some message into user space from kernel space,
 *	for this driver just for study Linux device driver...
 *	Jay Zhang 
 *	mail: [email protected]
 */
#include <linux/module.h>

#include <linux/err.h>
#include <linux/input.h>
#include <linux/hwmon.h>
#include <linux/kthread.h>
#include <linux/platform_device.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
struct virtual_dev {
	struct platform_device *vt_dev;
	struct task_struct *run_thread;
	struct input_dev *input;
	struct semaphore sem;
	int x,y;		//point position
	int color;		//line color
	int bcolor;	//background color
	int size;		//line size
	int running;
};

struct virtual_dev *vdev = NULL;

/* position read/write function */
static ssize_t read_position (struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "(%d, %d)\n", vdev->x, vdev->y);
}

static ssize_t write_position(struct device *dev,
		struct device_attribute *attr, const char *buffer, ssize_t count)
{
	int x,y;
	sscanf(buffer, "%d%d", &x, &y);
	vdev->x = x;
	vdev->y = y;
	//do something with x and y ===> poll the data;
	up(&vdev->sem);
	return count;
}

/* color read/write function */
static ssize_t read_color(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "line color is %d\n", vdev->color);
}

static ssize_t write_color(struct device *dev,
		struct device_attribute *attr, const char *buffer, ssize_t count)
{
	int color;
	sscanf(buffer, "%d", &color);
	vdev->color = color;
	return count;
}

/* bcolor read/write function */
static ssize_t read_bcolor(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "background color is %d\n", vdev->bcolor);
}

static ssize_t write_bcolor(struct device *dev,
		struct device_attribute *attr, const char *buffer, ssize_t count)
{
	int bcolor;
	sscanf(buffer, "%d", &bcolor);
	vdev->bcolor = bcolor;
	return count;
}

/* attach the sysfs */
DEVICE_ATTR(position, 0666, read_position, write_position);
DEVICE_ATTR(color, 0666, read_color, write_color);
DEVICE_ATTR(bcolor, 0666, read_bcolor, write_bcolor);
//DEVICE_ATTR(size, 0666, read_size, write_size);

/* attribute description */
static struct attribute *vdev_attrs[] = {
	&dev_attr_position.attr,
	&dev_attr_color.attr,
	&dev_attr_bcolor.attr,
//	&dev_attr,size,
	NULL
};

/* attribute group */
static struct attribute_group vdev_attr_group = {
	.attrs = vdev_attrs,
};

static int work_thread(void *data)
{
	int x, y, ret;
	struct virtual_dev *pvdev = (struct virtual_dev *)data;
	struct semaphore sema = pvdev->sem;
	// poll the data into user space
	printk(KERN_INFO "work thread running!!");
	
	while(pvdev->running) {
		do{
			ret = down_interruptible(&sema);
		} while(ret == -EINTR);
		//printk("done!\n");
		//poll the x and y data into user space
		x = pvdev->x;
		y = pvdev->y;
		input_report_abs(pvdev->input, ABS_X, x);
		input_report_abs(pvdev->input, ABS_Y, y);
		input_sync(pvdev->input);
		printk("position: %d | %d\n", x, y);	
	}
	return 0;
}

/*
static int virtual_probe(struct platform_device *pdev)
{
	int ret;
	//malloc for vdev
	vdev = kzalloc(sizeof(struct virtual_dev), GFP_KERNEL);
	if(!vdev) {
		vdev = NULL;
		printk(KERN_INFO "kzalloc for vdev failed.\n");
		ret = -ENOMEM;
		goto kzalloc_failed;
	}
	//initialized for semaphore
	sema_init(&(vdev->sem), 0);
	//initialized for input subsystem 
	vdev->input = input_allocate_device();
	if(!(vdev->input)) {
		vdev->input = NULL;
		printk(KERN_INFO "allocate input device failed.\n");
		ret = -ENOMEM;
		goto allocate_input_failed;
	}
	set_bit(EV_ABS, vdev->input->evbit);
	//for x
	input_set_abs_params(vdev->input, ABS_X, -1024, 1024, 0, 0);
	//for y
	input_set_abs_params(vdev->input, ABS_Y, -1024, 1024, 0, 0);
	//set name
	vdev->input->name = "virtual-touch";
	ret = input_register_device(vdev->input);
	if(ret) {
		printk(KERN_ERR "%s: Unable to register input device: %s\n",__func__, vdev->input->name);
		goto input_register_failed;
		//return ret;
	}
	//initialized for sysfs of our virtual driver
	vdev->vt_dev = pdev;
	sysfs_create_group(&vdev->vt_dev->dev.kobj, &vdev_attr_group);
	//run thread to poll data
	vdev->run_thread = kthread_run(work_thread, vdev, "vt_thread");
	vdev->running = 1;
	platform_set_drvdata(pdev, vdev);
	printk(KERN_INFO "virtual touch device probe successful.\n");
	return 0;
input_register_failed:
	input_free_device(vdev->input);
allocate_input_failed:
	kfree(vdev);
kzalloc_failed:
	return ret;
}


static struct platform_driver virtual_driver = {
	.probe = virtual_probe,
	.driver = {
		.name = "virtual touch",
	},
};
*/

static int virtual_init(void)
{
	int ret;
	//malloc for vdev
	vdev = kzalloc(sizeof(struct virtual_dev), GFP_KERNEL);
	if(!vdev) {
		vdev = NULL;
		printk(KERN_INFO "kzalloc for vdev failed.\n");
		ret = -ENOMEM;
		goto kzalloc_failed;
	}
	//register a platform device
	vdev->vt_dev = platform_device_register_simple("virtual-touch", -1, NULL, 0);
	if(IS_ERR(vdev->vt_dev)) {
		PTR_ERR(vdev->vt_dev);
		printk("register device failed.\n");
	}
	//initialized for semaphore
	sema_init(&(vdev->sem), 0);
	//initialized for input subsystem 
	vdev->input = input_allocate_device();
	if(!(vdev->input)) {
		vdev->input = NULL;
		printk(KERN_INFO "allocate input device failed.\n");
		ret = -ENOMEM;
		goto allocate_input_failed;
	}
	set_bit(EV_ABS, vdev->input->evbit);
	//for x
	input_set_abs_params(vdev->input, ABS_X, -1024, 1024, 0, 0);
	//for y
	input_set_abs_params(vdev->input, ABS_Y, -1024, 1024, 0, 0);
	//set name
	vdev->input->name = "virtual-touch";
	ret = input_register_device(vdev->input);
	if(ret) {
		printk(KERN_ERR "%s: Unable to register input device: %s\n",__func__, vdev->input->name);
		goto input_register_failed;
		//return ret;
	}
	//initialized for sysfs of our virtual driver
//	vdev->vt_dev = pdev;
	sysfs_create_group(&vdev->vt_dev->dev.kobj, &vdev_attr_group);
	//run thread to poll data
	vdev->run_thread = kthread_run(work_thread, vdev, "vt_thread");
	vdev->running = 1;
//	platform_set_drvdata(pdev, vdev);
	printk(KERN_INFO "virtual touch device probe successful.\n");
	return 0;
input_register_failed:
	input_free_device(vdev->input);
allocate_input_failed:
	kfree(vdev);
kzalloc_failed:
	return ret;

}

static void virtual_exit(void)
{
	vdev->running = 0;
	sysfs_remove_group(&(vdev->vt_dev->dev.kobj), &vdev_attr_group);
	input_unregister_device(vdev->input);
	platform_device_unregister(vdev->vt_dev);
}

module_init(virtual_init);
module_exit(virtual_exit);

MODULE_LICENSE("GPL");

下面是Makefile

obj-m := virtualtouch.o
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
	make -C $(KERNEL_DIR) SUBDIRS=$(PWD) modules
clean:
	rm *.o *.ko *.mod.c *.symvers modules.order

然後我們來編譯模塊再加載

# make

# insmod virtualtouch.ko

然後看下我們生成的文件系統

root@jay-Vbox:/sys/devices/platform/virtual-touch# pwd
/sys/devices/platform/virtual-touch
root@jay-Vbox:/sys/devices/platform/virtual-touch# tree
.
├── bcolor
├── color
├── modalias
├── position
├── power
│   ├── async
│   ├── autosuspend_delay_ms
│   ├── control
│   ├── runtime_active_kids
│   ├── runtime_active_time
│   ├── runtime_enabled
│   ├── runtime_status
│   ├── runtime_suspended_time
│   └── runtime_usage
├── subsystem -> ../../../bus/platform
└── uevent


2 directories, 14 files

還有我們的input subsystem的文件系統,使用dmesg查看

[  413.650710] input: virtual-touch as /devices/virtual/input/input6
[  413.662695] virtual touch device probe successful.
[  413.663616] work thread running!!

按照log可以知道,我們生成的event6

root@jay-Vbox:/dev/input# pwd
/dev/input
root@jay-Vbox:/dev/input# ls -l event6 
crw-r----- 1 root root 13, 70 Jul  2 22:07 event6

OK,瞭解了我們生成的文件系統之後,我們寫一個測試程序來測試下,嘿嘿 ^0^

貼代碼如下:

include <stdlib.h>
#include <fcntl.h>
#include <linux/input.h>

int main(void)
{
	struct input_event ev_temp;
	int fd = open("/dev/input/event6", O_RDWR);
	if(fd < 0) {
		printf("open device failed.\n");
		return 0;
	}
	printf("open done!\n");
	//return 0;
	int count, x, y;

	while(1) {
		printf("read!\n");
		count = read(fd, &ev_temp, sizeof(struct input_event))	;
		if(EV_ABS == ev_temp.type) {
			if(ev_temp.code == ABS_X)
				x = ev_temp.value;
			else if(ev_temp.code == ABS_Y)
				y = ev_temp.value;
			printf("position : (%d, %d)\n", x, y);
		} else if(EV_SYN == ev_temp.type) {
			printf("sync!\n");
		}
	}
	return 0;
}

這個。。。這個小測試程序就不多說了,open  ---->  read  ---->  show

測試程序的編寫一般是參考驅動中report出去的到底是什麼類型的數據。

# gcc main -o virtual-touch.c

root@jay-Vbox:/home/jay/workspace/virtual/main# ./main 
open done!

read!


這個時候被阻塞在read函數這邊,因爲沒有數據被讀出,所以會阻塞在那邊得不到返回,當然了,我們也可以使用非阻塞的去讀(open的時候標誌設置成 | NOBLOCK),接着接着,咱們來出發input 設備

root@jay-Vbox:/home/jay/workspace/virtual/main# echo 1 2 > /sys/devices/platform/virtual-touch/position 
root@jay-Vbox:/home/jay/workspace/virtual/main# position : (1, 32767)
read!
position : (1, 2)
read!
sync!
read!

看到沒,看到沒,數據已經打印出來了,具體的流程大家可以自己分析驅動代碼,最主要的還是那張驅動架構圖。

好了,現在,我們來寫一個jni來封裝下read函數

。。。等等,咱們還是先看我們的java代碼,一般jni是提供接口的,但是接口到底是怎麼樣的還是取決於java中的需要。

這裏我寫了一個class來封裝open,read等函數。

GetPosition.java

class GetPosition
{
	private int x;
	private int y;
	private native int readEvent();
	private native void openEvent(String path);
	private native void closeEvent();	
	GetPosition(String path) {
		x = 0;
		y = 0;
		openEvent(path);
	}

	public void close()
	{
		closeEvent();
	}
	public void setXY(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public int getX()
	{
		return this.x;
	}

	public int getY()
	{
		return this.y;
	}
	
	public void read()
	{
		int retVal = readEvent();
		setXY(retVal/256, retVal%256);	
	}
	static {
		System.loadLibrary("virtouch");
	}
}

我承認,我寫的java代碼很爛,好吧,咱不多看了,只要看這裏的native函數,這裏封裝了open、read、close函數,

好,然後咱們使用javah來生成jni的頭文件。

# javah -jni GetPosition

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class GetPosition */

#ifndef _Included_GetPosition
#define _Included_GetPosition
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     GetPosition
 * Method:    readEvent
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_GetPosition_readEvent
  (JNIEnv *, jobject);

/*
 * Class:     GetPosition
 * Method:    openEvent
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_GetPosition_openEvent
  (JNIEnv *, jobject, jstring);

/*
 * Class:     GetPosition
 * Method:    closeEvent
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_GetPosition_closeEvent
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif

然後我們按照這個頭文件來完成我們的jni

virtual-touch.c

#include <stdio.h>
#include "GetPosition.h"
#include <stdlib.h>
#include <linux/input.h>
#include <fcntl.h>

int fd;


JNIEXPORT jint JNICALL
Java_GetPosition_readEvent(JNIEnv *env, jobject obj)
{
	static int x, y;
	int count;
	struct input_event ev_temp;

	count = read(fd, &ev_temp, sizeof(struct input_event));

//AGAIN:
	if(EV_ABS == ev_temp.type) {
		if(ev_temp.code == ABS_X) {
			x = ev_temp.value;
		} else if(ev_temp.code == ABS_Y) {
			y = ev_temp.value;
		}
		printf("x: %d, y: %d\n", x, y);
	} 
	//else if(EV_SYN == ev_temp.type) {
		return (x*256 + y);
	//}
	//goto AGAIN;
}

JNIEXPORT void JNICALL
Java_GetPosition_openEvent(JNIEnv *env, jobject obj, jstring prompt)
{
	char path[64];
	const jbyte *str;
	str = (*env)->GetStringUTFChars(env, prompt, NULL);
	if(str == NULL) {
		printf("error: str is NULL!\n");
		return ;
	}
	sprintf(path, "%s", str);
	fd = open(path, O_RDWR);
	if(fd < 0) {
		printf("open %s failed...\n", path);
	}
	(*env)->ReleaseStringUTFChars(env, prompt, str);
}

JNIEXPORT void JNICALL
Java_GetPosition_closeEvent(JNIEnv *env, jobject obj)
{
	close(fd);
}

jni的基礎知識,大家可以參考我的一個博客專欄,嘻嘻~~~打廣告了

http://blog.csdn.net/column/details/jnijni.html

用命令來生成動態庫

這裏給大家提供一個shell腳本來使用

#/bin/bash

if [ $# != 2 ]
then
	echo "input argument error!"
else
	cc -I /usr/lib/jvm/java-6-sun/include/linux -I /usr/lib/jvm/java-6-sun/include/ -fPIC -shared -o $1lib$2.so $2.c	
fi

好了,最後是我們的java測試程序

class Test {
//	private native int openEvent(String path);
//	private native int closeEvent();
//	private int getPosition();
	public static void main(String[] args)
	{
		GetPosition getPosition = new GetPosition("/dev/input/event5");
		//openEvent("/dev/input/event5");
		while(true) {
			getPosition.read();
			System.out.println("[ " + getPosition.getX() + " , " + getPosition.getY() + "]");
		}	
		//getPosition.close();
		//System.out.println()
	}
/*
	static {
		System.loadLibrary("virtouch");
	}
*/
}

繼續測試下

可以得到跟之前一樣的結果。

OK,現在我們可以再Linux下學習 Linux 驅動還有jni了。




參考:

在Linux下如何使用GCC編譯程序、簡單生成靜態庫及動態庫。

http://blog.csdn.net/clozxy/article/details/5716227

Linux下JNI實現

http://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349775.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章