• 02
  • 3月

为了取出我那笔奖学金,今天在工行折腾了大半个下午。最终多亏工行系统一个小小的后门才将其搞定。

这件事的起源得追究到我家那的派出所,给我办身份证时居然把关键的身份证号给我打错了。因为这个错号,大学统一办的那张交学费的银行卡身份信息也是错的。我大一发现这个问题后用新身份证办了张新卡。几年下来学校发那张卡也不知道扔哪去了。

没了学校发那张卡,除了学费要亲自到财务上交之外,一直没出什么问题。直到上学期,学校突然改变了发奖学金的方式,不再发现金,而是直接划到学校发的卡上。于是为了把这笔奖学金取出来,可费了我不少时间。

18位身份证号最后是有校验位的,因此即便是银行的工作人员查看银行卡信息,能确认我是那张卡的主人,但因校验通不过,工行的电脑系统坚决不给我办理任何业务——不能挂失,无法补卡,连网上银行都办不出来。通过银行的正常手续,我完全无法把这笔钱取出来。

一筹莫展的时候,银行的工作人员帮我想出了一个“后门”——学校的一卡通系统。我们学校的一卡通是关联到工行的银行卡上的,可以用学校里的自助转账机把关联工行卡中的钱转到一卡通中。而这个关联操作是不需要刷银行卡的,认证工作完全由人工保证。

于是利用这个“后门”,我把那张丢失卡中的所有钱都转到了一卡通之中。银行的工作人员又帮我联系了一下学校的一卡通管理中心,最终将钱从一卡通中提了出来。

虽然工行效率之低让我非常不爽,但还是得谢谢这些工作人员,总算是把我的钱弄出来了。并且一分钱手续费没花。

事后,我在想。如果工行没有一卡通关键这一不完全受计算机控制的后门,我该怎么办?也许逐层上报,到哪一层可能有权力跳过计算机监管或有权力执行程序的“例外”路径(如果有的话),最终补出一张卡来。但这就不知道得等到猴年马月了。也许我到淘宝上看看,有没有传说中的知道卡号就能做出张银行卡的“特殊服务”,花点钱办个“兼容卡”?

而对于那个强悍的工行计算机系统,在信息化大行其道的今天。我们也不能不看到信息化的一个重大缺点——过分死板。程序只能按照设计时所定下的流程走,一点也不能通融。在绝大部分时间里,这确实是计算机系统的一个优势,但凡事总有例外,一但出现了像我这样的特殊情况,就会变得特别麻烦。

联想到上周帮人擦屁股的那个破系统,也不过是因为实际业务发生了一点点改变——本来由该系统处理的一类资金转给银行处理了。系统只需记录之前的数据就可以了。但这套系统还固执的每月汇总这些数据,每季度为它增加利息。导致系统显示的余额大于实际余额。

设计系统确实应该多为变化和例外考虑一下,留一两个安全的“后门”确实有必要。当然,这绝对不是一件简单的事。

多谢工行的这个后门了!

  • 26
  • 2月

昨天被老师叫去给一个系统擦屁股,看到了一段实在是让我很无语的程序。不批一下难解我心头之不爽。

其实就是一个非常简单的功能:从数据库中读出一段时间内的流水,起始时间:(ayear, amonth, aday),结束时间:(byear, bmonth, bday)——变量的命名已经反映了这段程序的质量。

因为要从两张表里读数据,此仁兄居然写出了如此“高技术含量”的代码,大体如下:

for(int i=ayear;i< =byear;i++)
{
	if(i-ayear>0)
	{
	amonth=1;
	}
	for(int j=amonth;j< =12;j++)
	{
		if((i-byear==0)&&j>bmonth)
		break;
		for(day=1;day< =31;day++)
		{
			sql="select * from lxd where jzrq ='"+String.valueOf(i)+"-"+String.valueOf(j)+"-"+String.valueOf(day)+"' and qymc='"+qymc+"' and qyzh='"+qyzh+"' and xhflag is null";
			// 略去中间输出用代码

			sql="select * from dwls where sj = '"+String.valueOf(i)+"-"+String.valueOf(j)+"-"+String.valueOf(day)+"' and dwmc='"+qymc+"' and qyzh='"+qyzh+"' and del is null "+order;
			// 略去中间输出用代码
		}
	}
}

