辉夜的博客

繁花似锦,辉夜如昼

0%

科研的第二个任务是AIDL和binder机制相关内容,b站上找到了binder源码剖析的很好的讲解,但是AIDL的很多博客都已经过时了。
由于本人没有任何安卓开发经验,所以很需要一步一步示范的教程。在ytb上找到了一个教程,基于此解决了Android11以后bindService无法绑定成功、不会回调onServiceConnected的问题,经测试可以在Android12 (Samsung G9910)上跑通

AIDL概述

AIDL是安卓为进程间通讯留下的接口,和其他RPC框架一样,AIDL也包括代理层,协议层和通讯层。代理层是安卓的跨进程通讯机制Binder,协议层是安卓接口定义语言AIDL,代理层除了AIDL自动生成的存根代码之外,还需要手动实现服务的绑定以及服务并将存根转换为服务接口。这就构成了整个AIDL的框架。
具体实现AIDL的过程分为以下几步:

  1. 创建服务端项目,编写AIDL
  2. 注册服务,实现服务方法并回传服务存根
  3. 创建客户端项目,拷贝AIDL
  4. 设计客户端UI
  5. 在客户端实现业务逻辑
  6. 在客户端注册queries

注意,缺少这一步将无法在Android11以上的机器中绑定服务

  1. 在客户端实现服务绑定并用AIDL存根作为通讯接口
  2. 利用存根中的方法实现需求功能

在本例中,我们将实现一个进行四则运算计算器,以及一个计算器的服务器。计算器通过绑定到服务器上的服务来完成计算功能

阅读须知

本文可以仅需要RPC基础和c语言基础,对于java中类的继承、抽象类、方法等概念只需初步了解,无需Android开发经验

示例开发

先决条件:
Android Studio
Android Studio 虚拟机或开启了USB调试功能的Android实体机

安装android虚拟机的方法

  • 首先找到设备管理器

devicemanager

  • 然后点击创建设备

createdevice

  • 没有特殊需求就选默认硬件,直接点next
  • 操作系统映像选默认的(和硬件匹配的)点Download,许可协议选accept然后再点next就开始下载了。我这里是已经装好了

image

开启USB调试的方法

这个各个设备不太一样,一般要先在设备信息界面点击某个信息十次解锁,比如我是点击设置-设备信息-软件信息-编译编号解锁开发者模式

解锁了之后在设置-开发者选项里找到USB调试打开就可以了。

1.创建服务端项目,编写AIDL代码

创建新项目

从左上角File-New-New Project新建项目,注意选择”Empty Activity”

newproject

接下来可以点next,但在最后的界面记得把语言改成java,项目名字我这里叫做AIDLServer

java

创建新的包

在项目列表里选择java目录,右键,点击new,点击Package,在目录结构里选main

newpackage

给它起个名字,注意过会需要创建这个包的一个副本,所以名字别起太怪,不方便检查,这里叫做AidlPackage

创建新的AIDL文件

刚才的创建的包上右键创建AIDL文件,名字也别起太怪,这里叫做IAidlDemo

newaidlfile

应该能在新建的Aidl文件中看到如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// IAidlDemo.aidl
package AidlPackage;

// Declare any non-default types here with import statements

interface IAidlDemo {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}

编写接口定义

我们要把计算的过程委托给远程服务器,也就是传递两个运算数和一个运算符,接受一个结果。方便起见,规定运算是整数运算,并且用1 2 3 4表示+ - * /,这样我们就形成了如下接口:

1
2
3
4
5
6
7
8
// IAidlDemo.aidl
package AidlPackage;

// Declare any non-default types here with import statements

interface IAidlDemo {
int calculate(int v1, int v2, int op);
}

然后需要点击Build-Rebuild Project或者点击右上角配置栏左侧的绿色小锤子进行Make Project。注意,每次修改AIDL文件都要进行rebuild
到此为止,我们的服务应用程序框架就创建完成。

2.注册服务,实现方法

在APP里右键创建新的Service,注意不要Service(Intent Service),在接下来的窗口里enabledexported都要选中

newservice

新建存根

打开新建的服务的java代码app/java/AidlPackage/<ServiceName>.java,为<ServiceName>类添加一个存根变量

1
2
private final IAidlDemo.Stub
...

注意,这里按下.之后,应该提示后面的Stub类型,如果没有提示就说明刚才没有进行Rebuild!点击下图所示绿色小锤子进行Rebuild

rebuild

确认自己rebuild完成之后继续写。

1
private final IAidlDemo.Stub = new IAidlDemo.

现在应该能自动补全Stub了,按Tab键之后,就应该出现类似下面的内容:

1
2
3
4
5
6
7
8
private final IAidlDemo.Stub stub = new IAidlDemo.Stub() {
@Override
public int calculate(int v1, int v2, int op) throws RemoteException {
return 0;
}
};
//不会自动打分号

注意到calculate正是我们之前定义的接口定义语言,因为AIDL生成的存根类型是个抽象类,因此系统会自动提醒我们复写其中的calculate方法。

实现方法

