Mac远程桌面到Linux服务器

假期结束,回到学校开始干活 :]

为了远程使用Linux服务器,折腾了一个下午。最终看来还是用vnc最简单了。

实验室有两台强劲的Linux服务器用来做研究。之前我一直都是用ssh登到服务器上去码代码,反应速度很快,感觉很不错。但是因为在做机器视觉,难免需要看远程的图片。命令行虽然快,总不能每次都把图片拷贝到本地再看,有时候需要可视化中间结果,ssh也行不通。

当本地机器是Linux系统的时候比较好办。可以用ssh加X forwarding的方法。在本地开一个X,然后把远程服务器的X指令通过ssh转发到本地的X,码代码没有什么延迟,感觉还是很不错的。

sudo X :11 vt11 2>&1 >/dev/null &

这样可以在本地新开一个X,Ubuntu下用Ctrl+Alt+F11可以切到第11个虚拟终端

回到之前的终端,开ssh和xterm

xterm -display :11 -e ssh -X server-host &

然后可以切换到第11个虚拟终端来使用远程Linux服务器上的X了。

这样虽然好,但是要求本地机器上有安装X。在Windows和MacOS下虽然有解决方法,但是比较麻烦。

用VNC的话就没有这个问题,毕竟VNC的客户端是很容易找的。

当然需要先ssh登录到Linux服务器上安装vncserver

sudo apt-get install vnc4server

然后启动vncserver

vncserver

这样就搞定了。

在本地的Mac下可以用自带的Screen Sharing App或者著名的Chicken of the VNC连接到server-host:5901来查看和控制远程Linux桌面。

在服务器上启动了vncserver之后,可以通过修改

~/.vnc/xstartup

这个文件,来指定远程的X启动之后要执行什么命令。我喜欢用openbox,所以我的xstartup文件就是这样子

#!/bin/sh

# Uncomment the following two lines for normal desktop:
# unset SESSION_MANAGER
# exec /etc/X11/xinit/xinitrc

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &
openbox-session&
#x-terminal-emulator -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
#x-window-manager &

另外,在启动vncserver的时候可以使用参数修改远程桌面的分辨率

vncserver  -geometry 1280x1024

这么一来就可以在命令行下写代码,同时再开一个vnc窗口看图,感觉不错!

这篇文章有列举一些远程到Linux服务器上的其它解决方法,如果有服务器的root用户权限的话,freeNX貌似也是一个不错的选项。

dup, pipe 和 fork

(好久没更新了,呼…一大波死线刚刚结束…)

我几乎一直在用Bash,可是却少有接触到Unix系的系统编程,对系统调用还是知之甚少。这两天实验室里讨论了一个比较基础的问题: 在自己写的程序中,怎么样得到另一个可执行文件的输出?

比如我们有/bin/pwd这个可执行文件,我们可以在自己的程序中用

system("/bin/pwd");

或者是

execl("/bin/pwd",".",NULL);

调用它。

如果想要得到它的输出,应该怎么做比较好?

这个其实是作业题的范畴,涉及到了几个Unix的系统调用:

     #include 

     pid_t
     fork(void);
     #include 

     int
     pipe(int fildes[2]);
     #include 

     int
     dup(int fildes);

     int
     dup2(int fildes, int fildes2);

手册里有各个函数的详细解释。
在这个例子里,pipe用来生成一对文件描述子 (file descriptor,我觉得描述子这个翻译不是很好…),往第二个描述子里写内容,从第一个里面读内容。fork用来得到一个子进程,这里,我们会让子进程来执行pwd,并且把输出写到pipe的一端,然后父进程可以从另一端读入。

但是pwd默认输出到屏幕,也就是标准输出,我们需要使用dup2,把标准输出指向pipe的一端,这样就可以完成任务了。

所以,最终的代码是这样的

#include 
#include 
#include 
#include 

int main(int argc, char **argv)
{
    int fd[2];
    int pid;
    pipe(fd);
    int rpipe = fd[0];
    int wpipe = fd[1];
    pid = fork();
    if (pid == 0)
    {   
        /* 子进程关掉读的那端,只用写的一端 */
        close(rpipe); 

        /* 把标准输入指向pipe的写的一端 */
        dup2(wpipe, STDOUT_FILENO);

        /* 执行pwd */
        execl("/bin/pwd",".",NULL);

        /* 嗯 */
        exit(0);
    }   
    else
    {   
        /* 父进程关掉写的那端,只用读的一端 */
        close(wpipe);

        printf("begin parent process\n");
        char readbuffer[1024];

        /* 从读的这端读出pwd的输出 */
        int nbytes = read(rpipe, readbuffer, sizeof(readbuffer));

        printf("Received string: %s | %d\n", readbuffer, nbytes);
        printf("end parent process\n");

        wait(&pid);
    }   
    return 0;
}

所以最后的输出就是像这样的

begin parent process
Received string: /Users/XXX/XXXX
 | 16
end parent process

话说fork除了考试之外,似乎就没有用过了,代码还是写得太少了…