真不知此仁兄的数据库原理一课是怎么学的,为了把两张表的数据凑在一起居然写出了这么一陀代码。试问有哪本讲SQL的书中会不教联合查询。上面这陀东西不就是实现了这么点功能吗?

select * from lxd inner join dwls on lxd.jzrq=dwls.sj where lxd.jzrq between A and B

一个是查N天的数据生成2*N条SQL语句,另一个则不管查多少天一条搞定。

早就听说过一个说法,一个好的程序员和一个差的程序员写出来的代码效率会有上千倍的差距。此话果然不假。

  • 17
  • 12月

刚刚在Ubuntu8.10下安装了Eclipse,结果在其中运行之前写的那些swing程序一运行就假死在那。所有界面元素都不响应,只能点窗口的关闭按钮强制退出。但自己在终端里用java命令运行则正常。

研究了半天算是找到了问题所在——虚拟机版本。Ubuntu源里默认安装的Eclipse使用的是java-1.5.0-gcj的虚拟机运行java程序。在Eclipse中选择Window-Preferences,在左侧找到Java-Installed JREs,点右侧Search按钮,定位到/usr/lib/jvm即可让Eclipse找到系统中安装的其他JRE,在选择java-6-sun后问题解决。

  • 26
  • 4月

今天很高兴成功写出了生平第一个汇编程序,程序很简单:从键盘读入一个数字,判断正负,正数输出1,负数输出-1,0时输出0。

因为汇编不是高级语言,所有的分枝都得靠跳转指令。于是老老实实地用流程图画了一下,别看就这么点功能,但输入的数字得一个一个地接收、判断(想起有穷状 态自动机来了,不知道这能不能算它的一种实现)。输出的时候也要根据正负、是否为0判断。最终这么一个小程序居然用了6处判断跳转!用C写2个if就可以 搞定的东东嘛……呵呵。

按照《代码大全(Code Complete)》上建议的方法,先把程序思路用注释全部写到源文件中去,然后一条条展开成代码,最终经过调试得到了以下可以成功运行的代码:

CODE  SEGMENT
      ASSUME    CS:CODE
START:                        ;init
      XOR       AX ,AX
      MOV       BX ,0030H   ;bh-negative flag, bl non-zero flag
      XOR       CX ,CX      ;input flag
INPUT:                        ;input a charactor
      MOV       AH,01
      INT       21H
      CMP       AL,1BH
      JE        RETURN      ;press ESC to exit
      CMP       AL,0DH
      JE        OUTPUT      ;if it's x0d, jump to OUTPUT
      TEST      CL,01H
      JNE       COMMON      ;if it's not the first charactor, jump to COMMON
      MOV       CL,01H      ;set input flag
      CMP       AL,'-'
      JNZ       COMMON      ;if it's not '-', jump to COMMON
      MOV       BH,01H      ;set negative flag
      JMP       INPUT       ;jump to INPUT
COMMON:
      CMP       AL,'0'
      JE        INPUT       ;if it's '0', jump to INPUT
      MOV       BL,31H      ;set non-zero flag
      JMP       INPUT       ;jump to INPUT
OUTPUT:
      MOV       AH,02H
      MOV       DL,0AH
      INT       21H         ;start a new line
      CMP       BL,'0'
      JE        NZ          ;if it's 0, '-' is of no use
      CMP       BH,01H
      JNE       NZ          ;if negative flag is false, jump to NZ
      MOV       DL,'-'
      INT       21H         ;print '-'
 NZ:  MOV       DL,BL
      INT       21H         ;print the non-zero flag
      MOV       DL,0AH
      INT       21H
      MOV       DL,0DH
      INT       21H         ;move cursor to a new line
      JMP       START       ;get the next number
RETURN:  RET
CODE  ENDS
      END       START

PS:期间犯了两个错误,记录一下
1.忘记了 ASSUME CS:CODE 这一句,没了代码段,造成了所有的跳转语句都报:“ Near jump or call to different CS”错误。没指定自己的代码段在哪里,一跳可不是to different CS嘛!
2.代码最后没有加RET,一接收完输入就报莫名其妙的错误。汇编毕竟是汇编,最后的CODE ENDS和END START只是给编译器看的。程序执行完毕,返回操作系统的指令也得写明。

搞定之后小研究了一个EXE文件的结构,找到一篇不错的文章: com和exe文件结构

  • 26
  • 2月