接下来,我们就来修改这个存根的内部的方法,这里就简单的复制一份代码吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final IAidlDemo.Stub stub = new IAidlDemo.Stub() {
@Override
public int calculate(int v1, int v2, int op) throws RemoteException {
switch (op) {
case 1:
return v1 + v2;
case 2:
return v1 - v2;
case 3:
return v1 * v2;
case 4:
return v1 / v2;
default:
Log.e("Error: ","Invalid Operator");
return 0;
}
}
};
//记得打分号

由于这是一个远程方法,因此默认可能会抛出一个远程异常,不过我们不必管他,一般不会出现这种情况。

传递存根

<ServiceName>.java中应该还有一个默认的方法onBind:

1
2
3
4
5
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}

这是因为我们的服务继承了Service抽象类,因此需要实现onBind方法,也就是指定当服务绑定时要进行的操作。我们只需要返回刚才创建的存根即可。

1
2
3
4
@Override
public IBinder onBind(Intent intent) {
return stub;
}

到此为止我们的服务端项目就编写完成,可以跑一下试试看:

server

虽然没有UI界面,但是服务已经就绪了~

3.创建客户端项目,拷贝AIDL

新建客户端项目可以单开一个project也可以就在这个project里操作,这里选择单开一个project。过程就不再赘述,注意仍然用Empty Activity作为界面。

这里仍然在app\java上右键,选New-Package新建包,注意包名要与刚刚创建的包名相同
在包上右键,新建Aidl文件,注意文件名要和刚刚创建的文件相同
复制刚刚的Aidl文件内容,粘贴到新的Aidl文件里
包名、文件名、文件内容完全相同。
copyaidl

4.设计客户端UI

打开activity_main.xml默认在创建项目的时候就会有这个文件生成,如果不小心关掉了就从左侧项目目录app/res/layout/目录下打开。
请检查一下这个目录,确保其中只有activity_main.xml。在右上角选择以Code方式查看该文件的内容:
layout

文件内容应该如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

这个xml制定了应用程序主界面的UI,如果layout文件夹里有多个文件或者这个.xml文件的内容和上述内容不一样,那么很有可能是因为你没有选择EmptyActivity类型的项目,你可自行判断是否需要重建项目。如果你不清楚这个.xml文件的具体行为,可以考虑重建。

接下来我们还是在右上角将视图切换到View,或者你也可以直接复制本项目的.xml文档,直接跳到实现业务逻辑的部分

修改布局

默认的布局是constraint,这样图标的上下左右位置都需要指定,很麻烦,于是我们把所有的androidx.constraintlayout.widget.ConstraintLayout修改为RelativeLayout

1
2
3
4
5
6
7
8
9
  <?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ <RelativeLayout xmlns: xmlns:android="http://schemas.android.com/apk/res/android"

...


- </androidx.constraintlayout.widget.ConstraintLayout>
+ </RelativeLayout>

如果修改成功,Android应该没有任何Warning,并且你会发现Hello World从原来的居中变为出现在左上角。(要查看.xml的效果,你可以在右上角选择SplitView模式)

添加TextView

我们需要一个文本框来显示结果,把下面这段话粘贴到.xml中,并且删除Hello World(从< 开始删除到 >为止)

1
2
3
4
5
6
7
8
9
10
11
<TextView
android:id="@+id/display_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="20dp"
android:background="#FDF171"
android:hint="Result"
android:padding="15dp"
android:textSize="20sp"
android:textStyle="bold" />

现在你应该看到这样的文本框:

textview

所以TextView就是一个文本框,我们设置了默认显示的字符串(hint)、背景颜色、字号、字体等内容信息,还设置了长宽、水平居中、边距等定型定位尺寸。我们可以在java代码中和TextView进行沟通,比如通过setText方法来设置要显示的内容,这样我们就可以把结果输出到这个文本框里。
还有值得注意的一点是,每个组建都拥有自己的id,比如@+id/display_result就对应我们刚刚的文本框,因为我们的第一行就指明了他的id。这个id1可以在xml中描述组件之间的相对关系,也可以在java中用于绑定组件信息。

添加EditText

我们需要两个文本框来接受输入的运算数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<EditText
android:id="@+id/enter_first_value"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_below="@+id/display_result"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:ems="10"
android:hint="Enter First Value"
android:imeOptions="actionNext"
android:inputType="number"
android:maxLength="5"
android:singleLine="true" />

<EditText
android:id="@+id/enter_next_value"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_below="@+id/enter_first_value"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"
android:autofillHints=""
android:ems="10"
android:hint="Enter Next Value"
android:inputType="number"
android:maxLength="5"
android:singleLine="true" />

开头是一个id号,而所有含有layout的都是定型尺寸和定位尺寸,这里注意layout_below指明它要放在上面那个文本框的下方。这里还限制了输入类型是数字,最多输入5个且只接受单行输入。

添加按钮

首先添加四则运算,这四个按钮之间的位置关系通过layout_belowlayout_toEndOf来指定,可以看到加法减法位于同一行,乘法除法位于同一行,减法除法在加法乘法右侧。
按钮默认全字大写,所以这里关闭了textAllCaps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<Button
android:id="@+id/add"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/enter_next_value"
android:layout_marginLeft="20dp"
android:background="#0288D1"
android:text="Add"
android:textAllCaps="false"
android:textColor="#ffff" />