(Image from Github)

Linux Swap文件

想象一下,两个实验进程跑了两天,还有一天就跑完了,这个时候你发现如果再跑一会儿内存就要爆了…怎么办? (好惊险的感觉 XD)

好吧,其实用到的只是很基本的操作系统知识,不过还真难得用到一回。

程序面对的都是虚拟内存。64位的操作系统下,虚拟内存非常大,但是实际物理内存相对而言小得多。所以,操作系统对内存分页 (就是分成一块一块的,每一块儿叫做一页) 物理内存一旦满了,把暂时不需要的页写到硬盘里。过了一会儿程序又要访问被写到硬盘里的那部分内容,操作系统就在物理内存中选一个页 (怎么选很讲究的),把硬盘里的那个给换回来。程序不停的运行,操作系统就换来换去…

所以,上面我们遇到的情形就可以解决了。把其中一个进程挂起 (suspend),Linux下可以用

Ctrl+Z

,然后这部分内存就是暂时不用的了。这个时候用

$top

查看内存使用情况,可以看到一个CPU占用率为0的进程占用的内存越来越少,另一个越来越多。这样就行了,等一个进程跑完,再用

$fg

命令把挂起的进程调到前台就可以了。

但是等等。虚拟内存具体是在哪里呢?数据终究是写在内存/硬盘上的,Linux下被换到硬盘上的内存在Swap分区 (交换分区) 里。安装系统的时候需要格式化一个分区为Swap格式,就是这个分区。

$swapon -s

可以查看交换分区的大小。

糟糕!刚才那个被挂起的进程占用了24G的内存,但是现在看到我的交换分区只用12G,怎么办?一旦交换分区和内存都满了,会发生神马事情,我也没有体验过,估计应该是系统卡死或者卡而不死吧。

所以,应该赶紧增加交换分区的大小才是。可是如果你和我一样,很悲催的没有Root权限 (Root权限貌似是必须的…),而且也根本没有多余的分区可以挂载了,怎么办?

可以用Swap文件 (点题) ! 就是把一个文件用做swap分区,Linux下什么都是文件,分区应该也是吧。要增加系统可用的虚拟内存,当然这要求你硬盘剩余空间够大…

$dd if=/dev/zero of=~/swapfile bs=1024 count=41943040

会在HOME下创建一个40G的文件”~/swapfile”,命令要执行一会儿,需要写一段时间硬盘,执行完了会显示写硬盘的速度,可以用来做测速的。
然后告诉系统用这个文件做交换文件

mkswap ~/swapfile

就没问题了。
(实际上不行,还需要这个命令, –!)

sudo swapon ~/swapfile

其实最一开始那个情形下,如果两个进程继续跑下去,操作系统仍然会把一部分内容换出来的。只是大量换页操作会让程序执行的时间更长,而且如果交换空间不够大,系统最终仍然可能会被卡死。

感叹一下,一个实验要跑三天的同学伤不起啊..

Linux Tips (1)

这里记录一些小技巧,比较杂。

1. bash下,x{a,b}会被展开为xa xb,很适合文件备份。
如果你要复制一份a.txt作为备份,到a.txt的路径又太长了,这么写比较方便。

cp /a/long/long/long/path/to/file/a.txt{,bak}

2. vim下,要把某些内容替换成为行号,可以用\=line(“.”)来处理。“.”用来连接行号和其它内容。

:%s/xxxx/\=line(".") . " "/g

3. bash脚本下,如果要写多行到文件里,可以用

cat > file << EOF
file content line 1
file content line 2
file content line 3
file content line 4
EOF

这是一种固定写法,从第二行起,遇到EOF就停止,把之前的内容写到file里。EOF也可以换成别的。

4. Trap SIGINT的时候记得要处理SIGINT信号。

#! /bin/bash

trap "echo 'hello'; exit 1" SIGINT SIGTERM

while [ 1=1 ];
do
    sleep 1
done

exit 1这部分是必须的,否则虽然按下Ctrl + C的时候会显示hello,但是脚本不会被终止。

Setup dropbox without X

Assume you are accessing your server remotely through SSH.

1. Download the package, say you are using Ubuntu

$wget https://www.dropbox.com/download?dl=packages/ubuntu/dropbox_1.4.0_amd64.deb

2. Install the package

$sudo dpkg -i *dropbox_1.4.0_amd64.deb

3. Start Dropbox

$dropbox start

4. You will get an url to link your computer here, copy-and-paste into your web-browser. Restart your dropbox, all set :]

$dropbox stop
$dropbox start

I tried to open the url with w3m or lynx inside the terminal but failed.
It seems they don’t support Javascript well.

tail -n

Quite useful trick, when you want to get rid of the first certain lines of the output/file.

$tail -n +2

tail prints out the result starts from the 2nd line.

When you use the “find” command to generate a file list under certain folder, this is quite easy to eliminate the folder path at the beginning of the output.

Learned this from SO How can I remove the first line of a text file using bash/sed script?