本文讨价如何在Ubuntu7.10下架设配合apache工作的subversion服务器端及客户端的使用。

如果你只想用Subversion进行版本管理,不想安装apache服务器,则只需要安装subversion一个软件包,参考《Ubuntu:Subversion服务器安装设置》一文中的6.4节,使用svnserve直接进行subversion服务。

使用apache上架设的subversion服务器最大的好处就在于即使没有安装客户端也能通过浏览器查看最新的版本。并且可以做到对所有的版本库使用 统一的用户名密码进行访问,这对于像我这样的版本库主要为个人使用来言是非常方便的,而且必要的时候也可以对单一版本库单独设置权限。

Ubuntu7.10下安装Subversion服务相当简单,在已经安装apache服务器的情况下执行:

sudo apt-get install subversion libapache2-svn

就可完成服务器及标准客户端的安装。

在终端执行:

sudo gedit /etc/apache2/mods-available/dav_svn.conf

在打开的文件末尾加入:

<Location /svn>
DAV svn
SVNParentPath /media/hda7/svn
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /etc/subversion/passwd
Require valid-user
</Location>

其中第一行中“/svn”是版本库在URL的路径,这样在访问“http://127.0.0.1/svn”时就不会去找WWW下的文件夹,而是执行这里设定的SVN操作。

第三行中“/media/hda7/svn”是我之前在Windows下放版本库的地方,每一个版本库都在其下建立一下子文件夹。所以前面写的是 “SVNParentPath”,如果只有一个版本库想直接指向里面,请使用“SVNPath”替换“SVNParentPath”,并将后面的路径直接 指向作为版本库的文件夹。

倒数第三行定义了存储登录验信息的passwd文件的位置。

在终端中使用

sudo htpasswd -c /etc/subversion/passwd test

创建第一个用户test,并按照提示为其设置密码。以后增加用户时使用

sudo htpasswd /etc/subversion/passwd 新用户名

保险起见,执行

sudo /etc/init.d/apache2 restart

重启apache服务器。

现在用浏览器打开http://127.0.0.1/svn应该会有提示输入用户名和密码。

创建新的Subversion软件仓库,在之前设定的放置软件仓库的文件夹(这里是“/media/hda7/svn”)内新建一个文件夹,如test,然后执行

 $ sudo svnadmin create /media/hda7/svn/test

将其制作成软件仓库。这时就可以通过http://127.0.0.1/svn/test对其访问。

Subversion客户端上,用Eclipse有SubEclipse插件可用,NetBeans6自带支持。

独立使用的客户端linux上没有tortoise,只能用RapidSVN,比较工具配合Meld。两个软件在Ubuntu的“添加/删除”软件里都能找到。使用方法就不多说了。

  • 19
  • 2月

实现Java中TextField或JTextField输入内容后按回车进行处理,只需在TextField或JTextField上简单地绑定一个ActionListener:

JTextField testField=new JTextField();
testField.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent event){
        // 处理焦点在文本框中按回车键的事件
    }
});
  • 19
  • 2月

Java对String的操作提供了两个类StringBuffer和StringBuilder(我是说操作,如果你直接用String对象进行大量的字符串操作,我只能说声佩服)。为什么要提供这样两个类呢?

查阅JDK得知:StringBuilder是在java1.5中新加的类,在使用上与StringBuffer主要的区别只是StringBuilder不是线程安全的,在编写非多线程程序时使用StringBuilder速度能快一些。

与此类似的还有Vector和ArrayList,同样前者是线程安全的,后者是在JDK较新的版本中加入的非线程安全的实现(java1.2时加入的)。

其实只要不是在太大规模和程序上使用,是否线程安全对运行的影响是很难看出来的。而且如果你根本不知线程为何物的话,选择非线程安全的类一般不会有任何问题:)

参考:

  1. 是 String,StringBuffer还是StringBuilder?
  2. 精辟的String与StringBuffer(StringBuilder)的区别
  • 16
  • 1月

上周我们进行了最后一周的课程:操作系统课程设计。

这个名字听起来挺吓人,不过实际上比期中的那个系统软件分析与设计简单多了。老师出的题目不过是让实现一个多线程的演示程序。由于只是个演示用途,也没有限制开发环境。但有一点特殊的是这次课程是分组做的,老师按照学生名单每相邻三个人分为一组,完成同一个任务。我被分到和班里学习最好的两个女生一起完成第一道题:演示对两个缓冲区的put、move、get操作。