<Button
android:id="@+id/subtract"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/enter_next_value"
android:layout_marginStart="20dp"
android:layout_toEndOf="@+id/add"
android:background="#0288D1"
android:text="Subtract"
android:textAllCaps="false"
android:textColor="#ffff" />

<Button
android:id="@+id/multiply"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/add"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:background="#0288D1"
android:text="Multiply"
android:textAllCaps="false"
android:textColor="#ffff" />

<Button
android:id="@+id/divide"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_below="@+id/subtract"
android:layout_marginStart="20dp"
android:layout_marginTop="10dp"
android:layout_toEndOf="@+id/multiply"
android:background="#0288D1"
android:text="Divide"
android:textAllCaps="false"
android:textColor="#ffff" />

这两个按钮是用于清零和绑定服务的,让他们居中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Button
android:id="@+id/clear_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/division"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="Clear Data" />

<Button
android:id="@+id/bind_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/clear_data"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="Bind Service" />

整体效果应该如图:
ui

注意,各个按钮之间的相对关系是通过id指定的,如果更改了id多半会有error,修改id修复error即可
另外会有一些warning,比如叫你把字符串做编码之类的,建议不懂的话忽略就好

这就是我们的ui界面了!AndroidUI的设计还是很简洁的,但是背后是项目自动为我们生成的许多文件~

5.实现业务逻辑

接下来我们将开始修改客户端的java代码,实现和前端组件的交互、业务流程、服务绑定以及利用远程服务实现功能!

修改主类,继承事件监听

下面打开客户端的MainActivity.java,这里我们可以直接修改主类,让主类完成对UI事件的监听,于是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.aidlclient;

import ...

- public class MainActivity extends AppCompatActivity {
+ public class MainActivity extends Activity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

}
//注意别漏了左大括号

这样它马上就会报错,先把鼠标放到View.OnClickListener里,可以看到这样的提示:
implement

我们赶紧按ALT+ENTER自动导入这个包,之后把鼠标挪到extend后面的Activity上,再导入一次。他可能会要你选是导入还是创建,就选Import Class Activity就行。
不过还是有错,这是因为我们继承了一个抽象类,必须实现其方法,所以我们在主类里再写一个方法注意方法名称、形参、返回值必须一模一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.aidlclient;

import ...
public class MainActivity extends Activity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
+ @Override
+ public void onClick (View view) {
+
+ }

}

现在应该一个error都没有了。

初始化UI

首先在类里声明私有成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.aidlclient;

import ...
public class MainActivity extends Activity implements View.OnClickListener{
+ private TextView textViewDisplayResult;
+ private EditText editTextFirstValue, editTextNextValue;
+ private Button buttonAdd,buttonSubtract, buttonMultiply, buttonDivide, buttonClearData,buttonBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
...
}

成员的名字无所谓,只要和之后的业务逻辑里面的代码对上就行。但接下来要实例化UI对象,这就必须和之前UI里设计时候的id一一对应了!
onCreate是活动被创建时会执行的方法,下面在onCreate方法中编写函数体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//Initialize UI
textViewDisplayResult = findViewById(R.id.display_result);
editTextFirstValue = findViewById(R.id.enter_first_value);
editTextNextValue = findViewById(R.id.enter_next_value);
buttonAdd = findViewById(R.id.add);
buttonSubtract = findViewById(R.id.subtract);
buttonDivide = findViewById(R.id.divide);
buttonMultiply = findViewById(R.id.multiply);
buttonClearData = findViewById(R.id.clear_data);
buttonBind = findViewById(R.id.bind_service);

//click listener
buttonAdd.setOnClickListener(this);
buttonSubtract.setOnClickListener(this);
buttonDivide.setOnClickListener(this);
buttonMultiply.setOnClickListener(this);
buttonClearData.setOnClickListener(this);
buttonBind.setOnClickListener(this);
}

注意findViewById中必须传对应的id名,否则会报错。这个函数完成了UI的初始化并且把按钮的事件监听器绑定到本类

首先编写事件监听器的逻辑,也就是onClick方法,不同的按钮被点击之后,我们让他们调用不同的方法进行响应。
这里有两个方法尚未实现:bindToDemoServiceverifyAndCalculate,前者是Bind Service按钮用来绑定服务的方法,后者是四个小按钮进行计算的方法。
在这里我们完全实现的只有Clear Data,注意到它通过setText方法将三个文本框的内容都设置为null,这样就会展示hint内容,也就是我们一开始设置的提示信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public void onClick (View view) {
switch(view.getId())
{
case R.id.add:
verifyAndCalculate(1);
break;
case R.id.subtract:
verifyAndCalculate(2);
break;
case R.id.multiply:
verifyAndCalculate(3);
break;
case R.id.divide:
verifyAndCalculate(4);
break;
case R.id.clear_data:
editTextNextValue.setText(null);
editTextFirstValue.setText(null);
textViewDisplayResult.setText(null);
break;
case R.id.bind_service:
bindToDemoService();
break;

}
}