Xresources File

感觉用Linux的一大好处就是纯文本的配置文件决定一切。
今天在一个新机器上搭自己的开发环境,
apt-get(墙外的网络真快,也用不着去辛苦地找源)一下,
把几个配置文件拷过去,就和老机器一样了。

基本上用到的就是openbox的配置, vimrc, screenrc, bashrc和Xresources

很奇怪的是Xresources拷过去不工作,google到了这个X resources

应该是因为没有起Xorg,~/.Xresources没有被自动地处理,所以需要在~/.xinitrc里面增加一行

xrdb -merge ~/.Xresources

重开一下X就行了。

MacPorts

经历了几天没有网络的日子,总算在学校初步安顿下来了。图书馆不错,网速很快 🙂

刚发现Mac下的一个好东西 MacPorts, 手册在这里MacPorts Guide

相当于Linux下的包管理工具吧,可以自动处理依赖。要安装软件的时候,一行命令解决问题。

$port install XXX

各种用法手册里都介绍了,手册足够长,完全没有耐心一次看完..

静态链接库

静态链接库

几个例子,使用和建立静态库的时候的几种常见情景。

首先建几个文件
a.h

void testA();

a.c

#include "a.h"

void testA() {
    printf("A");
}

b.h

void testB();

b.c

#include "b.h"
#include "a.h"

void testB(){
    testA();
    printf("B");
}
  1. 得到目标文件
  2. $gcc -c a.c
    
    $gcc -c b.c
    
  3. 得到静态库文件
  4. $ ar -r libba.a a.o b.o
    
    $ ar -r liba.a a.o
    
    $ ar -r libb.a b.o
    

得到了库libba.a libb.a liba.a

这是最终需要完成的程序。
test.c

#include "b.h"
void main()
{
    testB();
}

假设我们没有a.c,b.c这两个源文件。
在这种情况下,为了编译test.c得到可执行文件,至少需要那些文件?

由于b.h中包含了a.h,所以两个头文件都是需要的。

  1. 用libba.a
  2. $ gcc test.c -o test -L. -lba
    

    我们需要的是testB()和testA()的实现,libba.a里显然有这些实现,所以是可行的。

  3. 用libb.a 和 liba.a
  4. $ gcc test.c -o test -L. -lb -la
    

如果一开始就没有a.c,怎么办?
假设liba.a是一个第三方的闭源库,我们只有liba.a和a.h
当然,为了得到test这个可执行文件,我们也可以使用上面第二种方式编译。

如果我们不是要得到test,而是要为其它人提供libb.a怎么办?
上面的libb.a只含有testB()的实现,而没有testA()的实现。
除非“其它人”也有liba.a,否则只用libb.a是没有办法使用testB()这个函数的。

那么是不是可以把liba.a链入libb.a?
对于静态库来说,是可以的。这也就是我们的解决办法。
-r 告诉Linker进行增量式链接,得到二进制目标文件,而不是最终的可执行文件。

$ ar -r libnba.a ba.o

这个libnba.a和前面通过

$ ar -r libba.a a.o b.o

得到的libba.a应该是一样的。

这时我们就可以把libnba.a提供给”其它人”了。

$ gcc test.c -o test -L. -lnba

可以得到test可执行文件。

[Ruby]debian上更新gem

用Rails的时候需要配环境,配环境的大头是安装gem,安装gem的时候就会遇到各种令人崩溃的问题…Orz…

环境是Debian,需要安装refinerycms,出现了gem版本过低的错误。
比较通用的做法是

$gem update --system

但是可能会遇到这个错误

ERROR:  While executing gem ... (RuntimeError)
    gem update --system is disabled on Debian. 
RubyGems can be updated using the official Debian
repositories by aptitude or apt-get.

这种情况下就需要自己动手。

这里需要安装的是gem 1.3.6的版本。

先得到rubygems-update-1.3.6.gem,可以直接下载得到

$wget http://gems.rubyforge.org/gems/rubygems-update-1.3.6.gem

然后安装这个叫作rubygems-update的gem

$sudo gem install rubygems-update-1.3.6.gem 

也可以

$sudo gem install rubygems-update -v=1.3.6

然后执行update_rubygems这个脚本
这个文件的位置由之前gem的设定而定。

$which update_rubygems 
/home/XXX/.gem/bin/update_rubygems

执行这个脚本update_rubygems

$sudo /home/haoxiang/.gem/bin/update_rubygems

如果遇到错误

/usr/local/lib/site_ruby/1.8/rubygems.rb:777:in `report_activate_error': Could not find RubyGem rubygems-update (>= 0) (Gem::LoadError)
	from /usr/local/lib/site_ruby/1.8/rubygems.rb:211:in `activate'
	from /usr/local/lib/site_ruby/1.8/rubygems.rb:1056:in `gem'
	from /home/haoxiang/.gem/bin/update_rubygems:18

可以试着先

$su

以root身份执行

$/home/haoxiang/.gem/bin/update_rubygems