说实话,不过是一个简单的演示程序。后面用Java的Lock和Condition实现一个buffer的model,用swing做一个图形界面,再写个controller就可以了。上学期编的那个连连看才花了我四天时间。但现在我要和两个女生合作,这个难度可就高了一个数量级。不过毕竟是团队嘛,如果我什么都不管,自己单干的话确实也不是很好(我一哥们选择了这条路,自己单独完成一道题)。

一周的课设下来,大概花了一半的时间解释和讨论程序的思路和实现方法,一半的时间用在编码上。总算在让大家都基本明白程序是怎么回事的基础上完成了设计。而且体会到了给别人讲解确确实实是提升自己理解能力的非常好的方法。

以下是我自己写的缓冲区类,存放自定义的数据类型Data,这个类型里只包括一个整型的id和Color型的随机颜色信息:

package model;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author riqe
 * buffer
 */
public class SynchBuffer {

	private Data[] buffers;
	private int head;
	private int rear;
	private Lock bufferLock;
	private Condition puttable;
	private Condition takable;
	public Statistic statistic;

	public SynchBuffer(int size){
		buffers=new Data[size+1];
		head=0;
		rear=0;
		bufferLock=new ReentrantLock();
		puttable=bufferLock.newCondition();
		takable=bufferLock.newCondition();
		statistic=new Statistic();
	}

	/**
	 * Put data into the buffer.
	 * It will cause the calling thread blocked
	 *  when the buffer is being operated or full.
	 * @param value data to put into the buffer.
	 * @param delay millisecond to delay after put the data in.
	 */
	public void put(Data value,int delay){
		bufferLock.lock();
		try{
			while((head-rear+buffers.length)%buffers.length==1)
				puttable.await();
			buffers[rear]=value;
			rear=(rear+1)%buffers.length;
			statistic.addPutCount();
			takable.signalAll();
			Thread.sleep(delay);
		}catch (InterruptedException e) {

		}finally{
			bufferLock.unlock();
		}
	}

	/**
	 * Take data out of the buffer.
	 * It will cause the calling thread blocked
	 *  when the buffer is being operated or empty.
	 * @param delay millisecond to delay after take the data out
	 * @return
	 */
	public Data take(int delay){
		Data getValue=null;
		bufferLock.lock();
		try{
			while((buffers.length+head-rear)%buffers.length==0)
				takable.await();

			getValue=buffers[head];
			head=(head+1)%buffers.length;
			statistic.addTakeCount();
			puttable.signalAll();
			Thread.sleep(delay);
		}catch (InterruptedException e) {

		}finally{
			bufferLock.unlock();
		}
		return getValue;
	}

	/**
	 * clear up the buffer.
	 */
	public void sweep(){
		bufferLock.lock();
		rear=head;
		bufferLock.unlock();
	}
}

附上这次的成果,jar文件,安装Java Runtime Environment双击即可运行。
双缓冲操作显示

  • 18
  • 12月

花了一周的时间折腾Ubuntu玩,觉得该干点正事了。于是决定在上面搭建linux0.11的编译实验环境,把上次改内核时没搞定的问题搞定。做一个真正的最小化内核。

首先想到的就是《分享我的0.11实验环境》这个帖子,课上老师给的windows下的实验环境就是这里下载的:
http://oldlinux.org/oldlinux/viewthread.php?tid=3850&extra=page%3D1
于是下载它的linux实验环境。

不过由于作者使用的是Debian Sid,而且时间过去实在也很久了。在最新的Ubuntu7.10上使用会遇到很多问题。经过一番google之后终于将其搞定。总结过程如下:

1.下载linux-0.11-lab_060618_041042.tar.bz2并将其内容解压到用户主文件夹,如:/home/riqe/linux-0.11-lab

2.安装必须的软件build-essential(编译环境)、bin86(编译bootsect和setup)、mtools(处理软盘镜像)、bochs(虚拟机)

sudo apt-get install build-essential bin86 mtools bochs bochs-x

3.修改Makefile。GCC4.1 默认检查stack安全,会导致出现__stack_chk_fail错误。分别打开source/linux/kernel/Makefile、source/linux/kernel/chr_dev/Makefile、source/linux/fs/Makefile三个文件,在CFLAGS参数中加入-fno-stack-protector,变为:

CFLAGS	=-Wall -fstrength-reduce -fomit-frame-pointer -fno-stack-protector
	 -nostdinc -I../include

这样就已经可以按照说明执行make了。make过程中会产生一大把警告,但只要没有错误就不影响最后的结果。

4.修改mcopy_kernel、edit_menu.lst、linux-0.11文件,将它们第一行的

#!/bin/sh

改为:

#!/bin/bash

这样执行的时候就不会报错了。

5.修改conf/0.11.bxrc找到以下内容:

#ata0-master: type=disk, path="images/hdimage", mode=flat, cylinders=121, heads=16, spt=63

在下面添加一句:

ata0-slave:type=disk, path="images/c.img", cylinders=410, heads=16, spt=38

然后复制一份images下的hdimage-devel.img,重命名为c.img,再放回到images下。以解决0.11内核在bochs2.3下启动时的“HD controller not ready”问题。(原文及另一个解决方法

经过这么一番修改,已经完全可以在Ubuntu下的gcc4.12和bochs2.3下编译及运行linux0.11。截图如下:

直接在系统下编译的速度果然比在bochs下不知快了多少,而且还可以用安装了taglist等插件的gVim方便地进行代码查看和修改,爽!

PS:偶然遇上了在Windows下编译内核的帖子,怎么当初在交实验报告前没看见呢?唉……

  • 13
  • 12月

上次说到一个普通的接收用户输入的操作,居然成了整个课设的难点。确实是相当让人头痛。

不过顺藤摸瓜,亦步亦趋的方法在最坏的情况下总是能解决问题的。

用户输入用的是键盘,内核肯定要处理键盘操作的。那就从键盘操作的最底层找起。于是找到了键盘中断的底层处理代码keyboard.s,根据调用关系找到调用它的tty_read函数。继续向调用关系树的上方找的时候发现内核中没有函数调用tty_read。那好,就分析tty_read吧。

仔细分析了一下这个函数,其实功能很简单,其实不过是把read_q里的东西倒腾到secondary里,顺便做了一些必要的转换工作罢了。而read_q中的字符就是由键盘中断读取到的键盘输入。在没有输入函数的时候,最简单的方法就是循环检测这个队列,看看有没有输入回车。

理论分析很快就搞定了,但在实际修改的时候又遇到了很多麻烦。

最开始犯了一个错误是把读取输入队列的函数放到了处于用户态的进程1之中,弄出了一个困扰了我好几天的奇怪问题:可以正常读取队列头指针处的键盘输入,但只要读完之后increase一下头指针,下一次读取就再也得不到新的键盘输入了。最后发现只要把这些代码放入main函数MOVE_TO_USER_MODE之前就OK了。产生这个问题的具体原因我至今还不是非常明白。估计是遇到了内核的保护机制,内核区的内存对用户区的程序是只读的,若用户态程序修改时会给它一个副本(?)(在内存页式管理中实现的?)。

另一下问题则更另人郁闷。实验环境是Windows下跑的bochs里面运行的字符界面的linux0.11。对文件所有的修改都是在linux0.11下的vi里做的,编译也是用里面的make。vi好在已经在Windows下用过一个学期了(gVim)问题不大,但make对我来说可是个完全陌生的东西。这下在完成第二个要求的时候就遇到了很大的麻烦:虽然我基本分析明白了生成最小内核所需的文件,但只要小改一下代码或Makefile文件,make一下就会生成好几屏的错误报告。而字符界面的linux没有滚屏功能,这下我根本弄不明白到底最开始是哪里出了错误。为了减小代码,最终我不得不采取了一个极其笨拙、极其不geek的办法:把用不到的函数一个个清空内容。

就这样,好不容易在验收前那个上午把东西搞定了。验收时发现,全系只有我一个人用的是改内核代码的方法成功的。其他人有用汇编自己写了一段代码搞定的(体积是够小,但与linux还有什么关系?),另外大部分人居然用的是自己编写一个实现输入输出的程序,替换sh让内核启动时执行(这个与内核就更没什么关系了)。

看到了自己的成果与收获,小小骄傲一下。呵呵

[tags]linux,编程[/tags]

所有标签:.net Ajax Java javascript Linux map MySQL RSS TD-SCDMA Ubuntu vim web Win7 乱码 基础知识 备份 奥运会 希望泉 性能 缓存 编程