接下来我们来实现verifyAndCalculate,它首先验证是否两个输入文本框内都有内容,如果都有的话就获取内容并进行计算、显示结果。如果没有的话就在屏幕上显示一个小消息框(就是平时看到的那种网络连接不佳的小消息框>_<)
注意这里的calculateData方法是假的!完成服务绑定后我们会把他替换成通过stub调用的远程方法,到时候我们会对这部分的代码再加以改动,如果想获得最终版本的verifyAndCalculate可以直接到最后部分查看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void verifyAndCalculate(int op) {
if(isAnyValueMissing()){
Toast.makeText(this,"Please enter the values", Toast.LENGTH_SHORT).show();
//Toast就是用来显示小弹窗的东西
} else {
int result,firstValue,nextValue;
firstValue = Integer.parseInt(editTextFirstValue.getText().toString());
nextValue = Integer.parseInt(editTextNextValue.getText().toString());
//获取信息,转换为字符串,再用整数包裹把字符串解析为整数

result = calculateData(firstValue,nextValue,op);
textViewDisplayResult.setText(""+result);
//利用空字符串完成int到String的强制转换
}
}
private boolean isAnyValueMissing() {
return editTextFirstValue.getText().toString().isEmpty() ||
editTextNextValue.getText().toString().isEmpty();
//我个人感觉面向对象语言已经充分自注释了哈哈哈哈
}

到此为止,我们的业务逻辑已经编写完成,按下按钮会调用函数并传递运算符类型。

6.在客户端注册queries

首先,打开服务端项目,在app/menifests/AndroidMenifest.xml里找到服务程序的包名和服务名:
packageandservice
这两个名字都记下来,现在这里先用包名。打开客户端项目,在app/menifests/AndroidMenifest.xml中,manifest标签里,与application标签同级。这里的
"com.example.aidlserver"替换成自己的服务端包名

1
2
3
4
5
6
7
8
9
10
11
 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mydemo">

<application
...
</application>
+ <queries>
+ <package android:name="com.example.aidlserver" />
+ </queries>
</manifest>

从Android 11开始,应用程序要想访问别的应用,就必须指明该应用程序的包,很多不够新的教程里都没有这个阶段,因此无法正常运行。

7.绑定服务

这里我们来实现bindToDemoService方法,在MainActivity.java里,将下列内容填写进函数体,大家可以参照注释来理解代码。需要做的替换内容,也都在注释中说明。如果代码有变红的,就是因为有包没导入,用把鼠标放到红色的字上,ALT+ENTER导入

未完待续 以下全是草稿

注意:ComponentName的第一个参数是刚才记下来的服务端包名,第二个参数是"<包名>.<服务名>"
这里要用到bindService方法,因此还需要准备一个ServiceConnection对象,
我们需要复写其中的onServiceConnected方法,也就是服务接通之后的回调方法,这里用先打印一个提示信息,然后再将IBinder转换为Aidl存根。aidlObject = IAidlDemo.Stub.asInterface(iBinder);,其中aidlObject可以声明为一个私有变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private IAidlDemo aidlObject;
private void bindToDemoService() {
Toast.makeText(MainActivity.this, "Service connecting", Toast.LENGTH_SHORT).show();
Intent serviceIntentExplicit = new Intent();
serviceIntentExplicit.setComponent(new ComponentName("com.example.aidlserver", "com.example.aidlserver.Calculate"));


boolean qaq = bindService(
serviceIntentExplicit,
serviceConnectionObject,Context.BIND_AUTO_CREATE);
if (!qaq) {
Toast.makeText(MainActivity.this, "Please start server", Toast.LENGTH_SHORT).show();
}
}

ServiceConnection serviceConnectionObject = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Toast.makeText(MainActivity.this, "Service connected", Toast.LENGTH_SHORT).show();
aidlObject = IAidlDemo.Stub.asInterface(iBinder);
}

@Override
public void onServiceDisconnected(ComponentName componentName) {

}
};

remotee

1
2
3
4
5
6
7
result = 0;
try {
result = aidlObject.calculate(firstValue,nextValue,op
} catch (RemoteException e) {
e.printStackTrace();
}
textViewDisplayResult.setText(""+result);

很不幸的一件事情是,前段时间本人因为修过windows用户名及大量相关注册表把自己的操作系统搞炸了,其中还包括重要的wsl…本文主要讲解一下重装系统的过程,供以后参考

需要准备的文件

  • 个人临时文件和工作文件,下载文件,软件保存的文件
  • 大型软件的安装映像,虚拟机的虚拟磁盘
  • .家目录下的ssh密钥, vscode的settings.json,vsc插件列表

登陆操作系统

这回记得给自己起个阳间点的名字,不要再出现非ASCII字符或带有特殊含义的字符了,只用英文字母和数字比较稳妥.

加载备份文件

一般来讲,被逼到重装系统的时候已经没什么可以备份的软件和配置了,于是只能加载备份文件.一般来讲,可以分为:下载的文件/项目文档/一般文档/软件保存的文档.

  • 下载的文件可以直接放到下载文件夹里
  • 项目文档和个人文档放到原路径
  • 软件保存的文档先留着,等下完软件再放到他们自己的下载路径

这样可以确保再想找的时候一定能找到

重新安装软件

首先安装latern,然后安装clash,然后安装IDM,chrome
接下来安装QQ,微信,腾讯会议,火绒,讯飞输入法,网易云音乐,WinRAR
vscode,wsl,git,windows terminal
如果有大型软件可以等有空了再安装,其他软件可以按需安装

重新加载配置

  1. 网络配置
    用小蓝灯登录小蓝猫的官网,加载小蓝猫的配置
    按照这篇文章为wsl配置代理
    https://zinglix.xyz/2020/04/18/wsl2-proxy/
    安装谷歌tampermonkey插件,在gressyfork插件里找到简易下载助手
    为IDM进行谷歌插件配置,然后点击”绿化.bat”完成配置https://blog.csdn.net/Cappuccino_jay/article/details/105299073
  2. 软件配置
    vsc: 加载settings.json,重新安装插件(现在可以使用sync插件进行同步)
    git: 确认ssh链接良好,如果没有就再配一个,config文件也要改一下
    微信/qq: 更改下载路径,并且把之前下载的文件也扔进去
    Android Studio:汉字中文乱码需要将File-Settings-Editor-Encoding的所有选项都改成UTF-8.还需要在studio64.exe.vmoptions里加一句“-Dfile.encoding=UTF-8”,注意要在安装目录下的文件里加,不能直接在help里找这个选项修改否则会无法启动。
  3. 其他配置
    按照这篇文章重新配置nodejs和hexohttps://blog.csdn.net/DreamcoffeeZS/article/details/79401109
    hexo配置图片并且能按markdown格式预览:npm i -s hexo-asset-link (似乎不需要用hexo-asset-imagehttps://blog.csdn.net/xjm850552586/article/details/84101345)
    按照这篇文章重新加载latex(不要按照这个配置settings.json) https://blog.csdn.net/yinqingwang/article/details/79684419

概览

框架名称 开发者 序列化协议 传输协议 支持语言 备注
gRPC google protobuf http2.0 多语言 没有服务治理功能,序列化效率最好,性能较高
rpcx portobuf+多种 Go服务+多语言客户 性能最高,
Thrift facebook bin(compact) json tcp:socket http:frame file 多语言 性能一般,缺乏技术文档
Motan Weibo json hessian2 tcp Java 基于Dubbo剪裁,擅长处理大批量负载
brpc Baidu protobuf C++

RPC简介,gRPC原理

gRPC

  • 优点

  • 微服务:gRPC 设计用于低延迟和高吞吐量通信。 gRPC 对于效率至关重要的轻量级微服务非常有用。

  • 网络带宽要求低:gRPC 消息使用 Protobuf进行序列化。 这种序列化方法的时空效率都要比json,thrift,hessian强

  • 多语言环境:gRPC 工具支持所有常用的开发语言(C++,Java,Python,Ruby,Go…)

  • 点对点实时通信:gRPC 对双向流式传输提供出色的支持。 gRPC 服务可以实时推送消息而无需轮询。

  • 缺点

  • 只有少数浏览器支持http2

  • 移动端网络可能在wifi及4G频繁切换,无法体现出长连接以及多路复用的优势

  • 相较于json,protobuf编码后数据可读性低

rpcx、

Thrift vs gRPC

  • 优点
  • 高性能,gPRC平均响应延迟差距在2倍左右
  • 序列化和传输层可拔插,提供了多种方法
  • 不足
  • 开发环境生态很差,缺乏说明文档和软件更新
  • 不擅长提供大量流式数据传输

Motan vs gPRC

  • 优点
  • 提供了服务治理功能
  • 极限情况下的最长响应时间很短
  • 不足
  • 序列化效率较低
  • 综合运行效率较低
  • 只支持Java语言

erpc

哈尔滨工业大学 于鸿利 22040866

预学习阶段

如何科学的提问

见读后感

Linux系统安装和基本使用

  • 安装一个linux操作系统

    复用了wsl2-ubantu20.04

    首先将windows版本更新到build 21362+(win11即可),然后以管理员权限打开命令行

    输入wsl –update即可将已有的windows subsystem for linux更新到带有图形用户界面的版本

  • 获取一生一芯框架代码

    添加ssh密钥

    按照讲义clone一个本地项目即可

复习C语言

*

搭建verilator仿真环境

  • 认识verilator

    开源的数字电路仿真软件

  • 安装verilator

    make install编译了十几分钟啧啧啧

  • 运行示例

    按照手册中的说明编写了our.v, sim_main.cpp,编译并运行了程序

    1
    verilator -Wall --cc --exe --build sim_main.cpp our.v

    结果是这样的

    1
    2
    Hello World
    our.v:2: Verilog $finish
  • 对双控开关模块进行仿真

    具体而言,需要调整的内容有:

    1. .v文件名要修改为top.v

    2. cpp 文件中要引入头文件

    3. cpp 文件中

      1
      Vour* top = new Vour{contextp};

      的类名要改为Vour

    4. 编译语句中的文件名要做相应的更改

结果是这样的

1
2
3
4
5
6
7
a = 0, b = 1, f = 1
a = 1, b = 0, f = 1
a = 0, b = 0, f = 0
a = 0, b = 1, f = 1
a = 0, b = 1, f = 1
a = 0, b = 1, f = 1
^C

  • 理解RTL仿真的行为

  • 一键仿真

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #生成的可执行文件名称
    TOPNAME = top
    #在指定文件夹中按扩展名查找所有源文件
    VSRC = $(shell find $(abspath ./vsrc) -name "*.v")
    CSRC = $(shell find $(abspath ./csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
    #nvbroad自动绑定源文件,注意,如果没有$(BUILD_DIR)目录,将无法正确的生成该文件
    SRC_AUTO_BIND = $(abspath $(BUILD_DIR)/auto_bind.cpp)
    CSRC += $(SRC_AUTO_BIND)
    #nvbroad约束文件
    CONSTR = constr/top.nxdc

    #构建目录、verilator目标目录、可执行文件存放路径
    BUILD_DIR = ./build
    OBJ_DIR = $(BUILD_DIR)/obj_dir
    BIN = $(BUILD_DIR)/$(TOPNAME)

    #传递给verilator的参数:以c++形式输出调试信息,并自动依据makefile进行构建
    VERIARG = -Wall --cc -MMD --build

    #传递给preproject连接器的信息,添加SDL2库信息
    LDFLAGS += -lSDL2 -lSDL2_image

    #包含文件路径,加前缀-I传递给g++,INC_PATH在nvbroad.mk中定义
    INCFLAGS = $(addprefix -I, $(INC_PATH))

    #传递给g++的编译参数,包括包含路径和TOP_NAME的定义
    CFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""

    #包含nvbroad.mk
    include $(NVBOARD_HOME)/scripts/nvboard.mk
    #新建构建目录,防止出现无法新建autobind.cpp的错误
    $(shell mkdir -p $(BUILD_DIR))

    #默认的构建对象
    sim: $(CSRC) $(VSRC) $(NVBOARD_ARCHIVE)
    #提交当前存储区,提交信息为sim RTL
    $(call git_commit, "sim RTL") # DO NOT REMOVE THIS LINE!!!

    #运行verilator,传递给其编译参数
    #指定top模块名称
    #添加所有源文件和nvbroad库
    #传递所有preproject连接器参数,加前缀
    #传递所有编译器参数,加前缀
    #指定生成对象目录,生成可执行文件,输出路径为预先指定的可执行文件存储路径
    verilator $(VERIARG) \
    -top $(TOPNAME) \
    $^ \
    $(addprefix -LDFLAGS , $(LDFLAGS)) \
    $(addprefix -CFLAGS , $(CFLAGS)) \
    --Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN))

    #自动绑定文件,由于其被可执行文件依赖,会先生成该文件。调用python执行nvbroad自带的.py文件,输入约束文件,输出自动绑定文件
    $(SRC_AUTO_BIND): $(CONSTR)
    python3 $(NVBOARD_HOME)/scripts/auto_pin_bind.py $^ $@

  • 运行NVBoard示例

    看到了一组循环显示数字的数码管,还有南京大学的 “北 大楼”(南大的高中同学告诉我的)

  • 在NVBoard上实现双控开关

    我使用如下的代码来完成引脚绑定:

1
2
3
4
5
top=top

a SW0//开关0
b SW1//开关1
f LD0//LED0显示输出

配合如下激励代码来连接面板

1
2
3
4
5
6
7
nvboard_bind_all_pins(&dut);
nvboard_init();
/*verilate*/
while(1) {
dut.eval();
nvboard_update();
}

数字电路基础实验

实验一 选择器

  • 上板实验: 二位四选一选择器

我使用如下的代码来搭建top模块:

1
2
3
4
5
6
7
8
9
10
11
module top(a,s,y);
input [7:0] a;
input [1:0] s;
output [1:0] y;
MuxKeyWithDefault #(4, 2, 2) i0 (y, s, 2'b00, {
2'b00, a[1:0],
2'b01, a[3:2],
2'b10, a[5:4],
2'b11, a[7:6]
});//默认模板,参数分别为:输出端、选择端、默认值、选择-输出键值对
endmodule

可以看到, 双输出的选择器要改变a和y的位宽,调整模板的参数,并且对default output的位宽和键值对的输出值的位宽做相应的调整
我使用如下的代码来完成引脚绑定:

1
2
3
4
5
top=top     //设置TOP_NAME为top

s (SW1, SW0)//这两个开关作为选择端
a (SW9, SW8, SW7, SW6, SW5, SW4, SW3, SW2)//这八个开关作为输入端,每两个开关一组,共四组。
y (LD1, LD0)//用LED显示输出

这里按照实验的建议用开关作为输入,led灯作为输出

我使用如下的代码来完成激励代码的主体部分:

1
2
3
4
5
6
7
8
static TOP_NAME dut;//nvbroad接口,宏自动展开为Vtop
nvboard_bind_all_pins(&dut);//绑定所有引脚
nvboard_init();//初始化面板
/*verilate*/
while(1) {
dut.eval();//获取当前数据
nvboard_update();//重绘面板
}

更新值并且更新版面图像即可,我已经看过了,非常漂亮.:>

实验二 编码器和译码器

c源文件无需更改
verilog文件包含两个模块,数码管模块和编码器模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*top.v*/

module top(x,en,l,h);
/*接口定义,x是输入,en是使能,l是编码结果,h是数码管输出结果*/
input [7:0]x;
input en;
output [3:0]l;
output [6:0]h;

/*线路声明,用y存放编码结果,sign存放指示位*/
reg [2:0]y;
wire sign = en & (|x);//若x至少有一位为1.且使能为1,结果就为1

/*每当x或en发生变化时,都会进行译码,如果有使能,结果是x的为1的位的序号,高位优先,如果没有使能,结果为0*/
integer i;
always @(x or en) begin
if (en) begin
y = 0;
for( i = 0; i <= 7; i = i+1)
if(x[i] == 1) y = i[2:0];
end
else begin
y = 0;
end
end
/*l是编码的总结果,包含符号位和数据位,实例化一个数码管*/
assign l = {sign,y};
seg seg1(
.b(l),
.h(h)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*seg.v*/
module seg(
/*输入输出定义,输入4位编码值,输出7位数码管的显示控制*/
input [3:0] b,
output reg [6:0] h
);
/*每当b发生变化,都会重新更新输出*/
always @(*) begin
/*首先检验指示位,若指示位为0,默认显示为8(共阳极数码管)*/
if(b[3] == 0)
h = 7'b0000000;
else begin
/*若v指示位不为0,则根据数据位进行译码,默认输出为8,*/
case(b[2:0])
3'd0 : h = ~7'b0111111;
3'd1 : h = ~7'b0000110;
3'd2 : h = ~7'b1011011;
3'd3 : h = ~7'b1001111;
3'd4 : h = ~7'b1100110;
3'd5 : h = ~7'b1101101;
3'd6 : h = ~7'b1111101;
3'd7 : h = ~7'b0000111;
//'d8 : h = ~7'b1111111;
//'d9 : h = ~7'b1101111;
default : h = ~7'b1111111;
endcase
en
end
endmodule

约束文件的绑定是这样的:

1
2
3
4
5
top=top//指定top接口名称
x (SW7,SW6,SW5,SW4,SW3,SW2,SW1,SW0)//八个开关作为输入
en SW8//使能
l (LD3,LD2,LD1,LD0)//四个led灯显示编码结果
h (SEG0G, SEG0F, SEG0E, SEG0D, SEG0C, SEG0B, SEG0A)//数码管0输出结果,注意链接顺序要与编码匹配

第三章 加法器和ALU

首先通过约束文件介绍一下整体功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
top=top
operand (SW7,SW6,SW5,SW4,SW3,SW2,SW1,SW0)//八个开关输入操作数
button (BTNC,BTNU,BTND,BTNL,BTNR)//按钮:上下让指令加减2,左右让指令加减1,上下左右都会让使能归零,中间会让使能取反
//也就是实际使用的时候,每当调整指令,都会看到结果消失,按下中间的按钮结果才会再次出现
instr_seg (SEG7G, SEG7F, SEG7E, SEG7D, SEG7C, SEG7B, SEG7A)//最左端数码管显示指令号
/*为了提升视觉体验,没有用到的数码管全部设成空的*/
empty_seg (SEG6G, SEG6F, SEG6E, SEG6D, SEG6C, SEG6B, SEG6A,SEG5G, SEG5F, SEG5E, SEG5D, SEG5C, SEG5B, SEG5A,SEG4G, SEG4F, SEG4E, SEG4D, SEG4C, SEG4B, SEG4A,SEG3G, SEG3F, SEG3E, SEG3D, SEG3C, SEG3B, SEG3A)
/*四个标识符,对应进位/0/负/溢出*/
CF LD3
ZF LD2
SF LD1
OF LD0
/*右边三个数码管输出结果,如果是负数会显示负号哦~*/
value_seg (SEG2G, SEG2F, SEG2E, SEG2D, SEG2C, SEG2B, SEG2A,SEG1G, SEG1F, SEG1E, SEG1D, SEG1C, SEG1B, SEG1A,SEG0G, SEG0F, SEG0E, SEG0D, SEG0C, SEG0B, SEG0A)
/*左边的几个LED灯显示使能和运算结果的二进制表示,用作调试*/
value (LD13,LD12,LD11,LD10)
en LD15

seg模块复用了之前的模块,但是做了些许调整,以显示9,负号和空:

1
2
3
4
5
6
7
/*seg.v*/
case(b[3:0])
......
4'd9 : h = ~7'b1101111;
4'd10 : h = 7'b0111111;//负号
default : h = 7'b1111111;//空
endcase

接下来是alu的主体部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*top.v*/
module top(
/*输入输出声明*/
input [7:0]operand,
input [4:0]button,
output [6:0]instr_seg,
output reg CF,
output reg ZF,
output reg SF,
output reg OF,
output reg en,
output [20:0]value_seg,
output [27:0]empty_seg,
output reg [3:0]value);
wire [2:0]instr;
wire [3:0]a;
wire [3:0]b;
wire [7:0]value_num;
wire [3:0]value_sign;
/*将不需要使用的数码管置空*/
assign empty_seg = {28{1'b1}};
/*实例化:指令数码管,负号数码管,结果数码管,按钮控制器*/
seg seg7(
.b({1'b0,instr}),
.h(instr_seg)
);
seg seg0(
.b(value_num[3:0]),
.h(value_seg[6:0])
);
seg seg1(
.b(value_num[7:4]),
.h(value_seg[13:7])
);
seg seg2(
.b(value_sign[3:0]),
.h(value_seg[20:14])
);
button button0(
.button(button),
.instr(instr),
.en(en)
);

/*开关输入的操作数*/
assign {a,b} = operand;
/*为减法运算进行的取反,这里不能加一,为了防止Tmin的符号判断错误*/
wire [3:0]b_comp = {4{instr[0]}}^b;


/*操作数,使能,指令码之一变化,就重新输出结果*/
always @(operand or en or instr) begin
if (en) begin
case (instr)
/*加减法*/
3'd0, 3'd1: begin
/*不能把a+b_comp+{1'b0,instr}直接赋给{CF,value},否则当b=0时,b_comp+1会错误的进位*/
value = b_comp + {1'b0,instr};
{CF,value} = value +a;
/*计算溢出:操作数符号相同,结果与操作数符号不同*/
OF = (a[3] == b_comp[3]) && (value[3] != a[3]);
end
/*逻辑运算*/
3'd2 : begin value = ~a; {CF, OF} =2'd0; end
3'd3 : begin value = a & b; {CF, OF} =2'd0; end
3'd4 : begin value = a | b; {CF, OF} =2'd0; end
3'd5 : begin value = a ^ b; {CF, OF} =2'd0; end
/*关系运算*/
3'd6 : begin value = {3'b000,a<b}; {CF, OF} =2'd0; end
3'd7 : begin value = {3'b000,a==b};{CF, OF} =2'd0; end
endcase
ZF = value==0;
SF = value[3] == 1;
end
else begin
/*若使能为0,所有结果都为0*/
value = 0;
{CF, ZF, SF, OF} =4'd0;
end
end

/*value_show是value的绝对值*/
wire [3:0]value_show;
assign value_show = SF ? (~value+1) : value;

/*value_num高四位是十位数,低四位是个位数,value_sign是负号或空*/
assign value_num[7:4] = value_show / 10;
assign value_num[3:0] = value_show % 10;
assign value_sign[3:0] = 4'd11 - {3'b000, (value[3] == 1)};//为10,当为负.为11,当为正

endmodule

接下来是按钮控制模块,相当于有多个触发条件和触发行为的触发器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module button(
/*输入输出声明:输入中上下左右四个按钮的信号,输出指令编码和使能*/
input [4:0]button,
output reg [2:0]instr,
output reg en);
/*所有的五个按钮的上升沿都会触发指令或使能的改变*/
always @(posedge button[3]or posedge button[2] or posedge button[1]
or posedge button[0] or posedge button[4]) begin
/*按一下中间的按钮可以让使能取反*/
if(button[4]) begin
en <= ~en;
end
else begin
/*上:指令+2;下:指令-2;左:指令+1;右:指令-1;这四个按钮都会让使能归零*/
case(button)
5'b01000: begin instr <= instr+2; en <= 0;end
5'b00100: begin instr <= instr-2; en <= 0;end
5'b00010: begin instr <= instr+1; en <= 0;end
5'b00001: begin instr <= instr-1; en <= 0;end
default: en <= 1;
endcase
end
end
endmodule

总体而言,按中间的按钮会执行当前指令的运算,然后可以调节开关,观察结果.如果按上下左右四个按钮,会看到指令码变化,结果消失,需要重新按一次中间的按钮才会出现结果(就好像计算器的”等于”键)

实验五 寄存器组以及存储器

  • 分析三个输出端口的存储器实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      always @(posedge clk)
    begin
    if (we)
    ram[inaddr] <= din;
    else
    /*dout0在不进行写入的上升沿读取*/
    dout0 <= ram[outaddr];
    end

    always @(negedge clk)
    begin
    /*dout1在没有写使能的下降沿读取*/
    if (!we)
    dout1 <= ram[outaddr];
    end

    /*dout2永远直接读取输出信号*/
    assign dout2 = ram[outaddr];

    完成PA1第一阶段

    1. 开天辟地的篇章

  • 尝试理解计算机如何计算

    取指执行,以跳转实现控制流,以内存和寄存器实现变量

  • 从状态机视角理解程序运行

先把程序写出来:

1
2
3
4
5
6
7
// PC: instruction    | // label: statement
0: mov r1, 0 | pc0: r1 = 0;
1: mov r2, 0 | pc1: r2 = 0;
2: addi r2, r2, 1 | pc2: r2 = r2 + 1;
3: add r1, r1, r2 | pc3: r1 = r1 + r2;
4: blt r2, 100, 2 | pc4: if(r2<100) goto 2
5: jmp 5 | pc5: goto pc5;

然后是开头:

1
(0, x, x) -> (1, 0, x) -> (2, 0, 0) -> (3, 0, 1)

接下来的状态是:

1
2
(4, 1, 1) -> (2, 1, 1) -> (3, 1, 2) -> (4, 3, 2) -> (2, 3, 2) -> (3, 3, 3) -> (4, 3, 4)->...
->(4, 4851, 98) -> (2, 4851, 98) -> (3, 4851, 99) -> (4, 4950, 99) -> (2, 4950, 99) -> (3, 4950, 100) -> (4, 5050, 100) -> (5, 5050, 100) <-> (5, 5050, 100)

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment