mobaxterm

一、安装程序,太方便,在windows中完全模拟linux风格

[Administrator.WINDOWS-LGJ801D] ➤ apt-get install tcl
Trying to download file setup.ini
wget: read error: Connection reset by peer
Trying to download file setup.ini
Downloading /home/mobaxterm/.aptcyg/mobasetup.32.ini using Windows internet settings
Updated setup.ini
Found package tcl

Installing tcl
Downloading tcl-8.6.8-1.tar.xz...
Downloading /home/mobaxterm/.aptcyg/release/tcl/tcl-8.6.8-1.tar.xz using Windows internet settings
Unpacking tcl-8.6.8-1.tar.xz...
Extracting dependencies for usr/bin/libtcl8.6.dll...
Extracting dependencies for usr/bin/tclsh8.6.exe...
Package tcl requires the following packages, installing cygwin libgcc1 tzdata zlib0
Package cygwin is already installed, skipping
Found package libgcc1
Package libgcc1 is already installed, skipping
Found package tzdata
Package tzdata is already installed, skipping
Found package zlib0
Package zlib0 is already installed, skipping
Package tcl installed.
Rebasing new libraries

[2020-09-06 08:33.27]  ~
[Administrator.WINDOWS-LGJ801D] ➤ apt-get install expect
Found package expect

Installing expect
Downloading expect-5.45-3.tar.xz...
Unpacking expect-5.45-3.tar.xz...
Extracting dependencies for usr/bin/expect.exe...
Extracting dependencies for usr/bin/libexpect5.45.dll...
Package expect requires the following packages, installing cygwin tcl
Package cygwin is already installed, skipping
Found package tcl
Package tcl is already installed, skipping
Package expect installed.

Rebasing new libraries

Linux服务器安全之 fail2ban的安装与配置

缘起

作为一个程序员,自然知道服务器的安全性,知道总有好事这人,喜欢无聊地扫描别人的端口。
  因忙于其它功能,只是将密码设置的稍微复杂了点。今晚登录,发现界面提示,刚刚有一波登录尝试,显然有人想暴力破解我的密码。
    一方面,立即对网站进行备份,重要数据复制到备份盘
   另一方面,安全问题不得不立即解决了,于是问了一下度娘,找到如下两篇文章

一、Linux服务器(CentOS6)安全之 fail2ban的安装与配置

[文章来源](https://www.cnblogs.com/bestOPS/p/10616401.html)

因本人服务器是CentOS7.8,尽管这里讲的是针对 nginx 的配置,而本人服务器是基于 docker lamp 的 wordpress 环境。但是本人比较习惯使用 nginx 系统,所以这套配置流程,就抄录过来。
  原作者如有意见请留言,本人将立即清除

查看Nginx的访问日志中,发现了大量404请求,仔细研究一番发现有人正在试图爆破网站。

于是乎 --- 查资料后得知有一个神奇的工具 fail2ban 可以配合 iptables 自动封IP
iptables 是Centos6 的一种防火墙策略,在Centos7 中已被 Firewall所取代.
本次实验系统为Centos6

1、下载并安装fail2ban

cd /usr/loca/src
 wget https://github.com/fail2ban/fail2ban/archive/0.9.4.tar.gz
 tar -zxf 0.9.4.tar.gz
 mv 0.9.4 fail2ban-094
 cd fail2ban-094
 ./setup.py

2. 目录结构

/etc/fail2ban                    # fail2ban 服务配置目录
/etc/fail2ban/action.d           # iptables 、mail 等动作文件目录
/etc/fail2ban/filter.d           # 条件匹配文件目录,过滤日志关键内容
/etc/fail2ban/jail.conf          # fail2ban 防护配置文件
/etc/fail2ban/fail2ban.conf      # fail2ban 配置文件,定义日志级别、日志、sock 文件位置等

cd /etc/fail2ban                 # 进入配置文件所在的目录
cp jail.conf jail.local          # 备份配置文件

3、 配置防护

vim /etc/fail2ban/filter.d/nginx.conf

[Definition]
failregex = <HOST> -.*- .*HTTP/1.* 404 .*$
ignoreregex =


保存后测试配置文件相对于日志是否正确

fail2ban-regex /home/wwwlogs/access.log /etc/fail2ban/filter.d/nginx.conf 


然后将封禁规则加入配置文件

vim /etc/fail2ban/jail.conf   # 文件尾部写入

[nginx]
enabled = true
port = http,https
filter = nginx                          # 规则文件的名字
action = iptables[name=nginx, port=http, protocol=tcp]
        # sendmail-whois[name=tomcat, dest=abc@mail.com] 发送邮件功能
logpath = /home/wwwlogs/access.log     # 日志路径
bantime = 14400                           # 封禁的时间
findtime = 3                          # 在几秒内
maxretry = 2                           # 有几次

4、 启动

/etc/init.d/iptables start
/etc/init.d/fail2ban start 或 fail2ban-client start

5、 查看状态等命令

fail2ban-client start # 启动
fail2ban-client reload # 重载
fail2ban-client stop # 停止
fail2ban-client status # 查看状态
fail2ban-client set nginx addignoreip 180.158.35.30 # 将ip加入nginx监狱的白名单
fail2ban-client set nginx delignoreip 180.158.35.30 # 将ip移除nginx监狱的白名单
fail2ban-client set nginx banip IP地址 # 手工ban
fail2ban-client set nginx unbanip IP地址 # 手工解

6. 查看fail2ban的日志

fail2ban的日志默认在 /var/log/fail2ban.log
日志中记录着Ban IP的记录和UnBan IP的记录, 也有修改配置文件重载后的记录
cat /var/log/fail2ban

7、 小结

fail2ban 还有许多强大的功能待研究,在 /etc/fail2ban/filter.d 中,有许多模板可以学习并使用
虽说把非法请求封禁一段时间看似安全些,但只要有对外的端口就不能保证服务器的绝对安全。
多了解不同工具的组合使用,才会使服务器更加安全。

二、Linux 使用fail2ban+Firewalld来阻止恶意IP,提高服务器安全。

文章来源

CentOS 7安装fail2ban+Firewalld防止SSH爆破与CC攻击说明:
   差不多很多博主都会遇到被CC攻击和SSH爆破的情况,这个时候就需要做下防御措施了,
   前几天发过一个防CC脚本,参考: https://www.moerats.com/archives/484/ ,不过对于CentOS 7来说,
   由于防火墙的因素,配置起来还是有点麻烦的,这里争对CentOS 7再分享个简单防CC攻击和SSH爆破的方法。

0、前言

fail2ban可以监视你的系统日志,然后匹配日志的错误信息执行相应的屏蔽动作。
网上大部分教程都是关于fail2ban+iptables组合,考虑到CentOS 7已经自带Firewalld,所以这里我们也可以利用fail2ban+Firewalld来防CC攻击和SSH爆破。
本教程编辑文件使用vi命令,如果不会用的,可以使用比较简单的nano,可以参考: https://www.moerats.com/archives/485/ ,或者使用FTP工具,如WinSCP登录VPS操作。

准备工作

1、检查Firewalld是否启用

#如果您已经安装iptables建议先关闭 service iptables stop 
#查看Firewalld状态 firewall-cmd --state
#启动firewalld systemctl start firewalld 
#设置开机启动 systemctl enable firewalld.service

启用Firewalld后会禁止所有端口连接,因此请务必放行常用的端口,以免被阻挡在外,以下是放行SSH端口(22)示例,供参考:
#放行22端口 firewall-cmd --zone=public --add-port=80/tcp --permanent 
#重载配置 firewall-cmd --reload
#查看已放行端口 firewall-cmd --zone=public --list-ports

2、安装fail2ban

fail2ban可以监控系统日志,并且根据一定规则匹配异常IP后使用Firewalld将其屏蔽,尤其是针对一些爆破/扫描等非常有效。
# CentOS内置源并未包含fail2ban,需要先安装epel源 yum -y install epel-release #安装fial2ban yum -y install fail2ban
安装成功后fail2ban配置文件位于/etc/fail2ban,其中jail.conf为主配置文件,相关的匹配规则位于filter.d目录,其它目录/文件一般很少用到,如果需要详细了解可自行搜索。

3、配置规则

新建jail.local来覆盖fail2ban的一些默认规则:
#新建配置 vim /etc/fail2ban/jail.local
#默认配置 
[DEFAULT] ignoreip = 127.0.0.1/8
    bantime = 86400 
    findtime = 600 
    maxretry = 5 
    #这里banaction必须用firewallcmd-ipset,这是fiewalll支持的关键,如果是用Iptables请不要这样填写 banaction = firewallcmd-ipset action = %(action_mwl)s
参数说明:
ignoreip:IP白名单,白名单中的IP不会屏蔽,可填写多个以(,)分隔 
bantime:屏蔽时间,单位为秒(s) 
findtime:时间范围 
maxretry:最大次数 banaction:屏蔽IP所使用的方法,上面使用firewalld屏蔽端口

4、防止SSH爆破

如果您还在使用默认SSH端口(22),可能每天都会被扫描,我们可以修改端口尽量避免被扫,
参考: https://www.moerats.com/archives/394/ ,或者可以使用fail2ban将恶意IP屏蔽。
继续修改jail.local这个配置文件,在后面追加如下内容:
[sshd]
enabled = true 
filter = sshd 
port = 22 
action = %(action_mwl)s 
logpath = /var/log/secure
参数说明:
[sshd]:名称,可以随便填写 
filter:规则名称,必须填写位于filter.d目录里面的规则,sshd是fail2ban内置规则 
port:对应的端口 
action:采取的行动 
logpath:需要监视的日志路径

到这一步,我们jail.local的规则看起来可能像下面这样子:
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime = 86400
findtime = 600
maxretry = 5
banaction = firewallcmd-ipset
action = %(action_mwl)s

[sshd]
enabled = true
filter = sshd
port = 22
action = %(action_mwl)s
logpath = /var/log/secure
上面的配置意思是如果同一个IP,在10分钟内,如果连续超过5次错误,则使用Firewalld将他IP ban了。输入systemctl start fail2ban 启动fail2ban来试试效果。
使用另一台服务器不断尝试连接SSH,并且不断的将密码输入错误,你会发现连续超过5次后直接连不上,说明IP被ban了,可以输入:fail2ban-client status sshd查看被ban的IP,如下截图。

防止CC攻击

这里仅以Nginx为例,使用fail2ban来监视nginx日志,匹配短时间内频繁请求的IP,并使用firewalld将其IP屏蔽,达到CC防护的作用。
#需要先新建一个nginx日志匹配规则
vi /etc/fail2ban/filter.d/nginx-cc.conf
#填写如下内容
[Definition]
failregex = -.*- .*HTTP/1.* .* .*$ ignoreregex =
继续修改jail.local追加如下内容:
[nginx-cc]
enabled = true
    port = http,https
    filter = nginx-cc
    action = %(action_mwl)s
    maxretry = 20
    findtime = 60
    bantime = 3600
    logpath = /usr/local/nginx/logs/access.log
上面的配置意思是如果在60s内,同一IP达到20次请求,则将其IP ban 1小时,上面只是为了测试,请根据自己的实际情况修改。logpath为nginx日志路径。
防止Wordpress爆破
如果您经常分析日志会发现有大量机器人在扫描wordpress登录页面wp-login.php,虽然对方可能没成功,但是为了避免万一还是将他IP干掉为好。
#需要先新建一个nginx日志匹配规则
vi /etc/fail2ban/filter.d/wordpress.conf
#填写如下内容
[Definition] failregex = ^ -.* /wp-login.php.* HTTP/1\.." ignoreregex =
继续修改jail.local追加如下内容:
[wordpress] enabled = true port = http,https filter = wordpress action = %(action_mwl)s maxretry = 20 findtime = 60 bantime = 3600 logpath = /usr/local/nginx/logs/access.log
当然,别忘记输入systemctl restart fail2ban重启fail2ban使其生效。
常用命令
#启动 systemctl start fail2ban
#停止 systemctl stop fail2ban
#开机启动 systemctl enable fail2ban
#查看被ban IP,其中sshd为名称,比如上面的
[wordpress] fail2ban-client status sshd
#删除被ban IP fail2ban-client set sshd delignoreip 192.168.111.111
#查看日志 tail /var/log/fail2ban.log

总结

fail2ban已经内置很多匹配规则,位于filter.d目录下,包含了常见的SSH/FTP/Nginx/Apache等日志匹配,
如果都还无法满足您的需求,您也可以自行新建规则来匹配异常IP。
使用fail2ban+Firewalld来阻止恶意IP是行之有效的办法,可极大提高服务器安全。

三、在自己 linux + docker + lamp + wordpress 环境上部署 fail2ban

1、时间总是过的很快
刚刚把别人的优秀文档借鉴过来,时间已经23:45,备份已经完成,只能先睡觉,后面再部署

linux服务器ssh断开问题

方案一

修改server端的配置文件/etc/ssh/sshd_config
# server每隔60秒给客户端发送一次保活信息包给客户端
ClientAliveInterval 60

# server端发出的请求客户端没有回应的次数达到86400次的时候就断开连接,正常情况下客户端都会相应
ClientAliveCountMax 86400
修改client端的配置文件/etc/ssh/ssh_config
# client 每隔60秒给客户端发送一次保活信息包给客户端
ServerAliveInterval 60

# client 端发出的请求服务端没有回应的次数达到86400次的时候就断开连接,正常情况下服务端都会相应
ServerAliveCountMax 86400

# 方案二

在命令参数里
 ssh -o ServerAliveInterval=60 
这样子只会在需要的连接中保持持久连接,具体的参数请参考这里。

最后要重启sshd服务:
 # /etc/init.d/sshd restart 
或者:
[root@ecs-bxx7 ~]# systemctl restart sshd.service

linux安装ibus输入法

CentOS7 安装中文语言包及IBus重启提示无法连接解决办法

修改配置

vi /etc/locale.conf
 LANG= "zh_CN.UTF-8" 

安装语言包

yum install -y kde-l10n-Chinese glibc-common

查看语言包

locale

IBus重启提示无法连接解决办法
ibus-daemon -r -d -x

最后,发现只能输入英文,无法输入五笔

用 root 身份在终端下,运行下面命令:
yum install ibus-pinyin ibus ibus-gtk ibus-qt
使用im-chooser命令,选择ibus为默认输入法
重新登录桌面系统。

ibus 确实生效了,但是部分窗口无法输入中文
如何解决Linux在`某些`软件内无法输入中文的问题?
上网查过,但是他们的解决方法基本上就是在设置环境变量,但是对于一些软件真的没用,只要是鼠标点击某些软件的输入框,输入法就会切换成`布局\键盘`,而且无法切换成中文,试过好几种中午跟输入法,都是这样.

vim /root/.bashrc
export LC_CTYPE=zh_CN.UTF-8
操作之后---->> 然并卵

LinuxC上帝视角

LinuxC上帝视角
零、文章来源

文章来源

主体复制别人的内容,自己略有增加(后续补充)

一、Linux下的C编程实战(1)

1、开发平台搭建

Linux操作系统在嵌入式系统领域的延伸也可谓是如日中天,许多版本的嵌入式Linux系统被开发出来,如ucLinux、RTLinux、ARM-Linux等等。
   在嵌入式操作系统方面,Linux的地位是不容怀疑的,它开源、它包含TCP/IP协议栈、它易集成GUI。
   鉴于Linux操作系统在服务器和嵌入式系统领域愈来愈广泛的应用,社会上越来越需要基于Linux操作系统进行编程的开发人员。

鉴于读者的需求,在本文中,笔者将对Linux平台下C编程的几个方面进行实例讲解,并力求回答读者们关心的问题,
以与读者朋友们进行交流,共同提高。在本文的连载过程中,有任何问题或建议,您也可以进入笔者的博客参与讨论:
http://hovertree.com/  (截止2020-10-07转发此文时,进入链接,没有找到相关讨论,或者极少有程序员文章,而是到处音乐相关链接)

笔者建议在PC内存足够大的情况下,不要直接安装Linux操作系统,最好把它安装在运行VMWare虚拟机软件的Windows平台上

2、GCC编译器

GCC是Linux平台下最重要的开发工具,它是GNU的C和C++编译器,其基本用法为:
    gcc [options] [filenames]
options为编译选项,GCC总共提供的编译选项超过100个,但只有少数几个会被频繁使用,我们仅对几个常用选项进行介绍。

假设我们编译一输出“Hello World”的程序:
/* Filename:helloworld.c */
int main()
{
    printf("Hello World\n");
    return 0;
}`/* 何问起 hovertree.com */`

最简单的编译方法是不指定任何编译选项: gcc helloworld.c
它会为目标程序生成默认的文件名a.out,我们可用-o编译选项来为将产生的可执行文件指定一个文件名来代替a.out。
例如,将上述名为 helloworld.c 的 C 程序编译为名叫helloworld的可执行文件,需要输入如下命令:
  gcc –o helloworld helloworld.c

-c 选项告诉GCC仅把源代码编译为目标代码而跳过汇编和连接的步骤;
-S 编译选项告诉 GCC 在为 C代码产生了汇编语言文件后停止编译。GCC 产生的汇编语言文件的缺省扩展名是.s,上述程序运行如下命令:

[root@ecs-b0b7 c]# gcc -S helloworld.c
将生成helloworld.c的汇编代码,使用的是AT&T汇编。
`
[root@ecs-b0b7 c]# vim helloworld.s
        .file   "helloworld.c"
        .section        .rodata
.LC0:
        .string "Hello World"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    $.LC0, %edi
        call    puts
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
        .section        .note.GNU-stack,"",@progbits
`

-E 选项指示编译器仅对输入文件进行预处理。当这个选项被使用时,
预处理器的输出被送到标准输出(默认为屏幕)而不是储存在文件里。
将头文件引用全部展开:
`
[root@ecs-b0b7 c]# gcc -E helloworld.c | head -n 20
# 1 "helloworld.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "helloworld.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 375 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 392 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 393 "/usr/include/sys/cdefs.h" 2 3 4
# 376 "/usr/include/features.h" 2 3 4
# 399 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4
# 10 "/usr/include/gnu/stubs.h" 3 4
# 1 "/usr/include/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/gnu/stubs.h" 2 3 4
`
-O 选项告诉GCC对源代码进行基本优化从而使得程序执行地更快;而-O2选项告诉GCC产生尽可能小和尽可能快的代码。
使用-O2选项编译的速度比使用-O时慢,但产生的代码执行速度会更快。

-g 选项告诉GCC产生能被GNU调试器使用的调试信息以便调试你的程序,可喜的是,在GCC里,我们能联用-g和-O (产生优化代码)。

-pg 选项告诉GCC在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示你的程序的耗时情况。
`
[root@ecs-b0b7 c]# gcc helloworld.c -O3 -g -pg -o helloworldO3
[root@ecs-b0b7 c]# gprof helloworldO3
Flat profile:

Each sample counts as 0.01 seconds.
 no time accumulated

  %   cumulative   self              self     total
 time   seconds   seconds    calls  Ts/call  Ts/call  name

 %         the percentage of the total running time of the
time       program used by this function.

cumulative a running sum of the number of seconds accounted
 seconds   for by this function and those listed above it.

 self      the number of seconds accounted for by this
seconds    function alone.  This is the major sort for this
           listing.

calls      the number of times this function was invoked, if
           this function is profiled, else blank.

 self      the average number of milliseconds spent in this
ms/call    function per call, if this function is profiled,
           else blank.

 total     the average number of milliseconds spent in this
ms/call    function and its descendents per call, if this
           function is profiled, else blank.

name       the name of the function.  This is the minor sort
           for this listing. The index shows the location of
           the function in the gprof listing. If the index is
           in parenthesis it shows where it would appear in
           the gprof listing if it were to be printed.

Copyright (C) 2012-2016 Free Software Foundation, Inc.

Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved.
[root@ecs-b0b7 c]#
`

3、GDB调试器

GCC用于编译程序,而 Linux 的另一个 GNU 工具 gdb 则用于调试程序。
gdb 是一个用来调试 C 和 C++ 程序的强力调试器,我们能通过它进行一
系列调试工作,包括设置断点、观查变量、单步等。

其最常用的命令如下:
file:装入想要调试的可执行文件。
kill:终止正在调试的程序。
list:列表显示源代码。
next:执行一行源代码但不进入函数内部。
step:执行一行源代码而且进入函数内部。
run:执行当前被调试的程序
quit:终止gdb
watch:监视一个变量的值
break:在代码里设置断点,程序执行到这里时挂起
make:不退出gdb而重新产生可执行文件
shell:不离开gdb而执行shell

下面我们来演示怎样用GDB来调试一个求0+1+2+3+…+99的程序:
/* Filename:sum.c */
void main()
{
  int i, sum;
  sum = 0;
  for (i = 0; i < 100; i++)
  {
    sum +  = i;
  }
  printf("the sum of 1+2+...+ is %d", sum);
}
执行如下命令编译sum.c(加-g选项产生debug信息): gcc –g –o sum sum.c
在命令行上键入gdb sum并按回车键就可以开始调试sum了,再运行run命令执行sum,屏幕上将看到如下内容:
list命令:list命令用于列出源代
这个时候我们再run,程序会停止在第5行,如下图:
设置断点的另一种语法是 break <function>,它在进入指定函数(function)时停住。

clear 用于清除所有的已定义的断点
   clear lineNo
   clear functionName
delete
   delete lineNo
   delete breakPointNum

watch命令:
   watch命令用于观查变量或表达式的值,我们观查sum变量只需要运行watch sum

watch <expr>为表达式(变量)expr设置一个观察点,一量表达式值有变化时,程序会停止执行。

要观查当前设置的watch,可以使用info watchpoints命令。

next、step命令:
next、step用于单步执行,在执行的过程中,被watch变量的变化情况将实时呈现(分别显示Old value和New value),如下图:

next、step命令的区别在于step遇到函数调用,会跳转到到该函数定义的开始行去执行,而next则不进入到函数内部,它把函数调用语句当作

4、Make

make 是所有想在 Linux 系统上编程的用户必须掌握的工具,对于任何稍具规模的程序,
我们都会使用到make,几乎可以说不使用make的程序不具备任何实用价值。

在此,我们有必要解释编译和连接的区别。编译器使用源码文件来产生某种形式的目标文件(object files),在编译过程中,外部的符号参考

并没有被解释或替换(即外部全局变量和函数并没有被找到)。因此,在编译阶段所报的错误一般都是语法错误。而连接器则用于连接目标文

件和程序包,生成一个可执行程序。在连接阶段,一个目标文件中对别的文件中的符号的参考被解释,
如果有符号不能找到,会报告连接错误

编译和连接的一般步骤是:第一阶段把源文件一个一个的编译成目标文件,
第二阶段把所有的目标文件加上需要的程序包连接成一个可执行文件。
这样的过程很痛苦,我们需要使用大量的 gcc 命令。

而 make 则使我们从大量源文件的编译和连接工作中解放出来,综合为一步完成。
GNU Make 的主要工作是读进一个文本文件,称为 makefile。

这个文件记录了哪些文件(目的文件,目的文件不一定是最后的可执行程序,它可以是任何一种文件)由哪些文件(依靠文件)产生,用什么命令来产生。
Make依靠此makefile中的信息检查磁盘上的文件,如果目的文件的创建或修改时间比它的一个依靠文件旧的话,make就执行相应的命令,以便更新目的文件。

假设我们写下如下的三个文件,add.h用于声明add函数,add.c提供两个整数相加的函数体,而main.c中调用add函数:

`/* filename:add.h */
extern int add(int i, int j);`

`/* filename:add.c */
int add(int i, int j)
{
  return i + j;
};`

`/* filename:main.c */
#include "add.h"
main()
{
  int a, b;
  a = 2;
  b = 3;
  printf("the sum of a+b is %d", add(a + b));
};`


怎样为上述三个文件编写 makefile 呢?如下:
-------------------------
test : main.o add.o
gcc main.o add.o -o test

main.o : main.c add.h
gcc -c main.c -o main.o

add.o : add.c add.h
gcc -c add.c -o add.o
-----------------------
上述 makefile 利用 add.c 和 add. h文件执行 gcc -c add.c -o add.o 命令产生add.o目标代码,利用main.c和add.h文件执行gcc -c main.c -o

main.o 命令产生 main.o 目标代码,最后利用 main.o 和 add.o 文件(两个模块的目标代码)执行gcc main.o add.o -o test命令产生可执行文件 test。

我们可在 makefile 中加入变量,另外。
环境变量在make过程中也被解释成make的变量。这些变量是大小写敏感的,一般使用大写字母。

Make变量可以做很多事情,例如:
i) 存储一个文件名列表;
ii) 存储可执行文件名;
iii) 存储编译器选项。

要定义一个变量,只需要在一行的开始写下这个变量的名字,后面跟一个=号,再跟变量的值。
引用变量的方法是写一个$符号,后面跟(变量名)。
我们把前面的 makefile 利用变量重写一遍(并假设使用-Wall -O –g编译选项):
`
OBJS = main.o add.o
CC = gcc
CFLAGS = -Wall -O -g
test : $(OBJS)
$(CC) $(OBJS) -o test
main.o : main.c add.h
$(CC) $(CFLAGS) -c main.c -o main.o
add.o : add.c add.h
$(CC) $(CFLAGS) -c add.c -o add.o
`
makefile 中还可定义清除(clean)目标,可用来清除编译过程中产生的中间文件,例如在上述makefile文件中添加下列代码:
`
clean:
    rm -f *.o
`
运行 make clean 时,将执行rm -f *.o命令,删除所有编译过程中产生的中间文件。
不管怎么说,
自己动手编写 makefile 仍然是很复杂和烦琐的,而且很容易出错。
因此,GNU 也为我们提供了 Automake 和 Autoconf 来辅助快速自动产生makefile,读者可以参阅相关资料。

5、小结

本章主要阐述了 Linux 程序的编写、编译、调试方法及 make,
实际上就是引导读者学习怎样在 Linux 下编程,为后续章节做好准备

二、Linux下的C编程实战(二)

文件系统编程

1、Linux文件系统

Linux 支持多种文件系统,如 ext、ext2、minix、iso9660、msdos、fat、vfat、nfs等。
在这些具体文件系统的上层,Linux提供了虚拟文件系统(VFS)来统一它们的行为,虚拟文件系统为不同的文件系统与内核的通信提供了一致的接口。
下图给出了 Linux 中文件系统的关系(缺失)
在 Linux 平台下对文件编程可以使用两类函数:
(1)Linux操作系统文件API;
(2)C语言I/O库函数。
前者依赖于Linux系统调用,后者实际上与操作系统是独立的,因为在任何操作系统下,
使用C语言I/O库函数操作文件的方法都是相同的。
本章将对这两种方法进行实例讲解。

2、Linux文件API

Linux的文件操作API涉及到创建、打开、读写和关闭文件。
创建: int creat(const char *filename, mode_t mode);

参数 mode 指定新建文件的存取权限,它同 umask 一起决定文件的最终权限(mode&umask),
其中 umask 代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:int umask(int newmask);
该调用将umask设置为newmask,然后返回旧的umask,它只影响读、写和执行权限。

打开:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open函数有两个形式,其中pathname是我们要打开的文件名(包含路径名称,缺省是认为在当前路径下面),
flags可以去下面的一个值或者是几个值的组合:
------------------------------------------------------
标志         含义
O_RDONLY  以只读的方式打开文件
O_WRONLY  以只写的方式打开文件
O_RDWR    以读写的方式打开文件
O_APPEND  以追加的方式打开文件
O_CREAT   创建一个文件
O_EXEC    如果使用了O_CREAT而且文件已经存在,就会发生一个错误
O_NOBLOCK 以非阻塞的方式打开一个文件
O_TRUNC   如果文件已经存在,则删除文件的内容

O_RDONLY、O_WRONLY、O_RDWR三个标志只能使用任意的一个。

如果使用了 O_CREATE 标志,则使用的函数是
int open(const char *pathname,int flags,mode_t mode);
这个时候我们还要指定mode标志,用来表示文件的访问权限。mode可以是以下情况的组合:
------------------------------------------------------
标志            含义
S_IRUSR      用户可以读
S_IWUSR      用户可以写
S_IXUSR      用户可以执行
S_IRWXU      用户可以读、写、执行
S_IRGRP      组可以读
S_IWGRP      组可以写
S_IXGRP      组可以执行
S_IRWXG      组可以读写执行
S_IROTH      其他人可以读
S_IWOTH      其他人可以写
S_IXOTH      其他人可以执行
S_IRWXO      其他人可以读、写、执行
S_ISUID      设置用户执行ID
S_ISGID      设置组的执行ID
------------------------------------------------------
除了可以通过上述宏进行`或`逻辑产生标志以外,我们也可以自己用数字来表示,Linux 总共用 5 个数字来表示文件的各种权限:
第一位表示设置用户ID;
第二位表示设置组ID;
第三位表示用户自己的权限位;
第四位表示组的权限;
最后一位表示其他人的权限。

每个数字可以取1(执行权限)、2(写权限)、4(读权限)、0(无)或者是这些值的和。
例如,要创建一个用户可读、可写、可执行,但是组没有权限,其他人可以读、
可以执行的文件,并设置用户ID位。那么,我们应该使用的模式是1(设置用户ID)、0(不设置组ID)、
7(1+2+4,读、写、执行)、0(没有权限)、5(1+4,读、执行)即10705:
open("test", O_CREAT, 10705);
上述语句等价于: open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );

如果文件打开成功,open 函数会返回一个文件描述符,以后对该文件的所有操作就可以通过对这个文件描述符进行操作来实现。

读写:
在文件打开以后,我们才可对文件进行读写了,Linux 中提供文件读写的系统调用是read、write函数:
int read (int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);

其中参数 buf 为指向缓冲区的指针,length 为缓冲区的大小(以字节为单位)。
函数 read() 实现从文件描述符fd所指定的文件中读取 length 个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。
函数 write 实现将把 length 个字节从 buf 指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。

以O_CREAT为标志的open实际上实现了文件创建的功能,因此,下面的函数等同creat()函数:
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);

定位:
对于随机文件,我们可以随机的指定位置读写,使用如下函数进行定位:
int lseek(int fd, offset_t offset, int whence);

lseek() 将文件读写指针相对 whence 移动 offset 个字节。
操作成功时,返回文件指针相对于文件头的位置。

参数whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR:相对文件读写指针的当前位置
SEEK_END:相对文件末尾

offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5个字节:
lseek(fd, -5, SEEK_CUR);

由于lseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文件的长度:
lseek(fd, 0, SEEK_END);

关闭
当我们操作完成以后,我们要关闭文件了,只要调用close就可以了,其中fd是我们要关闭的文件描述符:
int close(int fd);

例程:
编写一个程序,在当前目录下创建用户可读写文件“hello.txt”,在其中写入“Hello, software weekly”,关闭该文件。
再次打开该文件,读取其中的内容并输出在屏幕上。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#define LENGTH 100

void main()
{
  int fd, len;
  char str[LENGTH];
  /* 创建并打开文件 */
  fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  if (fd)
  {
      /* 写入Hello, software weekly字符串 */
      write(fd, "Hello, Software Weekly", strlen("Hello, software weekly"));
      close(fd);
  }
  fd = open("hello.txt", O_RDWR);
  len = read(fd, str, LENGTH); /* 读取文件内容 */
  str[len] = '\0';
  printf("%s\n", str);
  close(fd);
};

编译并运行,执行

3、C 语言库函数
C库函数的文件操作实际上是独立于具体的操作系统平台的,不管是在DOS、Windows、Linux还是在VxWorks中都是这些函数:

创建和打开:
FILE *fopen(const char *path, const char *mode);

fopen()实现打开指定文件filename,其中的mode为打开模式,C语言中支持的打开模式如下表:
------------------------------------------------------
标志             含义
r, rb         以只读方式打开
w, wb         以只写方式打开。如果文件不存在,则创建该文件,否则文件被截断
a, ab         以追加方式打开。如果文件不存在,则创建该文件
r+, r+b, rb+  以读写方式打开
w+, w+b, wh+  以读写方式打开。如果文件不存在时,创建新文件,否则文件被截断
a+, a+b, ab+  以读和追加方式打开。如果文件不存在,创建新文件

其中b用于区分二进制文件和文本文件,这一点在DOS、Windows系统中是有区分的,但Linux不区分二进制文件和文本文件。

读写
C库函数支持以字符、字符串等为单位,支持按照某中格式进行文件的读写,这一组函数为:
int    fgetc(FILE *stream);
int    fputc(int c, FILE *stream);
char  *fgets(char *s, int n, FILE *stream);
int    fputs(const char *s, FILE *stream);
int    fprintf(FILE *stream, const char *format, ...);
int    fscanf (FILE *stream, const char *format, ...);
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite (const void *ptr, size_t size, size_t n, FILE *stream);

fread()实现从流stream中读取加n个字段,每个字段为size字节,并将读取的字段放入ptr所指的字符数组中,返回实际已读取的字段数。
在读取的字段数小于num时,可能是在函数调用时出现错误,也可能是读到文件的结尾。
所以要通过调用feof()和ferror()来判断。
write() 实现从缓冲区ptr所指的数组中把n个字段写到流stream中,每个字段长为size个字节,返回实际写入的字段数。
另外,C库函数还提供了读写过程中的定位能力,这些函数包括
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
int fseek(FILE *stream, long offset, int whence);
等。

关闭
利用C库函数关闭文件依然是很简单的操作:
int fclose (FILE *stream);

例程:将第2节中的例程用C库函数来实现。
#include <stdio.h>
#define LENGTH 100
void main()
{
  FILE *fd;
  char str[LENGTH] = {0};
  fd = fopen("hello.txt", "w+"); /* 创建并打开文件 */
  if (fd)
  {
     /* 写入Hello, software weekly字符串 */
     fputs("Hello, Software Weekly", fd);
     fclose(fd);
  }
  fd = fopen("hello.txt", "r");
  fgets(str, LENGTH, fd); /* 读取文件内容 */
  printf("%s\n", str);
  fclose(fd);
}

4、小结

Linux提供的虚拟文件系统为多种文件系统提供了统一的接口,Linux的文件编程有两种途径:
   基于Linux系统调用;
   基于C库函数。
这两种编程所涉及到文件操作有新建、打开、读写和关闭,对随机文件还可以定位。
本章对这两种编程方法都给出了具体的实例。

Linux下的C编程实战(三)

1、进程控制与进程通信编程

1、Linux进程

Linux进程在内存中包含三部分数据:代码段、堆栈段和数据段。
代码段存放了程序的代码。代码段可以为机器中运行同一程序的数个进程共享。
堆栈段存放的是子程序(函数)的返回地址、子程序的参数及程序的局部变量。
而数据段则存放程序的全局变量、常数以及动态数据分配的数据空间(比如用malloc函数申请的内存)。
与代码段不同,如果系统中同时运行多个相同的程序,它们不能使用同一堆栈段和数据段。

Linux进程主要有如下几种状态:
用户状态(进程在用户状态下运行的状态)、内核状态(进程在内核状态下运行的状态)、内存中就绪(进程没有执行,但处于就绪状态,只要内核调度它,就可以执行)、
内存中睡眠(进程正在睡眠并且处于内存中,没有被交换到SWAP设备)、就绪且换出(进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行)、
睡眠且换出(进程正在睡眠,且被换出内存)、被抢先(进程从内核状态返回用户状态时,内核抢先于它,做了上下文切换,调度了另一个进程,
原先这个进程就处于被抢先状态)、创建状态(进程刚被创建,该进程存在,但既不是就绪状态,也不是睡眠状态,
这个状态是除了进程0以外的所有进程的最初状态)、僵死状态(进程调用exit结束,进程不再存在,但在进程表项中仍有记录,该记录可由父进程收集)。

下面我们来以一个进程从创建到消亡的过程讲解Linux进程状态转换的“生死因果”。
(1)进程被父进程通过系统调用fork创建而处于创建态;
(2)fork调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程进入就绪态(或者在内存中就绪,或者因为内存不够而在SWAP设备中就绪);
(3)若进程在内存中就绪,进程可以被内核调度程序调度到CPU运行;
(4)内核调度该进程进入内核状态,再由内核状态返回用户状态执行。该进程在用户状态运行一定时间后,又会被调度程序所调度而进入内核状态,由此转入就绪态。
有时进程在用户状态运行时,也会因为需要内核服务,使用系统调用而进入内核状态,服务完毕,会由内核状态转回
用户状态。要注意的是,进程在从内核状态向用户状态返回时可能被抢占,这是由于有优先级更高的进程急需使用CPU,
不能等到下一次调度时机,从而造成抢占;
(5)进程执行exit调用,进入僵死状态,最终结束。

2、进程控制

进程控制中主要涉及到进程的创建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的进程创建方法,sleep的进程睡眠和exit的进程
退出调用,另外Linux还提供了父进程等待子进程结束的系统调用wait。

fork
对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一,它执行一次却返回两个值,完全“不可思议”。先看下面的程序
// fork 不需要依赖其它头文件
#include <stdio.h>
int main()
{
    int i = 0;
    if (fork() == 0)
    {
        for (i=1; i<=2; i++)
        {
            printf("this is child process\n");
        }
    }
    else
    {
        for (i=1; i<=2; i++)
        {
            printf("this is child process\n");
        }
    }
    return 0;
}
执行结果为:
This is child process
This is child process
This is parent process
This is parent process

fork在英文中是“分叉”的意思,这个名字取得很形象。一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就“分叉”了。
当前进程为父进程,通过fork()会产生一个子进程。对于父进程,fork函数返回子程序的进程号而对于子程序,fork函数则返回零,这就是
一个函数返回两次的本质。可以说,fork函数是Unix系统最杰出的成就之一,它是七十年代Unix早期的开发者经过理论和实践上的长期艰苦探索后取得的成果。

如果我们把上述程序中的循环放的大一点:
int main()
{
  int i;
  if (fork() == 0)
  {
    for (i = 1; i < 10000; i++)
      printf("This is child process\n");
  }
  else
  {
    for (i = 1; i < 10000; i++)
      printf("This is parent process\n");
  }
};

则可以明显地看到父进程和子进程的并发执行,交替地输出“This is child process”和“This is parent process”,事实上将其改为30时,就能够开始看到有交替打印。

此时此刻,我们还没有完全理解fork()函数,再来看下面的一段程序,看看究竟会产生多少个进程,程序的输出是什么?
[root@localhost ssss]# cat multiFork.c
int main()
{
  int i;
  int j=0;
  for (i = 0; i < 2; i++)
  {
    if (fork() == 0)
    {
      printf("This is child process:%d\n", j++);
    }
    else
    {
      printf("This is parent process:%d\n", j++);
    }
  }
  return 0;
};
[root@localhost ssss]# ./a.out
This is parent process:0
This is parent process:1
This is child process:1
This is child process:0
This is parent process:1
This is child process:1
[root@localhost ssss]#

exec

在Linux中可使用exec函数族,包含多个函数(execl、execlp、execle、execv、execve和execvp),被用于启动一个指定路径和文件名的进程。
exec 函数族的特点体现在:某进程一旦调用了 exec 类函数,正在执行的程序就被干掉了,系统把代码段替换成新的程序(由exec类函数执行)的代码,
并且原有的数据段和堆栈段也被废弃,新的数据段与堆栈段被分配,但是进程号却被保留。也就是说,exec执行的结果为:
系统认为正在执行的还是原先的进程,但是进程对应的程序被替换了。

fork

函数可以创建一个子进程而当前进程不死,如果我们在 fork 的子进程中调用exec函数族就可以实现既让父进程的代码执行又启动一个新的
指定进程,这实在是很妙的。fork 和 exec 的搭配巧妙地解决了程序启动另一程序的执行但自己仍继续运行的问题,请看下面的例子:
[root@localhost ssss]# cat execlp_tst.c 
#include <stdio.h>
#define MAX_CMD_LEN 512
char command[MAX_CMD_LEN];
void main()
{
  int rtn; /* 子进程的返回数值 */
  int errorno = 1024;
  while (1)
  {
    /* 从终端读取要执行的命令 */
    printf("# ");
    fgets(command, MAX_CMD_LEN, stdin);
    command[strlen(command) - 1] = 0;
    if (fork() == 0)
    {
      printf("now is executing : %s\n", command);
      /* 子进程执行此命令 */
      //execlp(command, command);
      execlp("./aa", command);
      /* 如果exec函数返回,表明没有正常执行命令,打印错误信息*/
      perror(command);
      exit(errorno);
    }
    else
    {
      /* 父进程,等待子进程结束,并打印子进程的返回值 */
      wait(&rtn);
      printf(" child process return %d\n", rtn);
    }
  }
}

这个函数基本上实现了一个 shell 的功能,它读取用户输入的进程名和参数,并启动对应的进程。

clone

clone 是 Linux2.0 以后才具备的新功能,它较 fork 更强(可认为 fork 是 clone 要实现的一部分),可以使得创建的子进程共享父进程的资源,
并且要使用此函数必须在编译内核时设置clone_actually_works_ok选项。
clone函数的原型为:
    int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

此函数返回创建进程的PID,函数中的flags标志用于设置创建子进程时的相关选项,具体含义如下表:
=====================================================================================
标志                             含义
CLONE_PARENT             创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
CLONE_FS                 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
CLONE_FILES              子进程与父进程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS              在新的namespace启动子进程,namespace描述了进程的文件hierarchy
CLONE_SIGHAND            子进程与父进程共享相同的信号处理(signal handler)表
CLONE_PTRACE             若父进程被trace,子进程也被trace
CLONE_VFORK              父进程被挂起,直至子进程释放虚拟内存资源
CLONE_VM                 子进程与父进程运行于相同的内存空间
CLONE_PID                子进程在创建时PID与父进程一致
CLONE_THREAD             Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

来看下面的例子:
#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

int variable, fd;
int do_something() {
   variable = 42;
   close(fd);
   printf("in new function cloned...\n");
   _exit(0);
}

int main(int argc, char *argv[])
{
   void **child_stack;
   char   tempch;

   variable = 9;
   fd = open("aa", O_RDONLY);
   child_stack = (void **) malloc(16384);
   printf("The variable was %d\n", variable);
   clone(do_something, child_stack, CLONE_VM|CLONE_FILES, NULL);
   sleep(3);   /* 延时以便子进程完成关闭文件操作、修改变量 */
   printf("The variable is now %d\n", variable);
   if (read(fd, &tempch, 1) < 1) {
      perror("File Read Error");
      exit(1);
   }
   printf("We could read from the file\n");
   return 0;
}

运行输出,很奇怪:
[root@localhost ssss]# gcc -g cloneTest.c

直接运行,崩溃,因为在 do_something 函数退出时崩溃了,没有打印,好像没有进入 do_something
[root@localhost ssss]# ./a.out
The variable was 9
段错误
[root@localhost ssss]#

采用 gdb 调试,却表现出不同的行为:
从截图上看,显然它进入到了 do_something 函数,而且输出了调试打印机,但遇到 exit(0) 后,直接崩溃


程序到的 CLONE_VM、CLONE_FILES 标志将使得变量和文件描述符表被共享),
父进程随即就感觉到了,这就是clone的特点。

sleep

函数调用sleep可以用来使进程挂起指定的秒数,该函数的原型为:
unsigned int sleep(unsigned int seconds);

该函数调用使得进程挂起一个指定的时间,如果指定挂起的时间到了,该调用返回0;
如果该函数调用被信号所打断,则返回剩余挂起的时间数(指定的时间减去已经挂起的时间)。

exit

系统调用exit的功能是终止本进程,其函数原型为:
void _exit(int status);
_exit会立即终止发出调用的进程,所有属于该进程的文件描述符都关闭。
参数status作为退出的状态值返回父进程,在父进程中通过系统调用

wait。
wait 可获得 void _exit(int status) 所返回的 status 值
wait系统调用包括:
    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);

wait 的作用为发出调用的进程只要有子进程,就睡眠到它们中的一个终止为止; waitpid 等待由参数 pid 指定的子进程退出。

3、进程间通信

Linux 的进程间通信(IPC,InterProcess Communication)
通信方法有 管道、消息队列、共享内存、信号量、套接口等。
管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。

#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

#define INPUT 0
#define OUTPUT 1

#define BUFFER_LEN 1024
void main()
{
  int file_descriptors[2];
  /*定义子进程号 */
  pid_t pid;
  char buf[BUFFER_LEN];
  int returned_count;
  /*创建无名管道*/
  pipe(file_descriptors);
  /*创建子进程*/
  if ((pid = fork()) ==  -1)
  {
    printf("Error in fork\n");
    exit(1);
  }

  /*执行子进程*/
  if (pid == 0)
  {
    printf("in the spawned (child) process...\n");
    /*子进程向父进程写数据,关闭管道的读端*/
    close(file_descriptors[INPUT]);
    write(file_descriptors[OUTPUT], "test data", strlen("test data"));
    exit(0);
  }
  else
  {
    /*执行父进程*/
    printf("in the spawning (parent) process...\n");
    /*父进程从管道读取子进程写的数据,关闭管道的写端*/
    close(file_descriptors[OUTPUT]);
    returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
    printf("%d bytes of data received from spawned process: %s\n", returned_count, buf);
  }
}
[root@localhost ssss]# ./a.out
in the spawning (parent) process...
in the spawned (child) process...
9 bytes of data received from spawned process: test data
[root@localhost ssss]#


上述程序中,无名管道以 int pipe(int filedis[2]);
方式定义,参数 filedis 返回两个文件描述符filedes[0]为读而打开,
filedes[1]为写而打开,filedes[1]的输出是filedes[0]的输入;

在Linux系统下,有名管道可由两种方式创建(假设创建一个名为“fifoexample”的有名管道):
(1)mkfifo("fifoexample","rw");
(2)mknod fifoexample p
mkfifo 是一个函数,mknod 是一个系统调用,即我们可以在shell下输出上述命令。
有名管道创建后,我们可以像读写文件一样读写之:
[root@localhost ssss]# cat writePipe.c
#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_LEN 128

/* 进程二:写有名管道*/
void main()
{
  FILE *out_file;
  int   count = 1;
  char  buf[BUFFER_LEN];

  out_file = fopen("pipeexample", "w");
  if (out_file == NULL)
  {
    printf("Error opening pipe.");
    exit(1);
  }
  sprintf(buf, "this is test data for the named pipe example\n");
  fwrite(buf, 1, BUFFER_LEN, out_file);
  fclose(out_file);
}

[root@localhost ssss]# cat readPipe.c
#include <stdio.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFER_LEN 128

/* 进程一:读有名管道*/
void main()
{
  FILE *in_file;
  int count = 1;
  char buf[BUFFER_LEN];
  in_file = fopen("pipeexample", "r");
  if (in_file == NULL)
  {
    printf("Error in fdopen.\n");
    exit(1);
  }

  while ((count = fread(buf, 1, BUFFER_LEN, in_file)) > 0)
  {
    printf("received from pipe: %s\n", buf);
  }
  fclose(in_file);
}

[root@localhost ssss]# ./writePipe
[root@localhost ssss]# ./readPipe
received from pipe: this is test data for the named pipe example

消息队列用

消息队列用于运行于同一台机器上的进程间通信,与管道相似;
共享内存通常由一个进程创建,其余进程对这块内存区进行读写。
得到共享内存有两种方式:映射/dev/mem设备 和 内存映像文件。
前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:
int shmget(key_t key, int size, int flag); /* 获得一个共享存储标识符 */

该函数使得系统分配size大小的内存用作共享内存;
void *shmat(int shmid, void *addr, int flag); /* 将共享内存连接到自身地址空间中*/

shmid 为 shmget 函数返回的共享存储标识符,addr 和 flag 参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址。
此后,进程可以对此地址进行读写操作访问共享内存。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。
一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;
(2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减1;
(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);
(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

下面是一个使用信号量的例子,该程序创建一个特定的 IPC 结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后清除信号量:

#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
  /* union semun is defined by including <sys/sem.h> */
#else
  /* according to X/OPEN we have to define it ourselves */
  union semun {
        int val;                  /* value for SETVAL */
        struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
        unsigned short *array;    /* array for GETALL, SETALL */
                                  /* Linux specific part: */
        struct seminfo *__buf;    /* buffer for IPC_INFO */
  };
#endif
void main()
{
  key_t  unique_key; /* 定义一个IPC关键字*/
  int    id;
  struct sembuf lock_it;
  union  semun  options;
  int    i;

  unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
  /* 创建一个新的信号量集合*/
  id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
  printf("semaphore id=%d\n", id);
  options.val = 1; /*设置变量值*/
  semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/

 /*打印出信号量的值*/
  i = semctl(id, 0, GETVAL, 0);
  printf("value of semaphore at index 0 is %d\n", i);

  /*下面重新设置信号量*/
  lock_it.sem_num = 0;          /*设置哪个信号量*/
  lock_it.sem_op  = - 1;        /*定义操作*/
  lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
  if (semop(id, &lock_it, 1) == - 1)
  {
    printf("can not lock semaphore.\n");
    exit(1);
  }

  i = semctl(id, 0, GETVAL, 0);
  printf("value of semaphore at index 0 is %d\n", i);

  /*清除信号量*/
  semctl(id, 0, IPC_RMID, 0);
}
[root@localhost ssss]# ./semTest
semaphore id=131072
value of semaphore at index 0 is 1  ==>> 表明此时有一个信号量可以获取
value of semaphore at index 0 is 0  ==>> 表明此时已经没有信号量可用
[root@localhost ssss]#

套接字
套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几

乎是完全一样的。

4.小节

本章讲述了 Linux 进程的概念,并以多个实例讲解了进程控制及进程间通信方法,理解这一章的内容可以说是理解 Linux 这个操作系统的关键。

Linux下的C编程实战(四)

“线程” 控制与 “线程” 通信编程

1、Linux “线程”

笔者曾经在《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年第5~12期)中详细叙述了进程和线程的区别,
并曾经说明 Linux 是一种 "多进程单线程" 的操作系统。
Linux本身只有进程的概念,而其所谓的 "线程" 本质上在内核里仍然是进程。
大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。
Linux 中所谓的"线程"只是在被创建的时候"克隆"(clone)了父进程的资源,因此,clone出来的进程表现为"线程",这一点一定要弄清楚。
因此,Linux "线程"这个概念只有在打引号的情况下才是最准确的,可惜的是几乎没有书籍留心去强调这一点。
  Linux内核只提供了轻量进程的支持,未实现线程模型,但Linux尽最大努力优化了进程的调度开销,这在一定程度上弥补无线程的缺陷。
  Linux用一个核心进程(轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成。
目前Linux中最流行的线程机制为 LinuxThreads,所采用的就是线程-进程"一对一"模型,调度交给核心,
而在用户级实现一个包括信号处理在内的线程管理机制。LinuxThreads 由 Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,
并已绑定在GLIBC中发行,它实现了一种 BiCapitalized 面向 Linux 的 Posix 1003.1c "pthread" 标准接口。
Linuxthread 可以支持 Intel、Alpha、MIPS 等平台上的多处理器系统。

按照POSIX 1003.1c 标准编写的程序与 Linuxthread 库相链接即可支持Linux平台上的多线程,
在程序中需包含头文件 pthread. h,在编译链接时使用命令:
gcc -D -REENTRANT -lpthread xxx. c

其中 '-REENTRANT' 宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),
-lpthread 则意味着链接库目录下的 libpthread.a 或 libpthread.so 文件。
使用Linuxthread库需要2.0以上版本的Linux内核及相应版本的C库(libc 5.2.18、libc 5.4.12、libc 6)。

2、”线程” 控制

线程创建

进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create:
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
  (start_routine)(void*), void *arg);

start_routine为新线程的入口函数,arg为传递给start_routine的参数。

每个线程都有自己的线程ID,以便在进程内区分。线程 ID 在pthread_create调用时回返给创建线程的调用者;
一个线程也可以在创建后使用 pthread_self() 调用获取自己的线程ID:
pthread_self (void) ;

线程退出

线程的退出方式有三:
(1)执行完成后隐式退出;
(2)由线程本身显示调用pthread_exit 函数退出;
pthread_exit (void * retval) ;
(3)被其他线程用pthread_cance函数终止:
pthread_cance (pthread_t thread) ;

在某线程中调用此函数,可以终止由参数thread 指定的线程。
如果一个线程要等待另一个线程的终止,可以使用 pthread_join 函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数 thread 的线程终止:
pthread_join (pthread_t thread, void** threadreturn);

3、线程通信

线程互斥

互斥意味着 "排它",即两个线程不能同时进入被互斥保护的代码。
   Linux下可以通过 pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,
在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,
直到拥有该互斥体的线程完成互斥部分的操作为止。

下面的代码实现了对共享全局变量 x 用互斥体 mutex 进行保护的目的:

int x; // 进程中的全局变量
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex
pthread_mutex_lock(&mutex); // 给互斥体变量加锁
… //对变量x 的操作
phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁

线程同步

同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。
当多个线程协作时,相互作用的任务必须在一定的条件下同步。

Linux 下的 C 语言编程有多种线程同步机制,最典型的是条件变量'condition variable'。
pthread_cond_init 用来创建一个条件变量,其函数原型为:
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait 和 pthread_cond_timedwait 用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体 mutex,
这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。pthread_cond_wait 的函数原型为:
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast 用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞:
pthread_cond_broadcast (pthread_cond_t *cond) ;

pthread_cond_signal 则用于解除某一个等待线程的阻塞状态:
pthread_cond_signal (pthread_cond_t *cond) ;

pthread_cond_destroy 则用于释放一个条件变量的资源。

在头文件 semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,
控制对资源的同步访问,提供程序设计人员更方便的调用接口。

sem_init(sem_t *sem, int pshared, unsigned int val);
这个函数初始化一个信号量 sem 的值为val,参数 pshared 是共享属性控制,表明是否在进程间共享。

sem_wait(sem_t *sem);
调用该函数时,若 sem 为无状态,调用线程阻塞,等待信号量sem值增加<post> 成为有信号状态;
若sem为有状态,调用线程顺序执行,但信号量的值减一。

sem_post(sem_t *sem);
调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。

4、实例

下面我们还是以著名的生产者/消费者问题为例来阐述 Linux 线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。
生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有N 个,是一个环形的缓冲池。
[root@localhost ssss]# cat producer_consumer.c
#include <stdio.h>
#include <linux/sched.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 8 // 缓冲区数量

struct prodcons
{
  // 缓冲区相关数据结构
  int              buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/
  pthread_mutex_t  lock;                /* 互斥体lock 用于对缓冲区的互斥操作 */
  int              readpos;             /* 读指针*/
  int              writepos;            /* 写指针*/
  pthread_cond_t  notempty;             /* 缓冲区非空的条件变量 */
  pthread_cond_t  notfull;              /* 缓冲区未满的条件变量 */
};

/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
  pthread_mutex_init(&b->lock, NULL);
  pthread_cond_init(&b->notempty, NULL);
  pthread_cond_init(&b->notfull, NULL);
  b->readpos = 0;
  b->writepos = 0;
}

/* 将产品放入缓冲区,这里是存入一个整数*/
void put(struct prodcons *b, int data)
{
  pthread_mutex_lock(&b->lock);
  /* 如果缓冲区已满*/
  if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
  {
    /* 等待缓冲区释放出空间*/
    pthread_cond_wait(&b->notfull, &b->lock);
  }
  /* 写数据,并移动指针 */
  b->buffer[b->writepos] = data;
  b->writepos++;
  if (b->writepos >= BUFFER_SIZE)
  {
     b->writepos = 0;
  }
  /* 设置缓冲区非空的条件变量*/
  pthread_cond_signal(&b->notempty);
  pthread_mutex_unlock(&b->lock);
}

/* 从缓冲区中取出整数*/
int get(struct prodcons *b)
{
  int data;
  pthread_mutex_lock(&b->lock);
  /* 如果缓冲区是空的*/
  if (b->writepos == b->readpos)
  {
    /* 等待缓冲区被放入数据 */
    pthread_cond_wait(&b->notempty, &b->lock);
  }

  /* 读数据,移动读指针 */
  data = b->buffer[b->readpos];
  b->readpos++;
  /* 已经取完最后一个缓冲数据 */
  if (b->readpos >= BUFFER_SIZE)
  {
    /* 将缓冲区置空*/
    b->readpos = 0;
  }
  /* 设置缓冲区未满的条件变量*/
  pthread_cond_signal(&b->notfull);
  pthread_mutex_unlock(&b->lock);
  return data;
}
/* 测试:生产者线程将 1 到 MaxNum 的整数送入缓冲区,消费者线
程从缓冲区中获取整数,两者都打印信息*/

#define MaxNum 32
#define GAME_OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
  int n;
  for (n = 0; n < MaxNum; n++)
  {
    printf("%d --->\n", n);
    put(&buffer, n);
  }
  put(&buffer, GAME_OVER);
  return NULL;
}

void *consumer(void *data)
{
  int d;
  while (1)
  {
    d = get(&buffer);
    if (d == GAME_OVER)
      break;
    printf("--->%d \n", d);
  }
  return NULL;
}

int main(void)
{
  void      *retval;
  pthread_t  th_a;
  pthread_t  th_b;

  init(&buffer);
  /* 创建生产者和消费者线程*/
  pthread_create(&th_a, NULL, producer, 0);
  pthread_create(&th_b, NULL, consumer, 0);
  /* 等待两个线程结束*/
  pthread_join(th_a, &retval);
  pthread_join(th_b, &retval);
  return 0;
}
==========================
[root@localhost ssss]# gcc producer_consumer.c -lpthread -o producer_consumer
[root@localhost ssss]#
[root@localhost ssss]# ./producer_consumer
0 --->
1 --->
2 --->
3 --->
4 --->
5 --->
6 --->
7 --->
--->0
--->1
--->2
--->3
--->4
--->5
--->6
8 --->
9 --->
10 --->
11 --->
12 --->
13 --->
14 --->
--->7
--->8
--->9
--->10
--->11
--->12
--->13
15 --->
16 --->
17 --->
18 --->
19 --->
20 --->
21 --->
--->14
--->15
--->16
--->17
--->18
--->19
--->20
--->21
22 --->
23 --->
24 --->
25 --->
26 --->
27 --->
28 --->
29 --->
--->22
--->23
--->24
--->25
--->26
--->27
--->28
30 --->
31 --->
--->29
--->30
--->31
[root@localhost ssss]#

5.WIN32、VxWorks、Linux线程类比

目前为止,笔者已经创作了《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年5~12期连载)、
《深入浅出Win32多线程程序设计》(天极网技术专题)系列,我们来找出这两个系列文章与本文的共通点。
看待技术问题要瞄准其本质,不管是Linux、VxWorks还是WIN32,其涉及到多线程的部分都是那些内容,无非就是线程控制和线程通信,
它们的许多函数只是名称不同,其实质含义是等价的,下面我们来列个三大操作系统共同点详细表单:

事项           WIN32          VxWorks          Linux
线程创建   CreateThread      taskSpawn      pthread_create
线程终止  执行完成后退出;线程自身调用ExitThread 函数即终止自己;被其他线程调用函数TerminateThread函数
执行完成后退出;由线程本身调用exit退出;被其他线程调用函数taskDelete终止
执行完成后退出;由线程本身调用pthread_exit 退出;被其他线程调用函数pthread_cance终止
获取线程ID  GetCurrentThreadId  taskIdSelf   pthread_self
创建互斥    CreateMutex         semMCreate   pthread_mutex_init
获取互斥  WaitForSingleObject、 semTake      pthread_mutex_lock
         WaitForMultipleObjects
释放互斥    ReleaseMutex        semGive      phtread_mutex_unlock
创建信号量  CreateSemaphore    semBCreate、  sem_init
                              semCCreate
等待信号量 WaitForSingleObject semTake       sem_wait
释放信号量 ReleaseSemaphore    semGive       sem_post

6、小结

本章讲述了 Linux 下多线程的控制及线程间通信编程方法,给出了一个生产者/消费者的实例,并将Linux的多线程
  与WIN32、VxWorks多线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。

Linux下的C编程实战(五)

驱动程序设计

1、引言

设备驱动程序是操作系统内核和机器硬件之间的接口,它为应用程序屏蔽硬件的细节,一般来说,Linux的设备驱动程序需要完成如下功能:
(1)初始化设备;
(2)提供各类设备服务;
(3)负责内核和设备之间的数据交换;
(4)检测和处理设备工作过程中出现的错误。

妙不可言的是,Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。
在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。
本系列文章的第2章文件系统编程中已经看到了这些函数的真面目,它们就是 open ()、 close ()、 read ()、 write() 等。
Linux主要将设备分为二类:
   字符设备和块设备(当然网络设备及USB等其它设备的驱动编写方法又稍有不同)。

这两类设备的不同点在于:
   在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,
   而块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求
能满足用户的要求,就返回请求的数据,如果不能,
就调用请求函数来进行实际的I/O操作。

块设备主要针对磁盘等慢速设备。以字符设备的驱动较为简单,因此本章主要阐述字符设备的驱动编写。

2、驱动模块函数

init 函数用来完成对所控设备的初始化工作,并调用 register_chrdev() 函数注册字符设备。

假设有一字符设备"exampledev",则其 init 函数为:
void exampledev_init(void)
{
  if (register_chrdev(MAJOR_NUM, "exampledev", &exampledev_fops))
    TRACE_TXT("Device exampledev driver registered error");
  else
    TRACE_TXT("Device exampledev driver registered successfully");
  //设备初始化
}

其中, register_chrdev() 函数中的参数 MAJOR_NUM 为主设备号, "exampledev" 为设备名,exampledev_fops为包含基本函数入口点的结构体,
类型为 file_operations。当执行 exampledev_init 时,它将调用内核函数register_chrdev,
把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。

file_operations 结构体定义为:
struct file_operations
{
  int (*lseek)();
  int (*read)();
  int (*write)();
  int (*readdir)();
  int (*select)();
  int (*ioctl)();
  int (*mmap)();
  int (*open)();
  void(*release)();
  int (*fsync)();
  int (*fasync)();
  int (*check_media_change)();
  void(*revalidate)();
};

大多数的驱动程序只是利用了其中的一部分,对于驱动程序中无需提供的功能,只需要把相应位置的值设为NULL。
对于字符设备来说,要提供的主要入口有:
   open ()、 release ()、 read ()、 write ()、 ioctl ()。

open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open () 函数:
   int open(struct inode * inode ,struct file * file);

其中参数 inode 为设备特殊文件的 inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。
open() 的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode-> i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码
(0表示成功,负数表示存在错误) 等;

release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:
void release (struct inode * inode ,struct file * file) ;
release() 函数 的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。

read()函数 当对设备特殊文件进行read() 系统调用时,将调用驱动程序read() 函数:
void read(struct inode * inode ,struct file * file ,char * buf ,int count) ;
参数buf是指向用户空间缓冲区的指针,由用户进程给出,count 为用户进程要求读取的字节数,也由用户给出。

read() 函数的功能就是从硬设备或内核内存中读取或复制count个字节到buf 指定的缓冲区中。
在复制数据时要注意,驱动程序运行在内核中,而 buf 指定的缓冲区在用户内存区中,是不能直接在内核中访问使用的,
因此,必须使用特殊的复制函数来完成复制工作,这些函数在<asm/segment.h>中定义:
void put_user_byte (char data_byte ,char * u_addr) ;
void put_user_word (short data_word ,short * u_addr) ;
void put_user_long(long data_long ,long * u_addr) ;
void memcpy_tofs (void * u_addr ,void * k_addr ,unsigned long cnt) ;
参数 u_addr 为用户空间地址,k_addr 为内核空间地址,cnt为字节数。

write( ) 函数 当设备特殊文件进行 write () 系统调用时,将调用驱动程序的write () 函数:
void write (struct inode * inode ,struct file * file ,char * buf ,int count) ;
write ()的功能是将参数buf 指定的缓冲区中的count 个字节内容复制到硬件或内核内存中, 和 read() 一样,复制工作也需要由特殊函数来完成:
unsigned char_get_user_byte (char * u_addr) ;
unsigned char_get_user_word (short * u_addr) ;
unsigned char_get_user_long(long * u_addr) ;
unsigned memcpy_fromfs(void * k_addr ,void * u_addr ,unsigned long cnt) ;
ioctl() 函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:
int ioctl (struct inode * inode ,struct file * file ,unsigned int cmd ,unsigned long arg);
参数 cmd 为设备驱动程序要执行的命令的代码,由用户自定义,参数arg 为相应的命令提供参数,类型可以是整型、指针等。
同样,在驱动程序中,这些函数的定义也必须符合命名规则,按照本文约定,设备 "exampledev" 的驱动程序的这些函数应分别命名为
exampledev_open()、 exampledev_release()、 exampledev_read()、 exampledev_write()、 exampledev_ioctl(),
因此设备 "exampledev" 的基本入口点结构变量 exampledev_fops 赋值如下:

struct file_operations exampledev_fops {
 NULL ,
 exampledev_read ,
 exampledev_write ,
 NULL ,
 NULL ,
 exampledev_ioctl ,
 NULL ,
 exampledev_open ,
 exampledev_release ,
 NULL ,
 NULL ,
 NULL ,
 NULL
} ;

3.内存分配

由于Linux驱动程序在内核中运行,因此在设备驱动程序需要申请/释放内存时,不能使用用户级的malloc/free函数,而需由内核级的函数
kmalloc/kfree () 来实现,kmalloc()函数的原型为:
void kmalloc (size_t size ,int priority);
参数 size 为申请分配内存的字节数;
参数 priority 说明若 kmalloc() 不能马上分配内存时用户进程要采用的动作:
  GFP_KERNEL 表示等待,
    即等 kmalloc() 函数将一些内存安排到交换区来满足你的内存需要,
  GFP_ATOMIC 表示不等待,如不能立即分配到内存则返回 0 值;
函数的返回值指向已分配内存的起始地址,出错时,返回 0。

kmalloc ()分配的内存需用kfree()函数来释放,kfree ()被定义为:

# define kfree (n) kfree_s( (n) ,0)

其中kfree_s () 函数原型为:
void kfree_s (void * ptr ,int size);

参数 ptr 为 kmalloc() 返回的已分配内存的指针,size是要释放内存的字节数,若为0 时,由内核自动确定内存的大小。

4、中断

许多设备涉及到中断操作,因此,在这样的设备的驱动程序中需要对硬件产生的中断请求提供中断服务程序。
与注册基本入口点一样,驱动程序也要请求内核将特定的中断请求和中断服务程序联系在一起。
在Linux中,用 request_irq() 函数来实现请求:
int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);

参数 irq 为要中断请求号,参数 handler 为指向中断服务程序的指针,参数type 用来确定是正常中断还是快
速中断(正常中断指中断服务子程序返回后,内核可以执行调度程序来确定将运行哪一个进程;而快速中断是指中断服务子程序返回后,
立即执行被中断程序,正常中断type 取值为0 ,快速中断type 取值为SA_INTERRUPT),参数name是设备驱动程序的名称。

5、实例

笔者最近设计了一块采用三星S3C2410 ARM处理器的电路板(ARM处理器广泛应用于手机、PDA等嵌入式系统),
板上包含四个用户可编程的发光二极管(LED),这些LED连接在ARM处理器的可编程I/O口(GPIO)上。
下图给出了 ARM 中央处理器与 LED 的连接原理(缺失)

我们在 ARM 处理器上移植 Linux 操作系统,现在来编写这些 LED 的驱动:

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#define DEVICE_NAME   "leds" /*定义led 设备的名字*/
#define LED_MAJOR      231 /*定义led 设备的主设备号*/

static unsigned long led_table[] =
{
  /* I/O 方式led 设备对应的硬件资源 */
  GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6,
};

/*使用 ioctl 控制 led */
static int leds_ioctl (struct inode *inode,
                       struct file  *file,
                       unsigned int  cmd,
                       unsigned long arg)
{
  switch (cmd)
  {
    case 0:
    case 1:
      if (arg > 4)
      {
        return  -EINVAL;
      }
      write_gpio_bit(led_table[arg], !cmd);
    default:
      return  -EINVAL;
  }
}

static struct file_operations leds_fops =
{
  owner: THIS_MODULE,
  ioctl: leds_ioctl,
};

static devfs_handle_t devfs_handle;
static int __init leds_init(void)
{
  int ret;
  int i;
  /*在内核中注册设备*/
  ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &leds_fops);
  if (ret < 0)
  {
    printk(DEVICE_NAME " can't register major number"n");
    return ret;
  }
  devfs_handle = devfs_register(NULL, DEVICE_NAME,
                                DEVFS_FL_DEFAULT, LED_MAJOR, 0, 
                                S_IFCHR | S_IRUSR | S_IWUSR, &leds_fops, 
                                NULL);
  /* 使用宏进行端口初始化,set_gpio_ctrl 和 write_gpio_bit 均为宏定义*/
  for (i = 0; i < 8; i++)
  {
    set_gpio_ctrl(led_table[i] | GPIO_PULLUP_EN | GPIO_MODE_OUT);
    write_gpio_bit(led_table[i], 1);
  }
  printk(DEVICE_NAME " initialized"n");
  return 0;
}

static void __exit leds_exit(void)
{
  devfs_unregister(devfs_handle);
  unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}
module_init(leds_init);
module_exit(leds_exit);

使用命令方式编译led 驱动模块:
# arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include  \
 -DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c

以上命令将生成leds.o 文件,把该文件复制到板子的/lib目录下,使用以下命令就可以安装leds驱动模块:
#insmod /lib/ leds.o

删除该模块的命令是:
# rmmod leds

6、小结

本章讲述了Linux设备驱动程序的入口函数及驱动程序中的内存申请、中断等,
并给出了一个通过ARM处理器的GPIO口控制LED的驱动实例。

jQuery UI

一、jQuery UI的版本及特性

1、版本

jQuery UI 1.9.2 发布,这是 jQuery UI 1.9的
第二个维护版本,修复了包括: Accordion, Autocomplete,
Button, Datepicker, Dialog, Menu, Tabs, Tooltip
and Widget Factory 等多个组件的 bug。

2、jQuery UI 特性

简单易用
继承 jQuery 简易使用特性,提供高度抽象接口,短期改善网站易用性。
开源免费
采用 MIT & GPL 双协议授权,轻松满足自由产品至企业产品各种授权需求。
广泛兼容
兼容各主流桌面浏览器。包括IE 6+、Firefox 2+、Safari 3+、Opera 9+、Chrome 1+。
轻便快捷
组件间相对独立,可按需加载,避免浪费带宽拖慢网页打开速度。
标准先进
支持 WAI-ARIA,通过标准 XHTML 代码提供渐进增强,保证低端环境可访问性。
美观多变
提供近 20 种预设主题,并可自定义多达 60 项可配置样式规则,提供 24 种背景纹理选择。
开放公开
从结构规划到代码编写,全程开放,文档、代码、讨论,人人均可参与。
强力支持
Google 为发布代码提供 CDN 内容分发网络支持.
完整汉化
开发包内置包含中文在内的 40 多种语言包。

缺点、不足
+- 1.代码不够健壮:缺乏全面的测试用例,部分组件 Bugs 较多,不能达到企业级产品开发要求。
+- 2.构架规划不足:组件间 API 缺乏协调,缺乏配合使用帮助。
+- 3.控件较少:相对于 Dojo、YUI、Ext JS 等成熟产品,可用控件较少,无法满足复杂界面功能要求。

3、关于 jQuery UI的详细教程

https://edu.aliyun.com/course/488/lesson/list?utm_campaign=aliyunedu&utm_medium=images&utm_source=wb&utm_content=m_39527

[来源](https://www.jianshu.com/p/be30d4a9997f)

javascript

一、input 输入控件,怎样提示其内容为空

1、文件上传控件

A、一种实现方法,案例

html 代码:

<input type="file" id="reportXML" name="reportXML" title="输入内容" multiple="multiple">
<button type="button" onclick="submitXML()">提交</button>


js 代码:

function submitXML(){
    var fileInput = $('#reportXML').get(0).files[0];
    console.info(fileInput);
    if(fileInput) {
        $("#reportXMLform").submit();
    } else {
        alert("请选择上传文件!");
    }
}

B、案例2,在别的 btn-click 里面提交

html 代码如下

<form id="xx" method="post" action="proc.html">
    <input type="file" id="reportXML" name="reportXML" title="输入内容" multiple="multiple">
</form>


js 如下

function submitXML(){
     var fileInput = $('#reportXML').get(0).files[0];
     console.info(fileInput);
     if(fileInput) {
         $("#xx").submit();  //这里利用的是<form>控件的submit功能
     } else {
         alert("请选择上传文件!");
     }
}

2、普通文件框控件

js 代码:

function btnck() {
    var a=document.getElementById("text");
    var b=document.getElementById("btn");
    if(a.value==""){
        alert("文本框内容不能为空!");
    } else {
        alert("提交成功!");
    }
}


html 代码

<input id="text" type="text" />
<input id="btn" type="button" onclick="btnck();" value="提交" />

二、按钮点击之后,3秒内不能再次点击

html 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title> 
    <script type="text/javascript">
        var date1 = new Date();
        var date2 = new Date();
        var data3 = new Date();
        var count = 1;
        function test() {
            date1 = date2;
            date2 = new Date();
            if(count==1) {
                count ++;
            } else {
                data3 = (date2.getTime()-date1.getTime())/1000;
            }
            if (data3 <3) {
                alert("请不要太快添加");
            } else {
                alert("OK");
            }
        }
    </script>
</head>
<body>
    <input type="button" οnclick="test();" value="点我" >
</body>
</html>

三、js实现全国31省市级联下拉选择框核心代码

1、例一,讲述思路

<html>
<head>
<script>
    //底层数据源
    var pro=['黑龙江','吉林','辽宁'];
    //次级数据源要使用二维数组
    var city=[['哈尔滨','佳木斯','齐齐哈尔','大庆','鹤岗'],['长春','四平','松原','吉林'], ['铁岭','大连','沈阳']];
    function init() {
        loadpro();
        loadCity();
    }
    //加载省级的选择函数 
    /*思路:在省级的select中加入option选项
    *实现:找到provice选择框->创建option并加入select中
    *   ->加入数量内容(.innerHTML)由数据源决定(循环)
    *   ->先写入内容再添加到父级标签select中
    */
    function loadpro() {
      var provice = document.getElementById('provice');
      for (var i = 0; i < pro.length; i++ ) {
        var option=document.createElement('option')
        option.innerHTML=pro[i]
        provice.appendChild(option);
      }
    }
    //加载城市的选择函数
    /*思路:在所选省级下的select中加入option城市            *实现:确认当前选择省级(确认索引):
      1.找到省级的select中的值(value)
      2.将当前值与第i个省级对比(确认索引)

      加入城市数据源中的第i组数据option:
      1.找到城市select
      2.循环第i组数据加入(数量:二维长度)              *注意:没选择一次要清空城市选择框中的内容              */
    function loadCity() {
        //找到select provi懈ce
        var provice=document.getElementById('provice');
        var c=document.getElementById('city');
        c.innerHTML='';
        for(var i=0;i<pro.length; i++) {
            if(pro[i]==provice.value) {
                var cs=city[i];
                for(var j=0;j<cs.length;j++) {
                    var option = document.createElement('option');
                    option.innerHTML=cs[j];
                    c.appendChild(option);
                }
            }
        }
    }
    //选择框
    </script>
    </head>
    <body onload="init()">
        <select id="provice" onchange="loadCity()">
        </select>==<select id = "city"></select>
    </body>
</html>

2、例二,一个完全可用的例子

A、源码如下

<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>全国省份城市级联下拉菜单</title>
<script language="javascript">
cityareaname=new Array(35);
cityareacode=new Array(35);
function first(preP,preC,formname,selectP,selectC)
{
    a=0;
    if (selectP=='01') {
        a=1;
        tempoption = new Option('北京','01',false,true);
    } else {
        tempoption = new Option('北京','01'); 
    }
    eval('document.'+formname+'.'+preP+'.options[1]=tempoption;');
    cityareacode[0]=new Array('0101','0102','0103','0104','0105','0106','0107','0108');
    cityareaname[0]=new Array('东城区','西城区','崇文区','宣武区','朝阳区','海淀区','丰台区','石景山');
    if (selectP=='02') {
        a = 2;
        tempoption = new Option('深圳','02',false,true);
    } else {
        tempoption = new Option('深圳','02');
    }
    eval('document.'+formname+'.'+preP+'.options[2]=tempoption;');
    cityareacode[1]=new Array('0201','0202','0203','0204','0205','0206');
    cityareaname[1]=new Array('罗湖','福田','南山','盐田','宝安','龙岗');
    if (selectP=='03') {
        a = 3;
        tempoption = new Option('上海','03',false,true);
    } else {
        tempoption = new Option('上海','03');
    }
    eval('document.'+formname+'.'+preP+'.options[3]=tempoption;');
    cityareacode[2] = new Array('0301','0302','0303','0304','0305','0306','0307','0308','0309','0310','0311','0312','0313','0314','0315','0316','0317','0318','0319','0320');
    cityareaname[2]=new Array('宝山','金山','南市','长宁','静安','青浦','崇明','卢湾','松江','奉贤','浦东','杨浦','虹口','普陀','闸北','黄浦','闵行','徐汇','嘉定','南汇');
    if (selectP=='04') {
        a=4;tempoption=new Option('重庆','04',false,true);
    } else {
        tempoption = new Option('重庆','04'); 
    }
    eval('document.'+formname+'.'+preP+'.options[4]=tempoption;');
    cityareacode[3]=new Array('0401','0402','0403','0404','0405','0406');
    cityareaname[3]=new Array('渝中','江北','沙坪坝','南岸','九龙坡','大渡口');
    if (selectP=='05') {
        a = 5;
        tempoption = new Option('天津','05',false,true);
    } else {
        tempoption = new Option('天津','05');
    }
    eval('document.'+formname+'.'+preP+'.options[5]=tempoption;');
    cityareacode[4]=new Array('0501','0502','0503','0504','0505','0506','0507','0508','0509','0510','0511','0512','0513','0514','0515');
    cityareaname[4]=new Array('和平','河北','河西','河东','南开','红桥','塘沽','汉沽','大港','东丽','西青','津南','北辰','武清','滨海');
    if (selectP=='06') {
        a = 6;tempoption=new Option('广东','06',false,true); 
    } else {
        tempoption = new Option('广东','06');
    }
    eval('document.'+formname+'.'+preP+'.options[6]=tempoption;');
    cityareacode[5]=new Array('0601','0602','0603','0604','0605','0606','0607','0608','0609','0610','0611','0612','0613','0614','0615');
    cityareaname[5]=new Array('广州','珠海','中山','佛山','东莞','清远','肇庆','阳江','湛江','韶关','惠州','河源','汕尾','汕头','梅州');
    if (selectP=='07') {
        a = 7;
        tempoption = new Option('河北','07',false,true);
    } else {
        tempoption = new Option('河北','07');
    }
    eval('document.'+formname+'.'+preP+'.options[7]=tempoption;');
    cityareacode[6]=new Array('0701','0702','0703','0704','0705','0706','0707','0708','0709','0710','0711');
    cityareaname[6]=new Array('石家庄','唐山','秦皇岛','邯郸','邢台','张家口','承德','廊坊','沧州','保定','衡水');
    if (selectP=='08') {
        a = 8;
        tempoption=new Option('山西','08',false,true);
    } else {
        tempoption=new Option('山西','08');
    }
    eval('document.'+formname+'.'+preP+'.options[8]=tempoption;');
    cityareacode[7]=new Array('0801','0802','0803','0804','0805','0806','0807');
    cityareaname[7]=new Array('太原','大同','阳泉','朔州','长治','临汾','晋城');
    if (selectP=='09') {
        a = 9;
        tempoption = new Option('内蒙古','09',false,true);
    } else {
        tempoption=new Option('内蒙古','09');
    }
    eval('document.'+formname+'.'+preP+'.options[9]=tempoption;');
    cityareacode[8]=new Array('0901','0902','0903','0904','0905','0906','0907','0908','0909','0910','0911');
    cityareaname[8]=new Array('呼和浩特','包头','乌海','临河','东胜','集宁','锡林浩特','通辽','赤峰','海拉尔','乌兰浩特');
    if (selectP=='10') {
        a=10;tempoption=new Option('辽宁','10',false,true);
    } else { 
        tempoption=new Option('辽宁','10');
    }
    eval('document.'+formname+'.'+preP+'.options[10]=tempoption;');
    cityareacode[9]=new Array('1001','1002','1003','1004','1005','1006','1007','1008','1009','1010','1011','1012','1013','1014');
    cityareaname[9]=new Array('沈阳','大连','鞍山','锦州','丹东','盘锦','铁岭','抚顺','营口','辽阳','阜新','本溪','朝阳','葫芦岛');
    if (selectP=='11') {
        a=11;tempoption=new Option('吉林','11',false,true);
    } else {
        tempoption=new Option('吉林','11');
    }
    eval('document.'+formname+'.'+preP+'.options[11]=tempoption;');
    cityareacode[10]=new Array('1101','1102','1103','1104','1105','1106','1107','1108','1109');
    cityareaname[10]=new Array('长春','吉林','四平','辽源','通化','白山','松原','白城','延边');
    if (selectP=='12') {
        a=12;tempoption=new Option('黑龙江','12',false,true);
    } else {
        tempoption=new Option('黑龙江','12');
    }
    eval('document.'+formname+'.'+preP+'.options[12]=tempoption;');
    cityareacode[11]=new Array('1201','1202','1203','1204','1205','1206','1207','1208','1209','1210','1211','1212','1213');
cityareaname[11]=new Array('哈尔滨','齐齐哈尔','牡丹江','佳木斯','大庆','伊春','黑河','鸡西','鹤岗','双鸭山','七台河','绥化','大兴安岭');
    if (selectP=='13') {
        a=13;tempoption=new Option('江苏','13',false,true);
    } else {
        tempoption=new Option('江苏','13');
    }
    eval('document.'+formname+'.'+preP+'.options[13]=tempoption;');
    cityareacode[12]=new Array('1301','1302','1303','1304','1305','1306','1307','1308','1309','1310','1311','1312','1313');
    cityareaname[12]=new Array('南京','苏州','无锡','常州','镇江','连云港 ','扬州','徐州 ','南通','盐城','淮阴','泰州','宿迁');
    if (selectP=='14') {
        a=14;tempoption=new Option('浙江','14',false,true);
    } else {
        tempoption=new Option('浙江','14');
    }
    eval('document.'+formname+'.'+preP+'.options[14]=tempoption;');
    cityareacode[13]=new Array('1401','1402','1403','1404','1405','1406','1407','1408','1409','1410','1411');
    cityareaname[13]=new Array('杭州','湖州','丽水','温州','绍兴','舟山','嘉兴','金华','台州','衢州','宁波');
    if (selectP=='15') {
        a=15;tempoption=new Option('安徽','15',false,true);
    } else {
        tempoption=new Option('安徽','15');
    }
    eval('document.'+formname+'.'+preP+'.options[15]=tempoption;');
    cityareacode[14]=new Array('1501','1502','1503','1504','1505','1506','1507','1508','1509','1510','1511','1512','1513','1514','1515','1516','1517');
    cityareaname[14]=new Array('合肥 ','芜湖 ','蚌埠 ','滁州 ','安庆 ','六安 ','黄山 ','宣城 ','淮南 ','宿州 ','马鞍山 ','铜陵','淮北 ','阜阳 ','池州 ','巢湖 ','亳州');
    if (selectP=='16') {
        a=16;tempoption=new Option('福建','16',false,true);
    } else {
        tempoption=new Option('福建','16');
    }
    eval('document.'+formname+'.'+preP+'.options[16]=tempoption;');
    cityareacode[15]=new Array('1601','1602','1603','1604','1605','1606','1607','1608','1609');
    cityareaname[15]=new Array('福州 ','厦门 ','泉州 ','漳州 ','龙岩 ','南平 ','宁德 ','莆田 ','三明');
    if (selectP=='17') {
        a = 17;tempoption=new Option('江西','17',false,true);
    } else {
        tempoption=new Option('江西','17');
    }
    eval('document.'+formname+'.'+preP+'.options[17]=tempoption;');
    cityareacode[16]=new Array('1701','1702','1703','1704','1705','1706','1707','1708','1709','1710','1711');
    cityareaname[16]=new Array('南昌','景德镇','九江','萍乡','新余','鹰潭','赣州','宜春','吉安','上饶','抚州');
    if (selectP=='18') {
        a=18;tempoption=new Option('山东','18',false,true);
    } else {
        tempoption=new Option('山东','18');
    }
    eval('document.'+formname+'.'+preP+'.options[18]=tempoption;');
    cityareacode[17]=new Array('1801','1802','1803','1804','1805','1806','1807','1808','1809','1810','1811','1812','1813','1814','1815','1816','1817');
    cityareaname[17]=new Array('济南','青岛','淄博','德州','烟台','潍坊','济宁','泰安','临沂','菏泽','威海','枣庄','日照','莱芜','聊城','滨州','东营');
    if (selectP=='19') {
        a=19;tempoption=new Option('河南','19',false,true);
    } else {
        tempoption=new Option('河南','19');
    }
    eval('document.'+formname+'.'+preP+'.options[19]=tempoption;');
    cityareacode[18]=new Array('1901','1902','1903','1904','1905','1906','1907','1908','1909','1910','1911','1912','1913','1914','1915','1916','1917','1918');
    cityareaname[18]=new Array('郑州','开封','洛阳','平顶山','安阳','鹤壁','新乡','焦作','濮阳','许昌','漯河','三门峡','南阳','商丘','周口','驻马店','信阳','济源');
    if (selectP=='20') {
        a=20;tempoption=new Option('湖北','20',false,true);
    } else {
        tempoption=new Option('湖北','20');
    }
    eval('document.'+formname+'.'+preP+'.options[20]=tempoption;');
    cityareacode[19]=new Array('2001','2002','2003','2004','2005','2006','2007','2008','2009','2010','2011','2012','2013','2014','2015','2016','2017');
    cityareaname[19]=new Array('武汉','黄石','十堰','荆州','宜昌','襄樊','鄂州','荆门','孝感','黄冈','咸宁','恩施','随州','仙桃','天门','潜江','神农架');
    if (selectP=='21') {
        a = 21;
        tempoption = new Option('湖南','21',false,true);
    } else {
        tempoption=new Option('湖南','21');
    }
    eval('document.'+formname+'.'+preP+'.options[21]=tempoption;');
    cityareacode[20]=new Array('2101','2102','2103','2104','2105','2106','2107','2108','2109','2110','2111','2112','2113');
    cityareaname[20]=new Array('长沙','株州','湘潭','衡阳','邵阳','岳阳','常德','郴州','益阳','永州','怀化','娄底','湘西 ');
    if (selectP=='22') {
        a = 22;
        tempoption=new Option('广西','22',false,true);
    } else {
        tempoption = new Option('广西','22');
    }
    eval('document.'+formname+'.'+preP+'.options[22]=tempoption;');
    cityareacode[21]=new Array('2201','2202','2203','2204','2205','2206','2207','2208','2209','2210','2211','2212');
    cityareaname[21]=new Array('南宁','柳州','桂林','梧州','北海','防城港','钦州','贵港','玉林','贺州','百色','河池');
    if (selectP=='23') {
        a = 23;
        tempoption=new Option('海南','23',false,true);
    } else {
        tempoption=new Option('海南','23');
    }
    eval('document.'+formname+'.'+preP+'.options[23]=tempoption;');
    cityareacode[22]=new Array('2301','2302','2303','2304','2305','2306','2307','2308','2309');
    cityareaname[22]=new Array('海口 ','三亚','通什','琼海','琼山','文昌','万宁','东方','儋州');
    if (selectP=='24') {
        a = 24;
        tempoption=new Option('四川','24',false,true);
    } else {
        tempoption=new Option('四川','24');
    }
    eval('document.'+formname+'.'+preP+'.options[24]=tempoption;');
    cityareacode[23]=new Array('2401','2402','2403','2404','2405','2406','2407','2408','2409','2410','2411','2412','2413','2414','2415','2416','2417','2418','2419','2420');
    cityareaname[23]=new Array('成都','自贡','攀枝花','泸州','德阳','绵阳','广元','遂宁','内江','乐山','南充 ','宜宾','广安','达川','巴中','雅安','眉山 ','阿坝 ','甘孜 ','凉山 ');
    if (selectP=='25') {
        a = 25;
        tempoption=new Option('贵州','25',false,true);
    } else {
        tempoption=new Option('贵州','25');
    }
    eval('document.'+formname+'.'+preP+'.options[25]=tempoption;');
    cityareacode[24]=new Array('2501','2502','2503','2504','2505','2506','2507','2508','2509');
    cityareaname[24]=new Array('贵阳 ','六盘水','遵义','铜仁','毕节','安顺','黔西南 ','黔东南','黔南');
    if (selectP=='26') {
        a = 26;
        tempoption = new Option('云南','26',false,true);
    } else {
        tempoption=new Option('云南','26');
    }
    eval('document.'+formname+'.'+preP+'.options[26]=tempoption;');
    cityareacode[25]=new Array('2601','2602','2603','2604','2605','2606','2607','2608','2609','2610','2611','2612','2613','2614','2615','2616','2617');
    cityareaname[25]=new Array('昆明','东川','曲靖','玉溪','昭通','思茅','临沧','保山','丽江','文山 ','红河 ','西双版纳 ','楚雄 ','大理 ','德宏 ','怒江','迪庆');
    if (selectP=='27') {
        a = 27;
        tempoption = new Option('西藏','27',false,true);
    } else { 
        tempoption = new Option('西藏','27');
    }
    eval('document.'+formname+'.'+preP+'.options[27]=tempoption;');
    cityareacode[26]=new Array('2701','2702','2703','2704','2705','2706','2707');
    cityareaname[26]=new Array('拉萨','那曲','昌都','山南','日喀则','阿里','林芝');
    if (selectP=='28') {
        a = 28;
        tempoption=new Option('陕西','28',false,true);
    } else {
        tempoption=new Option('陕西','28');
    }
    eval('document.'+formname+'.'+preP+'.options[28]=tempoption;');
    cityareacode[27]=new Array('2801','2802','2803','2804','2805','2806','2807','2808','2809','2810');
    cityareaname[27]=new Array('西安','铜川','宝鸡','咸阳','渭南','延安','汉中','榆林','商洛','安康');
    if (selectP=='29') {
        a = 29;
        tempoption=new Option('甘肃','29',false,true);
    } else {
        tempoption=new Option('甘肃','29');
    }
    eval('document.'+formname+'.'+preP+'.options[29]=tempoption;');
    cityareacode[28]=new Array('2901','2902','2903','2904','2905','2906','2907','2908','2909','2910','2911','2912','2913','2914');
    cityareaname[28]=new Array('兰州','金昌','白银','天水','嘉峪关','定西','平凉','庆阳','陇南','武威','张掖','酒泉','甘南 ','临夏');
    if (selectP=='30') {
        a = 30;
        tempoption=new Option('青海','30',false,true);
    } else {
        tempoption=new Option('青海','30');
    }
    eval('document.'+formname+'.'+preP+'.options[30]=tempoption;');
    cityareacode[29]=new Array('3001','3002','3003','3004','3005','3006','3007','3008');
    cityareaname[29]=new Array('西宁','海东',' 海北 ','黄南','海南','果洛','玉树','海西');
    if (selectP=='31') {
        a = 31;
        tempoption = new Option('宁夏','31',false,true);
    } else {
        tempoption=new Option('宁夏','31');
    }
    eval('document.'+formname+'.'+preP+'.options[31]=tempoption;');
    cityareacode[30]=new Array('3101','3102','3103','3104');
    cityareaname[30]=new Array('银川','石嘴山','银南','固原');
    if (selectP=='32') {
        a = 32;
        tempoption=new Option('新疆','32',false,true);
    } else {
        tempoption=new Option('新疆','32');
    }
    eval('document.'+formname+'.'+preP+'.options[32]=tempoption;');
    cityareacode[31]=new Array('3201','3202','3203','3204','3205','3206','3207','3208','3209','3210','3211','3212','3213');
    cityareaname[31]=new Array('乌鲁木齐','克拉玛依','石河子','吐鲁番','哈密','和田','阿克苏','喀什','克孜勒苏','巴音郭楞','昌吉','博尔塔拉','伊犁');
    if (selectP=='33') {
        a = 33;
        tempoption=new Option('香港','33',false,true);
    } else {
        tempoption=new Option('香港','33');
    }
    eval('document.'+formname+'.'+preP+'.options[33]=tempoption;');
    cityareacode[32]=new Array();
    cityareaname[32]=new Array();
    if (selectP == '34') {
        a = 34;
        tempoption = new Option('澳门','34',false,true);
    } else {
        tempoption=new Option('澳门','34');
    }
    eval('document.'+formname+'.'+preP+'.options[34]=tempoption;');
    cityareacode[33]=new Array();
    cityareaname[33]=new Array();
    if (selectP=='35') {
        a = 35;
        tempoption = new Option('台湾','35',false,true);
    } else {
        tempoption=new Option('台湾','35');
    }
    eval('document.'+formname+'.'+preP+'.options[35]=tempoption;');
    cityareacode[34]=new Array();
    cityareaname[34]=new Array();
    eval('document.'+formname+'.'+preP+'.options[a].selected=true;');
    cityid=selectP;
    if (cityid!='0') {
        b = 0 ;
        for (i = 0; i < cityareaname[cityid-1].length; i++) {
            if (selectC==cityareacode[cityid-1][i]) {
                b = i+1;
                tempoption=new Option(cityareaname[cityid-1][i],cityareacode[cityid-1][i],false,true);
            } else {
                tempoption=new Option(cityareaname[cityid-1][i],cityareacode[cityid-1][i]);
            }
            eval('document.'+formname+'.'+preC+'.options[i+1]=tempoption;');
        }
        eval('document.'+formname+'.'+preC+'.options[b].selected=true;');
    }
}

function selectcityarea(preP,preC,formname) {
    cityid=eval('document.'+formname+'.'+preP+'.selectedIndex;');
    j = eval('document.'+formname+'.'+preC+'.length;');
    for (i=1;i<j;i++) {
        eval('document.'+formname+'.'+preC+'.options[j-i]=null;')
    }
    if (cityid!="0") {
        for (i=0;i<cityareaname[cityid-1].length;i++) {
            tempoption=new Option(cityareaname[cityid-1][i],cityareacode[cityid-1][i]);
            eval('document.'+formname+'.'+preC+'.options[i+1]=tempoption;');
        }
    }
}
</script>

<form action="/procProvince" method="post or get" name="form1">
    <label>省份:</label>
    <select name="selectp" onchange="selectcityarea('selectp','selectc','form1');">
        <option value="0" selected="">不限
        </option>
    </select>

    <label>县/市:</label>
    <select name="selectc">
        <option value="0" selected="">不限
        </option>
    </select>

    <input type="submit" value="确定" style=“width: 100px” />
</form>

<script language="javascript">
first("selectp","selectc","form1",0,0);
</script>

B、说明、解释,使用技巧

i、如果不加改造,用法是这样的

此代码虽然可以正常工作。
比如将它放在shttpd服务器的web目录下,访问127.0.0.1:port/xx.html 时,可以成功看到全国省市级联下拉框,能够完美工作。
  点击确定按钮,能够向服务器发送两个值,把 method 设置为 get,这样可以在浏览器中看到发送的具体内容:selectp=03&selectc=0307
ii、默认用法的缺陷
1、采用独立文件,无法与其它功能集成在同一个界面显示
  如果硬要采用 <iframe> 将它们嵌套在一起,那么当向后台发送请示后,后台回复的数据,只会显示在原来<iframe>所在区域,千万显示混乱

2、此段代码,量虽然大,绝大部分是js代码。
  所以可以将其分割为 provinceSelect.js 和 provinceSelect.html 两个文件。
  html部分因为内容不多,采用 shttpd_printf 打印出来也是可以的。
  这样一来,在 shttpd C 源码中,就轻松包含了一个功能👍强大,而且完整的 html 代码。而 C 代码本身,还很小巧漂亮。

3、采用默认方法,向后台传送的是数字给编码 03为省,0307为市,具体表示什么需要在 C 中建立相应的数组,进行对比查询。
   这实际是最具风险的事情,因为数组可能位置错位,造成很大的麻烦。而且建立全国省市数组,也是不小的工作量。
   希望希望能够直接传输省市字符串到后台,后台收到后直接使用。如江苏连云港,后台的C代码不用对数字进行处理
iiii、解决办法
1、对于文件过大,不够灵活的问题,采用分割原文件为 proinceSelect.js 和 html 的方式解决

2、对于无法与其它控件在同一个页面显示问题,采用 shttpd_printf() 输出的方式来解决

3、对于后台不想接收数字,想接收字符串的问题
    可以在原来的<form>中,再增加一个<input type="hidden" /> 控件,一旦用户点击了确定按钮,首先从两个下拉框中取出当前的省市值,赋值给隐藏控件的value,然后再调用<form>的submit函数,将它内部的所有控件值传给后台。
    后台收到数据,直接跳过前面的两个数字,只提取隐藏控件中的值,即省市字符串。

综上所述,在shttpd中,需要添加的代码为

shttpd_printf(arg,"%s",
    "<form id=\"allData\" action=\"\" method=\"get\" name=\"form1\"> "
    "<fieldset><legend>省市</legend>"
    "<label>省</label>"
    "<select id=\"provinceSlect\" name=\"selectp\" onChange=\"selectcityarea('selectp','selectc','form1');\“ style=\"width:80px\" >"
    "   <option value=\"0\" selected>全国</option>"
    "</select>"

    "<label>市</label>"
    "<select id=\"citySelect\" name=\"selectc\" style=\"width:80px;\" >"
    "   <option value=\"0\" selected>全省</option>
    "</selected>"

    "<label>运营商</label>"
    "<select id=\"isp\" style=\"xxx\">"
    "   <option value=\"0\" selected>全部</option>"
    "....三大运营商...."
    "</selected>"

    //这行是辅助传数据的控件,是关键技术
    "<input type=\"hidden\" id=\"auxData\" value=\"\" />"

    //这行增加一个手工输入框,为防止下拉框中内容不全
    "<input type=\"text\" id=\"mannualInput\" name=\"manualInput\" placeholder=\"输入框提示请,请注意\" style=\"width:800px\" >"
    "<input type=\"submit\" onclick=\"confirmSelect()\" value=\"确定查询\" style=\"width:100px\" />"
    "</fieldset>"
    "</form>"
    )

###4、为了将下拉框的值赋值给 <input>控件,需要如下代码

function confirmSelect() {
    var province=("#provinceSelect option:selected");     var cityText=("#citySelect option:selected")
    var ISP=("#ISP option:selected")     //得到三个下拉框的值,将它们合并成一个字符串     var auxData=province.text()+','+cityText.text()+','+ISP.text();     //将合并后的值赋值给隐藏控件     ('#auxData').val(auxData);
    //最后调用<form>的submit函数
    $("#allData").submit();
}

四、js 怎样根据控件ID,定位它的位置

1、方法一:js原生方法

var oDiv = document.getElementById('divID');
alert(oDiv.getBoundingClientRect().left)
alert(oDiv.getBoundingClientRect().top)

2、方法二,为什么要这么费事

function getElementPos(elementId) {
    var ua = navigator.userAgent.toLowerCase();
    var isOpera = (ua.indexOf('opera') != -1);
    var isIE = (ua.indexOf('msie') != -1 && !isOpera); // not opera spoof
    var el = document.getElementById(elementId);
    if (el.parentNode === null ||
        el.style.display == 'none') {
        return false;
    }
    var parent = null;
    var pos = [];
    var box;
    if (el.getBoundingClientRect) //IE
    {
        box = el.getBoundingClientRect();
        var scrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
        var scrollLeft = Math.max(document.documentElement.scrollLeft, document.body.scrollLeft);
        return {
            x: box.left + scrollLeft,
            y: box.top + scrollTop
        };
    }
    else if (document.getBoxObjectFor) //gecko
    {
        box = document.getBoxObjectFor(el);
        var borderLeft = (el.style.borderLeftWidth) ? parseInt(el.style.borderLeftWidth) : 0;
        var borderTop = (el.style.borderTopWidth) ? parseInt(el.style.borderTopWidth) : 0;
        pos = [box.x - borderLeft, box.y - borderTop];
    }
    else // safari & opera
    {
        pos = [el.offsetLeft, el.offsetTop];
        parent = el.offsetParent;
        if (parent != el) {
            while (parent) {
                pos[0] += parent.offsetLeft;
                pos[1] += parent.offsetTop;
                parent = parent.offsetParent;
            }
        }
        if (ua.indexOf('opera') != -1 ||
            (ua.indexOf('safari') != -1 &&
             el.style.position == 'absolute')) {
            pos[0] -= document.body.offsetLeft;
            pos[1] -= document.body.offsetTop;
        }
    }
    if (el.parentNode) {
        parent = el.parentNode;
    } else {
        parent = null;
    }
    while (parent &&
           parent.tagName != 'BODY' && parent.tagName != 'HTML') { // account for any scrolled ancestors
        pos[0] -= parent.scrollLeft;
        pos[1] -= parent.scrollTop;
        if (parent.parentNode) {
            parent = parent.parentNode;
        } else {
            parent = null;
        }
    }
    return {
        x: pos[0],
        y: pos[1]
    };
}

3、一个应用,将鼠标放到表格某项时,弹出相关数据,要求内容跟随鼠标

根据上述函数,以控件ID获取其左上角坐标,然后在新位置弹出表格即可
实验代码如下,建立一个table,为某些单元格分配ID
再建立两个默认隐藏的table,当鼠标移到相关位置时,将其显示,但是需要更改其位置信息

<html>
    <body>
        <p>鼠标移动到该段落</p>
        <table>
            <tr><th>name</th><th>detail</th></tr>
            <tr><td>zhou</td><td id="zhouMore">more</td></tr>
            <tr><td>wang</td><td id="wangMore">more</td></tr>
        </table>

        <table id="zhouDescript" width="400px" border="3px"
               cellspacing="0px" style="visibility:hidden">
            <tr><th>province</th><th>city</th><th>country</th></tr>
            <tr><td>jiangsu</td><td>lianyungang</td><td>china</td></tr>
        </table>

        <table id="wangDescript" width="400px" border="3px"
               cellspacing="0px" style="visibility:hidden">
            <tr><th>province</th><th>city</th><th>country</th></tr>
            <tr><td>sichuan</td><td>chendu</td><td>china</td></tr>
        </table>
    </body>
</html>


为了达到鼠标挪到表格时,将隐藏表格显示出来,需要加入如下 js 代码进行处理
这段代码采用jQuery写成

$(document).ready(function(){
    //$("p")表示对所有的<p>标签类型
    $("p").hover(  //这是一个基础代码,如果在函数开始加一名printf
        function() { //这是将鼠标挪到P上时发生的事件
            $("p").css("background-color", "yellow");
            //document.write("<h1>this is a heading!</h1>");
        },function(){//这是将鼠标挪走时,发生的事件
            $("p").css("background-color", "pink");
    })
    //$("#zhouMore")表示对ID值zhouDescript的对象进行操作
    $("#zhouMore").hover(
        function(){
            var basePos=getElementPos("zhouMore");
            //鼠标挪过来之后,将关键区域颜色标红
            $("#zhouMore").css("background-color", "pink");
            $("#zhouDescript").css("position","absolute");
            $("#zhouDescript").css(
            {
                "left":150 + "px",
                "top":basePos.y + "px",
                "color":"white",
                "background-color":"#98bf21",
                "font-family":"Arial",
                "font-size":"20px",
                "padding":"5px"
            });
            $("#zhouDescript").css("visibility","visible");
        },function(){
            $("#zhouMore").css("background-color":"white");
            $("#zhouDescript").css("visibility","hidden");
        }
    )
    $("#wangMore").hover(
        function(){
            var basePos=getElementPos("#wangMore");
            $("#wangMore").css("background-color":"pink");
            $("#wangDescript").css({
                "position":"absolute",
                "left":150 + "px",
                "top":basePos.y + "px",
                "color":"white",
                "background-color":"#98bf21",
                "font-family":"Arial",
                "font-size":"20px",
                "padding":"5px"
            })
            $("#wangDescript").css("visibility","visible");
        },function(){
            $("#wangMore").css("background-color","white");
            $("#wangMore").css("visibility":"hidden");
        });
});

4、感慨

当时想用这个功能的时候,引入一个bug。
折腾了一天,都没有查出来,最终只得放弃。
今日手工誊写,一眼看出故障所在。

五、js + html 实现简易考试系统

1、第一种

<html>
<head>
<meta http-equiv="Content-Language" mrc="zh-cn">
<meta http-equiv="Content-Type" mrc="text/html; charset=gb2312">
<title>JavaScript在线考试系统</title>
</head>
<body>
<SCRIPT LANGUAGE="JavaScript">
<!-- 
function scorequiz(form) {  var list=0  if(form.Q1.value!=null  &&  form.Q1.value=="*") {list=list+10}  if(form.Q2.value!=null  &&  form.Q2.value=="*") {list=list+10}  if(form.Q3.value!=null  &&  form.Q3.value=="*") {list=list+10}  if(form.Q4.value!=null  &&  form.Q4.value=="*") {list=list+10}  if(form.Q5.value!=null  &&  form.Q5.value=="*") {list=list+10}  if(form.Q6.value!=null  &&  form.Q6.value=="*") {list=list+10}  if(form.Q7.value!=null  &&  form.Q7.value=="*") {list=list+10}  if(form.Q8.value!=null  &&  form.Q8.value=="*") {list=list+10}  if(form.Q9.value!=null  &&  form.Q9.value=="*") {list=list+10}  if(form.Q10.value!=null  &&  form.Q10.value=="*") {list=list+10}  //alert(list);  form.SCORE.value =eval(list)  if (form.SCORE.value<=80){
ok="加油呀,你这次不及格哦!"
alert(ok)}
}  
function clearquiz(form) {  var list=0  form.Q1.value="?"  form.Q2.value="?"  form.Q3.value="?"  form.Q4.value="?"  form.Q5.value="?"  form.Q6.value="?"  form.Q7.value="?"  form.Q8.value="?"  form.Q9.value="?"  form.Q10.value="?"  form.SCORE.value =eval(list)
}
function msg() {  alert("您的成绩将E-mail给相关人员。")  }
// -->
</SCRIPT>
<body>
<table width="75%" border="0" align="center">   <tr>     <td>        <form name="quiz1"   action ="mailto:www.baidu.com@163.com"  method="post" enctype="text/plain"   onLoad="clearquiz(this.form)" onSubmit="msg()">         您的姓名:            <input type="Text" name="Name" value="" class="pt9">         <hr>         1. Internet上使用的网络协议是:__________            <ol>           <input type="radio" name="Q1" value="*TCP/IP" onClick=Q1.value="*">           TCP/IP           <input type="radio" name="Q1" value="X.25" onClick=Q1.value="X.25">           X.25           <input type="radio" name="Q1" value="NetBEUI" onClick=Q1.value="NetBEUI">           NetBEUI         </ol>         2. 我们通过__________长距离连接两台计算机使它们通讯。            <ol>           <input type="radio" name="Q2" value="LAN" onClick=Q2.value="A">           LAN              <input type="radio" name="Q2" value="*WAN" onClick=Q2.value="*">           WAN              <input type="radio" name="Q2" value="WAIS" onClick=Q2.value="C">           WAIS         </ol>         3. HUB是指:__________            <ol>           <input type="radio" name="Q3" value="网关" onClick=Q3.value="网关">           网关              <input type="radio" name="Q3" value="网桥" onClick=Q3.value="网桥">           网桥              <input type="radio" name="Q3" value="路由器" onClick=Q3.value="路由器">           路由器              <input type="radio" name="Q3" value="*集线器" onClick=Q3.value="*">           集线器            </ol>         4. 56K MODEM的传输速度为:__________            <ol>           <input type="radio" name="Q4" value="56Kbytes" onClick=Q4.value="A">           每秒传输56K bytes的数据。               <input type="radio" name="Q4" value="*56kbps" onClick=Q4.value="*">           每秒传输56k bits的数据。             </ol>         5. Gateway是指:__________            <ol>           <input type="radio" name="Q5" value="*网关" onClick=Q5.value="*">           网关              <input type="radio" name="Q5" value="网桥" onClick=Q5.value="B">           网桥              <input type="radio" name="Q5" value="路由器" onClick=Q5.value="C">           路由器              <input type="radio" name="Q5" value="集线器" onClick=Q5.value="D">           集线器            </ol>         6. Http默认的端口是:__________            <ol>           <input type="radio" name="Q6" value="8080" onClick=Q6.value="A">           8080             <input type="radio" name="Q6" value="*80" onClick=Q6.value="*">           80             <input type="radio" name="Q6" value="25" onClick=Q6.value="25">           25           </ol>         7. JavaScript是一种类似于C++的语言,比较难掌握:__________            <ol>           <input type="radio" name="Q7" value="True" onClick=Q7.value="T">           True             <input type="radio" name="Q7" value="*False" onClick=Q7.value="*">           False           </ol>         8. JAVA是 __________ 公司的产品:            <ol>           <input type="radio" name="Q8" value="Microsoft" onClick=Q8.value="A">           Microsoft              <input type="radio" name="Q8" value="Novell" onClick=Q8.value="B">           Novell              <input type="radio" name="Q8" value="*SUN" onClick=Q8.value="*">           SUN            </ol>         9. CGI通过__________收集用户填写的表单信息:            <ol>           <input type="radio" name="Q9" value="Form" onClick=Q9.value="A">           Form              <input type="radio" name="Q9" value="*环境变量" onClick=Q9.value="*">           环境变量              <input type="radio" name="Q9" value="javascripts脚本" onClick=Q9.value="C">           javascripts脚本            </ol>         10. 服务器响应"404 error"的含义是: __________            <ol>           <input type="radio" name="Q10" value="*文件不存在" onClick=Q10.value="*">           文件不存在           <input type="radio" name="Q10" value="Server error" onClick=Q10.value="B">           服务器错误        <input type="radio" name="Q10" value="Client error" onClick=Q10.value="C">           浏览器错误            </ol>         <hr>         <input type="button" name="submit" value="开始评分" onClick=scorequiz(this.form) class="pt9">         <input type="reset" name="reset" value="重新测试" onClick=clearquiz(this.form) class="pt9">           <input type="submit" value="将成绩MAIL给管理员" onClick=scorequiz(this.form) name="submit" class="pt9">           你的考试成绩:            <input  name="SCORE" type="text" class="pt9">       </form>
</body>
</html>

2、第二种(全是单选题,从网站原文复制,只是减少了题目)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>在线考试系统</title>
<style type="text/css">
*{padding:0;margin:0;}
body{
font-size:14px;
background-color:#F9FCFD;
}
.highlight{ background-color:#FCFFD7;}
.selected{background-color:#FFEBD7;}
.div_topic{
padding:10px;
border:#73AFD0 dashed;
border-width: 0 0 1px 0;
background-color:#F9FCFD;
}
.div_topic.odd{background-color: #E0EEF5;}
.div_topic h4{
font-size:16px;
color:#22536A;
display:block;
padding:3px;
}
.div_topic p{
display:block;
padding:3px;
}
.div_topic h4 span{
padding:0 0 0 20px;
color:#FF0000;
display:none;
}
#fixedLayer {
width:100%;
background-color: #A3CBE0;
border:1px solid #73AFD0;
position:fixed;
left:0px;
top:0px;
}
#answer_last {
width:150px;
padding:10px;
background-color:#FFCC00;
border:1px dashed #888313;
position:fixed;
right:0px;
top:42px;
display:none;
}
#fixedLayer div{
padding:10px;
}
#topic_num{
width:30px;
text-align:center;
}
#topic_right{
float:right;
}
#status{
color:#FF0000;
font-size:14;
font-weight:bold;}
/*打印时,背景为白色*/
@media print{
body{
font-size:12px;
background-color:#fff;
}
.div_topic{
padding:5px;
border:#000 dashed;
border-width: 0 0 1px 0;
background-color:#fff;
}
.div_topic h4{
font-size:14px;
color:#000;
}
.div_topic h4 span{ display:none}
#answer_last {
background-color:#eee;
border:1px dashed #333;
color:#000000;
display:block;
position: static;
width:auto;
height:auto;
}
}
</style>
<!--[if IE]>
<style type="text/css">
html {height:100%;overflow:hidden;}
body {height:100%;overflow:auto;}
#fixedLayer{position:absolute;}
#answer_last{position:absolute;}
</style>
<![endif]-->
<script>
// 烈火網 liehuo.net 欢迎复制,拒绝恶意采集 liehuo.net
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(J(){7(1e.19)L w=1e.19;L E=1e.19=J(a,b){K 1D E.2m.4Y(a,b)};7(1e.)L D=1e.;1e.=E;L u=/^[^<]*(<(.|\\s)+>)[^>]*|^#(\\w+)/;L G=/^.[^:#\\[\\.]*/;E.1i=E.2m={4Y:J(d,b){d=d||T;7(d.15){6[0]=d;6.M=1;K 6}N 7(1v d=="25"){L c=u.39(d);7(c&&(c[1]||!b)){7(c[1])d=E.5c([c[1]],b);N{L a=T.5N(c[3]);7(a)7(a.2s!=c[3])K E().2r(d);N{6[0]=a;6.M=1;K 6}N d=[]}}N K 1D E(b).2r(d)}N 7(E.1q(d))K 1D E(T)[E.1i.21?"21":"43"](d);K 6.6G(d.1n==1N&&d||(d.5j||d.M&&d!=1e&&!d.15&&d[0]!=10&&d[0].15)&&E.2H(d)||[d])},5j:"1.2.2",82:J(){K 6.M},M:0,22:J(a){K a==10?E.2H(6):6[a]},2E:J(b){L a=E(b);a.56=6;K a},6G:J(a){6.M=0;1N.2m.1h.1j(6,a);K 6},V:J(a,b){K E.V(6,a,b)},5E:J(b){L a=-1;6.V(J(i){7(6==b)a=i});K a},1K:J(c,a,b){L d=c;7(c.1n==4d)7(a==10)K 6.M&&E[b||"1K"](6[0],c)||10;N{d={};d[c]=a}K 6.V(J(i){P(c 1r d)E.1K(b?6.Y:6,c,E.1l(6,d[c],b,i,c))})},1m:J(b,a){7((b==\'29\'||b==\'1P\')&&2M(a)<0)a=10;K 6.1K(b,a,"2q")},1t:J(b){7(1v b!="4D"&&b!=W)K 6.4B().3t((6[0]&&6[0].2u||T).5v(b));L a="";E.V(b||6,J(){E.V(6.3p,J(){7(6.15!=8)a+=6.15!=1?6.6M:E.1i.1t([6])})});K a},5r:J(b){7(6[0])E(b,6[0].2u).5J().3n(6[0]).2a(J(){L a=6;2e(a.1B)a=a.1B;K a}).3t(6);K 6},8t:J(a){K 6.V(J(){E(6).6C().5r(a)})},8m:J(a){K 6.V(J(){E(6).5r(a)})},3t:J(){K 6.3P(1a,R,S,J(a){7(6.15==1)6.3k(a)})},6s:J(){K 6.3P(1a,R,R,J(a){7(6.15==1)6.3n(a,6.1B)})},6o:J(){K 6.3P(1a,S,S,J(a){6.1b.3n(a,6)})},5a:J(){K 6.3P(1a,S,R,J(a){6.1b.3n(a,6.2J)})},3h:J(){K 6.56||E([])},2r:J(b){L c=E.2a(6,J(a){K E.2r(b,a)});K 6.2E(/[^+>] [^+>]/.17(b)||b.1g("..")>-1?E.57(c):c)},5J:J(e){L f=6.2a(J(){7(E.14.1d&&!E.3W(6)){L a=6.6c(R),5u=T.2R("1u"),4T=T.2R("1u");5u.3k(a);4T.38=5u.38;K 4T.1B}N K 6.6c(R)});L d=f.2r("*").4R().V(J(){7(6[F]!=10)6[F]=W});7(e===R)6.2r("*").4R().V(J(i){7(6.15==3)K;L c=E.Q(6,"2N");P(L a 1r c)P(L b 1r c[a])E.16.1c(d[i],a,c[a][b],c[a][b].Q)});K f},1F:J(b){K 6.2E(E.1q(b)&&E.3x(6,J(a,i){K b.1O(a,i)})||E.3d(b,6))},4I:J(b){7(b.1n==4d)7(G.17(b))K 6.2E(E.3d(b,6,R));N b=E.3d(b,6);L a=b.M&&b[b.M-1]!==10&&!b.15;K 6.1F(J(){K a?E.35(6,b)<0:6!=b})},1c:J(a){K!a?6:6.2E(E.34(6.22(),a.1n==4d?E(a).22():a.M!=10&&(!a.12||E.12(a,"3i"))?a:[a]))},3K:J(a){K a?E.3d(a,6).M>0:S},7g:J(a){K 6.3K("."+a)},5P:J(b){7(b==10){7(6.M){L c=6[0];7(E.12(c,"2y")){L e=c.44,5L=[],11=c.11,30=c.U=="2y-30";7(e<0)K W;P(L i=30?e:0,2b=30?e+1:11.M;i<2b;i++){L d=11[i];7(d.2p){b=E.14.1d&&!d.9s.1C.9o?d.1t:d.1C;7(30)K b;5L.1h(b)}}K 5L}N K(6[0].1C||"").1p(/\\r/g,"")}K 10}K 6.V(J(){7(6.15!=1)K;7(b.1n==1N&&/5w|5y/.17(6.U))6.3o=(E.35(6.1C,b)>=0||E.35(6.37,b)>=0);N 7(E.12(6,"2y")){L a=b.1n==1N?b:[b];E("90",6).V(J(){6.2p=(E.35(6.1C,a)>=0||E.35(6.1t,a)>=0)});7(!a.M)6.44=-1}N 6.1C=b})},3q:J(a){K a==10?(6.M?6[0].38:W):6.4B().3t(a)},6P:J(a){K 6.5a(a).1Y()},6N:J(i){K 6.2V(i,i+1)},2V:J(){K 6.2E(1N.2m.2V.1j(6,1a))},2a:J(b){K 6.2E(E.2a(6,J(a,i){K b.1O(a,i,a)}))},4R:J(){K 6.1c(6.56)},3P:J(g,f,h,d){L e=6.M>1,3m;K 6.V(J(){7(!3m){3m=E.5c(g,6.2u);7(h)3m.8I()}L b=6;7(f&&E.12(6,"1V")&&E.12(3m[0],"4x"))b=6.3V("1S")[0]||6.3k(6.2u.2R("1S"));L c=E([]);E.V(3m,J(){L a=e?E(6).5J(R)[0]:6;7(E.12(a,"1o")){c=c.1c(a)}N{7(a.15==1)c=c.1c(E("1o",a).1Y());d.1O(b,a)}});c.V(6D)})}};E.2m.4Y.2m=E.2m;J 6D(i,a){7(a.3R)E.3Q({1f:a.3R,3l:S,1G:"1o"});N E.5l(a.1t||a.6A||a.38||"");7(a.1b)a.1b.2X(a)}E.1s=E.1i.1s=J(){L b=1a[0]||{},i=1,M=1a.M,5i=S,11;7(b.1n==8f){5i=b;b=1a[1]||{};i=2}7(1v b!="4D"&&1v b!="J")b={};7(M==1){b=6;i=0}P(;i<M;i++)7((11=1a[i])!=W)P(L a 1r 11){7(b===11[a])6z;7(5i&&11[a]&&1v 11[a]=="4D"&&b[a]&&!11[a].15)b[a]=E.1s(b[a],11[a]);N 7(11[a]!=10)b[a]=11[a]}K b};L F="19"+(1D 3O()).3N(),6y=0,5e={};L H=/z-?5E|89-?87|1y|6q|85-?1P/i;E.1s({81:J(a){1e.=D;7(a)1e.19=w;K E},1q:J(a){K!!a&&1v a!="25"&&!a.12&&a.1n!=1N&&/J/i.17(a+"")},3W:J(a){K a.1I&&!a.1k||a.28&&a.2u&&!a.2u.1k},5l:J(a){a=E.3f(a);7(a){L b=T.3V("6k")[0]||T.1I,1o=T.2R("1o");1o.U="1t/4l";7(E.14.1d)1o.1t=a;N 1o.3k(T.5v(a));b.3k(1o);b.2X(1o)}},12:J(b,a){K b.12&&b.12.2F()==a.2F()},1Q:{},Q:J(c,d,b){c=c==1e?5e:c;L a=c[F];7(!a)a=c[F]=++6y;7(d&&!E.1Q[a])E.1Q[a]={};7(b!=10)E.1Q[a][d]=b;K d?E.1Q[a][d]:a},3H:J(c,b){c=c==1e?5e:c;L a=c[F];7(b){7(E.1Q[a]){2T E.1Q[a][b];b="";P(b 1r E.1Q[a])1T;7(!b)E.3H(c)}}N{1R{2T c[F]}1W(e){7(c.55)c.55(F)}2T E.1Q[a]}},V:J(c,a,b){7(b){7(c.M==10){P(L d 1r c)7(a.1j(c[d],b)===S)1T}N P(L i=0,M=c.M;i-1}},6e:J(b,c,a){L e={};P(L d 1r c){e[d]=b.Y[d];b.Y[d]=c[d]}a.1O(b);P(L d 1r c)b.Y[d]=e[d]},1m:J(d,e,c){7(e=="29"||e=="1P"){L b,3S={3C:"4Z",4X:"23",18:"3u"},3r=e=="29"?["7P","7M"]:["7L","7K"];J 4S(){b=e=="29"?d.7J:d.7I;L a=0,3a=0;E.V(3r,J(){a+=2M(E.2q(d,"7H"+6,R))||0;3a+=2M(E.2q(d,"3a"+6+"62",R))||0});b-=1Z.7E(a+3a)}7(E(d).3K(":4b"))4S();N E.6e(d,3S,4S);K 1Z.2b(0,b)}K E.2q(d,e,c)},2q:J(e,k,j){L d;J 3y(b){7(!E.14.26)K S;L a=T.4a.4L(b,W);K!a||a.4K("3y")==""}7(k=="1y"&&E.14.1d){d=E.1K(e.Y,"1y");K d==""?"1":d}7(E.14.2B&&k=="18"){L c=e.Y.18;e.Y.18="3u";e.Y.18=c}7(k.1E(/4c/i))k=y;7(!j&&e.Y&&e.Y[k])d=e.Y[k];N 7(T.4a&&T.4a.4L){7(k.1E(/4c/i))k="4c";k=k.1p(/([A-Z])/g,"-1").2w();L h=T.4a.4L(e,W);7(h&&!3y(e))d=h.4K(k);N{L f=[],2L=[];P(L a=e;a&&3y(a);a=a.1b)2L.4U(a);P(L i=0;i<2L.M;i++)7(3y(2L[i])){f[i]=2L[i].Y.18;2L[i].Y.18="3u"}d=k=="18"&&f[2L.M-1]!=W?"2D":(h&&h.4K(k))||"";P(L i=0;i<f.M;i++)7(f[i]!=W)2L[i].Y.18=f[i]}7(k=="1y"&&d=="")d="1"}N 7(e.4j){L g=k.1p(/\\-(\\w)/g,J(a,b){K b.2F()});d=e.4j[k]||e.4j[g];7(!/^\\d+(2P)?/i.17(d)&&/^\\d/.17(d)){L l=e.Y.2c,3A=e.3A.2c;e.3A.2c=e.4j.2c;e.Y.2c=d||0;d=e.Y.7l+"2P";e.Y.2c=l;e.3A.2c=3A}}K d},5c:J(l,h){L k=[];h=h||T;7(1v h.2R==\'10\')h=h.2u||h[0]&&h[0].2u||T;E.V(l,J(i,d){7(!d)K;7(d.1n==53)d=d.3D();7(1v d=="25"){d=d.1p(/(<(\\w+)[^>]*?)\\/>/g,J(b,a,c){K c.1E(/^(7k|7h|5Q|7f|48|5O|a3|3v|9Y|9W|9T)/i)?b:a+"></"+c+">"});L f=E.3f(d).2w(),1u=h.2R("1u");L e=!f.1g("<9R")&&[1,"<2y 78=\'78\'>","</2y>"]||!f.1g("<9O")&&[1,"<77>","</77>"]||f.1E(/^<(9K|1S|9I|9F|9A)/)&&[1,"<1V>","</1V>"]||!f.1g("<4x")&&[2,"<1V><1S>","</1S></1V>"]||(!f.1g("<9y")||!f.1g("<9v"))&&[3,"<1V><1S><4x>","</4x></1S></1V>"]||!f.1g("<5Q")&&[2,"<1V><1S></1S><76>","</76></1V>"]||E.14.1d&&[1,"1u<1u>","</1u>"]||[0,"",""];1u.38=e[1]+d+e[2];2e(e[0]--)1u=1u.5D;7(E.14.1d){L g=!f.1g("<1V")&&f.1g("<1S")<0?1u.1B&&1u.1B.3p:e[1]=="<1V>"&&f.1g("<1S")<0?1u.3p:[];P(L j=g.M-1;j>=0;--j)7(E.12(g[j],"1S")&&!g[j].3p.M)g[j].1b.2X(g[j]);7(/^\\s/.17(d))1u.3n(h.5v(d.1E(/^\\s*/)[0]),1u.1B)}d=E.2H(1u.3p)}7(d.M===0&&(!E.12(d,"3i")&&!E.12(d,"2y")))K;7(d[0]==10||E.12(d,"3i")||d.11)k.1h(d);N k=E.34(k,d)});K k},1K:J(d,e,c){7(!d||d.15==3||d.15==8)K 10;L f=E.3W(d)?{}:E.3S;7(e=="2p"&&E.14.26)d.1b.44;7(f[e]){7(c!=10)d[f[e]]=c;K d[f[e]]}N 7(E.14.1d&&e=="Y")K E.1K(d.Y,"9r",c);N 7(c==10&&E.14.1d&&E.12(d,"3i")&&(e=="9q"||e=="9p"))K d.9n(e).6M;N 7(d.28){7(c!=10){7(e=="U"&&E.12(d,"48")&&d.1b)6Z"U 9i 9g\'t 9b 9a";d.99(e,""+c)}7(E.14.1d&&/6T|3R/.17(e)&&!E.3W(d))K d.4z(e,2);K d.4z(e)}N{7(e=="1y"&&E.14.1d){7(c!=10){d.6q=1;d.1F=(d.1F||"").1p(/6W\\([^)]*\\)/,"")+(2M(c).3D()=="93"?"":"6W(1y="+c*6S+")")}K d.1F&&d.1F.1g("1y=")>=0?(2M(d.1F.1E(/1y=([^)]*)/)[1])/6S).3D():""}e=e.1p(/-([a-z])/92,J(a,b){K b.2F()});7(c!=10)d[e]=c;K d[e]}},3f:J(a){K(a||"").1p(/^\\s+|\\s+/g,"")},2H:J(b){L a=[];7(1v b!="91")P(L i=0,M=b.M;i*",6).1Y();2e(6.1B)6.2X(6.1B)}},J(a,b){E.1i[a]=J(){K 6.V(b,1a)}});E.V(["8e","62"],J(i,c){L b=c.2w();E.1i[b]=J(a){K 6[0]==1e?E.14.2B&&T.1k["5h"+c]||E.14.26&&1e["8d"+c]||T.6I=="6H"&&T.1I["5h"+c]||T.1k["5h"+c]:6[0]==T?1Z.2b(1Z.2b(T.1k["5g"+c],T.1I["5g"+c]),1Z.2b(T.1k["5f"+c],T.1I["5f"+c])):a==10?(6.M?E.1m(6[0],b):W):6.1m(b,a.1n==4d?a:a+"2P")}});L C=E.14.26&&4t(E.14.5n)<8c?"(?:[\\\\w*4s-]|\\\\\\\\.)":"(?:[\\\\w\\8b-\\8a*4s-]|\\\\\\\\.)",6w=1D 4r("^>\\\\s*("+C+"+)"),6v=1D 4r("^("+C+"+)(#)("+C+"+)"),6u=1D 4r("^([#.]?)("+C+"*)");E.1s({5d:{"":"m[2]==\'*\'||19.12(a,m[2])","#":"a.4z(\'2s\')==m[2]",":":{88:"im[3]-0",31:"m[3]-0==i",6N:"m[3]-0==i",3j:"i==0",3M:"i==r.M-1",6r:"i%2==0",6p:"i%2","3j-4m":"a.1b.3V(\'*\')[0]==a","3M-4m":"19.31(a.1b.5D,1,\'4v\')==a","84-4m":"!19.31(a.1b.5D,2,\'4v\')",6E:"a.1B",4B:"!a.1B",83:"(a.6A||a.80||19(a).1t()||\'\').1g(m[3])>=0",4b:\'"23"!=a.U&&19.1m(a,"18")!="2D"&&19.1m(a,"4X")!="23"\',23:\'"23"==a.U||19.1m(a,"18")=="2D"||19.1m(a,"4X")=="23"\',7Y:"!a.2W",2W:"a.2W",3o:"a.3o",2p:"a.2p||19.1K(a,\'2p\')",1t:"\'1t\'==a.U",5w:"\'5w\'==a.U",5y:"\'5y\'==a.U",5b:"\'5b\'==a.U",3J:"\'3J\'==a.U",59:"\'59\'==a.U",6n:"\'6n\'==a.U",6m:"\'6m\'==a.U",2G:\'"2G"==a.U||19.12(a,"2G")\',48:"/48|2y|6l|2G/i.17(a.12)",3E:"19.2r(m[3],a).M",7X:"/h\\\\d/i.17(a.12)",7W:"19.3x(19.3I,J(1i){K a==1i.O;}).M"}},6j:[/^(\\[) *@?([\\w-]+) *([!*^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,1D 4r("^([:.#]*)("+C+"+)")],3d:J(a,c,b){L d,2o=[];2e(a&&a!=d){d=a;L f=E.1F(a,c,b);a=f.t.1p(/^\\s*,\\s*/,"");2o=b?c=f.r:E.34(2o,f.r)}K 2o},2r:J(t,p){7(1v t!="25")K[t];7(p&&p.15!=1&&p.15!=9)K[];p=p||T;L d=[p],2j=[],3M,12;2e(t&&3M!=t){L r=[];3M=t;t=E.3f(t);L o=S;L g=6w;L m=g.39(t);7(m){12=m[1].2F();P(L i=0;d[i];i++)P(L c=d[i].1B;c;c=c.2J)7(c.15==1&&(12=="*"||c.12.2F()==12))r.1h(c);d=r;t=t.1p(g,"");7(t.1g(" ")==0)6z;o=R}N{g=/^([>+~])\\s*(\\w*)/i;7((m=g.39(t))!=W){r=[];L l={};12=m[2].2F();m=m[1];P(L j=0,3g=d.M;j<3g;j++){L n=m=="~"||m=="+"?d[j].2J:d[j].1B;P(;n;n=n.2J)7(n.15==1){L h=E.Q(n);7(m=="~"&&l[h])1T;7(!12||n.12.2F()==12){7(m=="~")l[h]=R;r.1h(n)}7(m=="+")1T}}d=r;t=E.3f(t.1p(g,""));o=R}}7(t&&!o){7(!t.1g(",")){7(p==d[0])d.4k();2j=E.34(2j,d);r=d=[p];t=" "+t.6i(1,t.M)}N{L k=6v;L m=k.39(t);7(m){m=[0,m[2],m[3],m[1]]}N{k=6u;m=k.39(t)}m[2]=m[2].1p(/\\\\/g,"");L f=d[d.M-1];7(m[1]=="#"&&f&&f.5N&&!E.3W(f)){L q=f.5N(m[2]);7((E.14.1d||E.14.2B)&&q&&1v q.2s=="25"&&q.2s!=m[2])q=E(\'[@2s="\'+m[2]+\'"]\',f)[0];d=r=q&&(!m[3]||E.12(q,m[3]))?[q]:[]}N{P(L i=0;d[i];i++){L a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];7(a=="*"&&d[i].12.2w()=="4D")a="3v";r=E.34(r,d[i].3V(a))}7(m[1]==".")r=E.58(r,m[2]);7(m[1]=="#"){L e=[];P(L i=0;r[i];i++)7(r[i].4z("2s")==m[2]){e=[r[i]];1T}r=e}d=r}t=t.1p(k,"")}}7(t){L b=E.1F(t,r);d=r=b.r;t=E.3f(b.t)}}7(t)d=[];7(d&&p==d[0])d.4k();2j=E.34(2j,d);K 2j},58:J(r,m,a){m=" "+m+" ";L c=[];P(L i=0;r[i];i++){L b=(" "+r[i].1w+" ").1g(m)>=0;7(!a&&b||a&&!b)c.1h(r[i])}K c},1F:J(t,r,h){L d;2e(t&&t!=d){d=t;L p=E.6j,m;P(L i=0;p[i];i++){m=p[i].39(t);7(m){t=t.7V(m[0].M);m[2]=m[2].1p(/\\\\/g,"");1T}}7(!m)1T;7(m[1]==":"&&m[2]=="4I")r=G.17(m[3])?E.1F(m[3],r,R).r:E(r).4I(m[3]);N 7(m[1]==".")r=E.58(r,m[2],h);N 7(m[1]=="["){L g=[],U=m[3];P(L i=0,3g=r.M;i<3g;i++){L a=r[i],z=a[E.3S[m[2]]||m[2]];7(z==W||/6T|3R|2p/.17(m[2]))z=E.1K(a,m[2])||\'\';7((U==""&&!!z||U=="="&&z==m[5]||U=="!="&&z!=m[5]||U=="^="&&z&&!z.1g(m[5])||U=="="&&z.6i(z.M-m[5].M)==m[5]||(U=="*="||U=="~=")&&z.1g(m[5])>=0)^h)g.1h(a)}r=g}N 7(m[1]==":"&&m[2]=="31-4m"){L e={},g=[],17=/(-?)(\\d*)n((?:\\+|-)?\\d*)/.39(m[3]=="6r"&&"2n"||m[3]=="6p"&&"2n+1"||!/\\D/.17(m[3])&&"7U+"+m[3]||m[3]),3j=(17[1]+(17[2]||1))-0,d=17[3]-0;P(L i=0,3g=r.M;i<3g;i++){L j=r[i],1b=j.1b,2s=E.Q(1b);7(!e[2s]){L c=1;P(L n=1b.1B;n;n=n.2J)7(n.15==1)n.4p=c++;e[2s]=R}L b=S;7(3j==0){7(j.4p==d)b=R}N 7((j.4p-d)%3j==0&&(j.4p-d)/3j>=0)b=R;7(b^h)g.1h(j)}r=g}N{L f=E.5d[m[1]];7(1v f!="25")f=E.5d[m[1]][m[2]];f=4A("S||J(a,i){K "+f+"}");r=E.3x(r,f,h)}}K{r:r,t:t}},4w:J(b,c){L d=[];L a=b[c];2e(a&&a!=T){7(a.15==1)d.1h(a);a=a[c]}K d},31:J(a,e,c,b){e=e||1;L d=0;P(;a;a=a[c])7(a.15==1&&++d==e)1T;K a},5m:J(n,a){L r=[];P(;n;n=n.2J){7(n.15==1&&(!a||n!=a))r.1h(n)}K r}});E.16={1c:J(f,i,g,e){7(f.15==3||f.15==8)K;7(E.14.1d&&f.54!=10)f=1e;7(!g.2A)g.2A=6.2A++;7(e!=10){L h=g;g=J(){K h.1j(6,1a)};g.Q=e;g.2A=h.2A}L j=E.Q(f,"2N")||E.Q(f,"2N",{}),1x=E.Q(f,"1x")||E.Q(f,"1x",J(){L a;7(1v E=="10"||E.16.52)K a;a=E.16.1x.1j(1a.3G.O,1a);K a});1x.O=f;E.V(i.2d(/\\s+/),J(c,b){L a=b.2d(".");b=a[0];g.U=a[1];L d=j[b];7(!d){d=j[b]={};7(!E.16.2l[b]||E.16.2l[b].4i.1O(f)===S){7(f.3F)f.3F(b,1x,S);N 7(f.6h)f.6h("4h"+b,1x)}}d[g.2A]=g;E.16.2g[b]=R});f=W},2A:1,2g:{},1Y:J(e,h,f){7(e.15==3||e.15==8)K;L i=E.Q(e,"2N"),2f,5E;7(i){7(h==10)P(L g 1r i)6.1Y(e,g);N{7(h.U){f=h.2k;h=h.U}E.V(h.2d(/\\s+/),J(b,a){L c=a.2d(".");a=c[0];7(i[a]){7(f)2T i[a][f.2A];N P(f 1r i[a])7(!c[1]||i[a][f].U==c[1])2T i[a][f];P(2f 1r i[a])1T;7(!2f){7(!E.16.2l[a]||E.16.2l[a].4g.1O(e)===S){7(e.6f)e.6f(a,E.Q(e,"1x"),S);N 7(e.6d)e.6d("4h"+a,E.Q(e,"1x"))}2f=W;2T i[a]}}})}P(2f 1r i)1T;7(!2f){L d=E.Q(e,"1x");7(d)d.O=W;E.3H(e,"2N");E.3H(e,"1x")}}},1U:J(f,b,c,d,g){b=E.2H(b||[]);7(!c){7(6.2g[f])E("*").1c([1e,T]).1U(f,b)}N{7(c.15==3||c.15==8)K 10;L a,2f,1i=E.1q(c[f]||W),16=!b[0]||!b[0].32;7(16)b.4U(6.51({U:f,2K:c}));b[0].U=f;7(E.1q(E.Q(c,"1x")))a=E.Q(c,"1x").1j(c,b);7(!1i&&c["4h"+f]&&c["4h"+f].1j(c,b)===S)a=S;7(16)b.4k();7(g&&E.1q(g)){2f=g.1j(c,a==W?b:b.6Q(a));7(2f!==10)a=2f}7(1i&&d!==S&&a!==S&&!(E.12(c,\'a\')&&f=="50")){6.52=R;1R{c[f]()}1W(e){}}6.52=S}K a},1x:J(c){L a;c=E.16.51(c||1e.16||{});L b=c.U.2d(".");c.U=b[0];L f=E.Q(6,"2N")&&E.Q(6,"2N")[c.U],3B=1N.2m.2V.1O(1a,1);3B.4U(c);P(L j 1r f){L d=f[j];3B[0].2k=d;3B[0].Q=d.Q;7(!b[1]||d.U==b[1]){L e=d.1j(6,3B);7(a!==S)a=e;7(e===S){c.32();c.41()}}}7(E.14.1d)c.2K=c.32=c.41=c.2k=c.Q=W;K a},51:J(c){L a=c;c=E.1s({},a);c.32=J(){7(a.32)a.32();a.7T=S};c.41=J(){7(a.41)a.41();a.7S=R};7(!c.2K)c.2K=c.7R||T;7(c.2K.15==3)c.2K=a.2K.1b;7(!c.4W&&c.4V)c.4W=c.4V==c.2K?c.7Q:c.4V;7(c.6b==W&&c.6a!=W){L b=T.1I,1k=T.1k;c.6b=c.6a+(b&&b.2i||1k&&1k.2i||0)-(b.68||0);c.7O=c.7N+(b&&b.2x||1k&&1k.2x||0)-(b.67||0)}7(!c.3r&&((c.4f||c.4f===0)?c.4f:c.66))c.3r=c.4f||c.66;7(!c.65&&c.64)c.65=c.64;7(!c.3r&&c.2G)c.3r=(c.2G&1?1:(c.2G&2?3:(c.2G&4?2:0)));K c},2l:{21:{4i:J(){5A();K},4g:J(){K}},47:{4i:J(){7(E.14.1d)K S;E(6).2z("4Q",E.16.2l.47.2k);K R},4g:J(){7(E.14.1d)K S;E(6).42("4Q",E.16.2l.47.2k);K R},2k:J(a){7(I(a,6))K R;1a[0].U="47";K E.16.1x.1j(6,1a)}},46:{4i:J(){7(E.14.1d)K S;E(6).2z("4P",E.16.2l.46.2k);K R},4g:J(){7(E.14.1d)K S;E(6).42("4P",E.16.2l.46.2k);K R},2k:J(a){7(I(a,6))K R;1a[0].U="46";K E.16.1x.1j(6,1a)}}}};E.1i.1s({2z:J(c,a,b){K c=="4O"?6.30(c,a,b):6.V(J(){E.16.1c(6,c,b||a,b&&a)})},30:J(d,b,c){K 6.V(J(){E.16.1c(6,d,J(a){E(6).42(a);K(c||b).1j(6,1a)},c&&b)})},42:J(a,b){K 6.V(J(){E.16.1Y(6,a,b)})},1U:J(c,a,b){K 6.V(J(){E.16.1U(c,a,6,R,b)})},63:J(c,a,b){7(6[0])K E.16.1U(c,a,6[0],S,b);K 10},2h:J(){L b=1a;K 6.50(J(a){6.4N=0==6.4N?1:0;a.32();K b[6.4N].1j(6,1a)||S})},7F:J(a,b){K 6.2z(\'47\',a).2z(\'46\',b)},21:J(a){5A();7(E.2Q)a.1O(T,E);N E.3w.1h(J(){K a.1O(6,E)});K 6}});E.1s({2Q:S,3w:[],21:J(){7(!E.2Q){E.2Q=R;7(E.3w){E.V(E.3w,J(){6.1j(T)});E.3w=W}E(T).63("21")}}});L x=S;J 5A(){7(x)K;x=R;7(T.3F&&!E.14.2B)T.3F("61",E.21,S);7(E.14.1d&&1e==3b)(J(){7(E.2Q)K;1R{T.1I.7D("2c")}1W(3e){3z(1a.3G,0);K}E.21()})();7(E.14.2B)T.3F("61",J(){7(E.2Q)K;P(L i=0;i=0){L i=g.2V(e,g.M);g=g.2V(0,e)}c=c||J(){};L f="4J";7(d)7(E.1q(d)){c=d;d=W}N{d=E.3v(d);f="5Z"}L h=6;E.3Q({1f:g,U:f,1G:"3q",Q:d,1z:J(a,b){7(b=="1X"||b=="5Y")h.3q(i?E("<1u/>").3t(a.4e.1p(/<1o(.|\\s)*?\\/1o>/g,"")).2r(i):a.4e);h.V(c,[a.4e,b,a])}});K 6},7q:J(){K E.3v(6.5X())},5X:J(){K 6.2a(J(){K E.12(6,"3i")?E.2H(6.7p):6}).1F(J(){K 6.37&&!6.2W&&(6.3o||/2y|6l/i.17(6.12)||/1t|23|3J/i.17(6.U))}).2a(J(i,c){L b=E(6).5P();K b==W?W:b.1n==1N?E.2a(b,J(a,i){K{37:c.37,1C:a}}):{37:c.37,1C:b}}).22()}});E.V("5W,5V,5U,69,5T,5S".2d(","),J(i,o){E.1i[o]=J(f){K 6.2z(o,f)}});L B=(1D 3O).3N();E.1s({22:J(d,b,a,c){7(E.1q(b)){a=b;b=W}K E.3Q({U:"4J",1f:d,Q:b,1X:a,1G:c})},7o:J(b,a){K E.22(b,W,a,"1o")},7n:J(c,b,a){K E.22(c,b,a,"2O")},7m:J(d,b,a,c){7(E.1q(b)){a=b;b={}}K E.3Q({U:"5Z",1f:d,Q:b,1X:a,1G:c})},7Z:J(a){E.1s(E.4H,a)},4H:{2g:R,U:"4J",2U:0,5R:"49/x-7j-3i-7i",6x:R,3l:R,Q:W,6t:W,3J:W,4n:{3L:"49/3L, 1t/3L",3q:"1t/3q",1o:"1t/4l, 49/4l",2O:"49/2O, 1t/4l",1t:"1t/7e",4o:"*/*"}},4q:{},3Q:J(s){L f,2Y=/=\\?(&|)/g,1A,Q;s=E.1s(R,s,E.1s(R,{},E.4H,s));7(s.Q&&s.6x&&1v s.Q!="25")s.Q=E.3v(s.Q);7(s.1G=="4u"){7(s.U.2w()=="22"){7(!s.1f.1E(2Y))s.1f+=(s.1f.1E(/\\?/)?"&":"?")+(s.4u||"7d")+"=?"}N 7(!s.Q||!s.Q.1E(2Y))s.Q=(s.Q?s.Q+"&":"")+(s.4u||"7d")+"=?";s.1G="2O"}7(s.1G=="2O"&&(s.Q&&s.Q.1E(2Y)||s.1f.1E(2Y))){f="4u"+B++;7(s.Q)s.Q=(s.Q+"").1p(2Y,"="+f+"1");s.1f=s.1f.1p(2Y,"="+f+"1");s.1G="1o";1e[f]=J(a){Q=a;1X();1z();1e[f]=10;1R{2T 1e[f]}1W(e){}7(h)h.2X(g)}}7(s.1G=="1o"&&s.1Q==W)s.1Q=S;7(s.1Q===S&&s.U.2w()=="22"){L i=(1D 3O()).3N();L j=s.1f.1p(/(\\?|&)4s=.*?(&|)/,"a2="+i+"2");s.1f=j+((j==s.1f)?(s.1f.1E(/\\?/)?"&":"?")+"4s="+i:"")}7(s.Q&&s.U.2w()=="22"){s.1f+=(s.1f.1E(/\\?/)?"&":"?")+s.Q;s.Q=W}7(s.2g&&!E.5M++)E.16.1U("5W");7((!s.1f.1g("9Z")||!s.1f.1g("//"))&&(s.1G=="1o"||s.1G=="2O")&&s.U.2w()=="22"){L h=T.3V("6k")[0];L g=T.2R("1o");g.3R=s.1f;7(s.7c)g.9X=s.7c;7(!f){L l=S;g.9V=g.9U=J(){7(!l&&(!6.3c||6.3c=="60"||6.3c=="1z")){l=R;1X();1z();h.2X(g)}}}h.3k(g);K 10}L m=S;L k=1e.7a?1D 7a("9S.9Q"):1D 79();k.9P(s.U,s.1f,s.3l,s.6t,s.3J);1R{7(s.Q)k.4G("9N-9M",s.5R);7(s.5I)k.4G("9L-5H-9J",E.4q[s.1f]||"9H, 9G 9E 9B 5G:5G:5G 9z");k.4G("X-9x-9u","79");k.4G("9t",s.1G&&s.4n[s.1G]?s.4n[s.1G]+", */*":s.4n.4o)}1W(e){}7(s.75)s.75(k);7(s.2g)E.16.1U("5S",[k,s]);L c=J(a){7(!m&&k&&(k.3c==4||a=="2U")){m=R;7(d){74(d);d=W}1A=a=="2U"&&"2U"||!E.73(k)&&"3e"||s.5I&&E.72(k,s.1f)&&"5Y"||"1X";7(1A=="1X"){1R{Q=E.71(k,s.1G)}1W(e){1A="5C"}}7(1A=="1X"){L b;1R{b=k.5B("70-5H")}1W(e){}7(s.5I&&b)E.4q[s.1f]=b;7(!f)1X()}N E.5t(s,k,1A);1z();7(s.3l)k=W}};7(s.3l){L d=54(c,13);7(s.2U>0)3z(J(){7(k){k.9m();7(!m)c("2U")}},s.2U)}1R{k.9l(s.Q)}1W(e){E.5t(s,k,W,e)}7(!s.3l)c();J 1X(){7(s.1X)s.1X(Q,1A);7(s.2g)E.16.1U("5T",[k,s])}J 1z(){7(s.1z)s.1z(k,1A);7(s.2g)E.16.1U("5U",[k,s]);7(s.2g&&!--E.5M)E.16.1U("5V")}K k},5t:J(s,a,b,e){7(s.3e)s.3e(a,b,e);7(s.2g)E.16.1U("69",[a,s,e])},5M:0,73:J(r){1R{K!r.1A&&9k.9j=="5b:"||(r.1A>=6Y&&r.1A<9h)||r.1A==6X||r.1A==9e||E.14.26&&r.1A==10}1W(e){}K S},72:J(a,c){1R{L b=a.5B("70-5H");K a.1A==6X||b==E.4q[c]||E.14.26&&a.1A==10}1W(e){}K S},71:J(r,b){L c=r.5B("9d-U");L d=b=="3L"||!b&&c&&c.1g("3L")>=0;L a=d?r.9c:r.4e;7(d&&a.1I.28=="5C")6Z"5C";7(b=="1o")E.5l(a);7(b=="2O")a=4A("("+a+")");K a},3v:J(a){L s=[];7(a.1n==1N||a.5j)E.V(a,J(){s.1h(3s(6.37)+"="+3s(6.1C))});N P(L j 1r a)7(a[j]&&a[j].1n==1N)E.V(a[j],J(){s.1h(3s(j)+"="+3s(6))});N s.1h(3s(j)+"="+3s(a[j]));K s.6g("&").1p(/%20/g,"+")}});E.1i.1s({1J:J(c,b){K c?6.27({1P:"1J",29:"1J",1y:"1J"},c,b):6.1F(":23").V(J(){6.Y.18=6.5x||"";7(E.1m(6,"18")=="2D"){L a=E("<"+6.28+" />").6B("1k");6.Y.18=a.1m("18");7(6.Y.18=="2D")6.Y.18="3u";a.1Y()}}).3h()},1H:J(b,a){K b?6.27({1P:"1H",29:"1H",1y:"1H"},b,a):6.1F(":4b").V(J(){6.5x=6.5x||E.1m(6,"18");6.Y.18="2D"}).3h()},6U:E.1i.2h,2h:J(a,b){K E.1q(a)&&E.1q(b)?6.6U(a,b):a?6.27({1P:"2h",29:"2h",1y:"2h"},a,b):6.V(J(){E(6)[E(6).3K(":23")?"1J":"1H"]()})},98:J(b,a){K 6.27({1P:"1J"},b,a)},97:J(b,a){K 6.27({1P:"1H"},b,a)},96:J(b,a){K 6.27({1P:"2h"},b,a)},95:J(b,a){K 6.27({1y:"1J"},b,a)},94:J(b,a){K 6.27({1y:"1H"},b,a)},9f:J(c,a,b){K 6.27({1y:a},c,b)},27:J(l,k,j,h){L i=E.6V(k,j,h);K 6[i.2S===S?"V":"2S"](J(){7(6.15!=1)K S;L g=E.1s({},i);L f=E(6).3K(":23"),4y=6;P(L p 1r l){7(l[p]=="1H"&&f||l[p]=="1J"&&!f)K E.1q(g.1z)&&g.1z.1j(6);7(p=="1P"||p=="29"){g.18=E.1m(6,"18");g.36=6.Y.36}}7(g.36!=W)6.Y.36="23";g.40=E.1s({},l);E.V(l,J(c,a){L e=1D E.2v(4y,g,c);7(/2h|1J|1H/.17(a))e[a=="2h"?f?"1J":"1H":a](l);N{L b=a.3D().1E(/^([+-]=)?([\\d+-.]+)(.*)/),24=e.2o(R)||0;7(b){L d=2M(b[2]),2C=b[3]||"2P";7(2C!="2P"){4y.Y[c]=(d||1)+2C;24=((d||1)/e.2o(R))*24;4y.Y[c]=24+2C}7(b[1])d=((b[1]=="-="?-1:1)*d)+24;e.3Z(24,d,2C)}N e.3Z(24,a,"")}});K R})},2S:J(a,b){7(E.1q(a)||(a&&a.1n==1N)){b=a;a="2v"}7(!a||(1v a=="25"&&!b))K A(6[0],a);K 6.V(J(){7(b.1n==1N)A(6,a,b);N{A(6,a).1h(b);7(A(6,a).M==1)b.1j(6)}})},8Z:J(b,c){L a=E.3I;7(b)6.2S([]);6.V(J(){P(L i=a.M-1;i>=0;i--)7(a[i].O==6){7(c)a[i](R);a.6R(i,1)}});7(!c)6.5z();K 6}});L A=J(b,c,a){7(!b)K 10;c=c||"2v";L q=E.Q(b,c+"2S");7(!q||a)q=E.Q(b,c+"2S",a?E.2H(a):[]);K q};E.1i.5z=J(a){a=a||"2v";K 6.V(J(){L q=A(6,a);q.4k();7(q.M)q[0].1j(6)})};E.1s({6V:J(b,a,c){L d=b&&b.1n==8Y?b:{1z:c||!c&&a||E.1q(b)&&b,2t:b,3Y:c&&a||a&&a.1n!=8W&&a};d.2t=(d.2t&&d.2t.1n==53?d.2t:{9w:8U,8T:6Y}[d.2t])||8S;d.5o=d.1z;d.1z=J(){7(d.2S!==S)E(6).5z();7(E.1q(d.5o))d.5o.1j(6)};K d},3Y:{6O:J(p,n,b,a){K b+a*p},5F:J(p,n,b,a){K((-1Z.9C(p*1Z.9D)/2)+0.5)*a+b}},3I:[],3T:W,2v:J(b,c,a){6.11=c;6.O=b;6.1l=a;7(!c.3U)c.3U={}}});E.2v.2m={4C:J(){7(6.11.33)6.11.33.1j(6.O,[6.2I,6]);(E.2v.33[6.1l]||E.2v.33.4o)(6);7(6.1l=="1P"||6.1l=="29")6.O.Y.18="3u"},2o:J(a){7(6.O[6.1l]!=W&&6.O.Y[6.1l]==W)K 6.O[6.1l];L r=2M(E.1m(6.O,6.1l,a));K r&&r>-8N?r:2M(E.2q(6.O,6.1l))||0},3Z:J(c,b,d){6.5s=(1D 3O()).3N();6.24=c;6.3h=b;6.2C=d||6.2C||"2P";6.2I=6.24;6.4E=6.4F=0;6.4C();L e=6;J t(a){K e.33(a)}t.O=6.O;E.3I.1h(t);7(E.3T==W){E.3T=54(J(){L a=E.3I;P(L i=0;i<a.M;i++)7(!a[i]())a.6R(i--,1);7(!a.M){74(E.3T);E.3T=W}},13)}},1J:J(){6.11.3U[6.1l]=E.1K(6.O.Y,6.1l);6.11.1J=R;6.3Z(0,6.2o());7(6.1l=="29"||6.1l=="1P")6.O.Y[6.1l]="8L";E(6.O).1J()},1H:J(){6.11.3U[6.1l]=E.1K(6.O.Y,6.1l);6.11.1H=R;6.3Z(6.2o(),0)},33:J(a){L t=(1D 3O()).3N();7(a||t>6.11.2t+6.5s){6.2I=6.3h;6.4E=6.4F=1;6.4C();6.11.40[6.1l]=R;L b=R;P(L i 1r 6.11.40)7(6.11.40[i]!==R)b=S;7(b){7(6.11.18!=W){6.O.Y.36=6.11.36;6.O.Y.18=6.11.18;7(E.1m(6.O,"18")=="2D")6.O.Y.18="3u"}7(6.11.1H)6.O.Y.18="2D";7(6.11.1H||6.11.1J)P(L p 1r 6.11.40)E.1K(6.O.Y,p,6.11.3U[p])}7(b&&E.1q(6.11.1z))6.11.1z.1j(6.O);K S}N{L n=t-6.5s;6.4F=n/6.11.2t;6.4E=E.3Y[6.11.3Y||(E.3Y.5F?"5F":"6O")](6.4F,n,0,1,6.11.2t);6.2I=6.24+((6.3h-6.24)*6.4E);6.4C()}K R}};E.2v.33={2i:J(a){a.O.2i=a.2I},2x:J(a){a.O.2x=a.2I},1y:J(a){E.1K(a.O.Y,"1y",a.2I)},4o:J(a){a.O.Y[a.1l]=a.2I+a.2C}};E.1i.5f=J(){L b=0,3b=0,O=6[0],5q;7(O)8K(E.14){L d=O.1b,45=O,1M=O.1M,1L=O.2u,5p=26&&4t(5n)<8H,2Z=E.1m(O,"3C")=="2Z";7(O.7b){L c=O.7b();1c(c.2c+1Z.2b(1L.1I.2i,1L.1k.2i),c.3b+1Z.2b(1L.1I.2x,1L.1k.2x));1c(-1L.1I.68,-1L.1I.67)}N{1c(O.5k,O.5K);2e(1M){1c(1M.5k,1M.5K);7(3X&&!/^t(8F|d|h)/i.17(1M.28)||26&&!5p)3a(1M);7(!2Z&&E.1m(1M,"3C")=="2Z")2Z=R;45=/^1k/i.17(1M.28)?45:1M;1M=1M.1M}2e(d&&d.28&&!/^1k|3q/i.17(d.28)){7(!/^a0|1V.*/i.17(E.1m(d,"18")))1c(-d.2i,-d.2x);7(3X&&E.1m(d,"36")!="4b")3a(d);d=d.1b}7((5p&&(2Z||E.1m(45,"3C")=="4Z"))||(3X&&E.1m(45,"3C")!="4Z"))1c(-1L.1k.5k,-1L.1k.5K);7(2Z)1c(1Z.2b(1L.1I.2i,1L.1k.2i),1Z.2b(1L.1I.2x,1L.1k.2x))}5q={3b:3b,2c:b}}J 3a(a){1c(E.2q(a,"a1",R),E.2q(a,"8D",R))}J 1c(l,t){b+=4t(l)||0;3b+=4t(t)||0}K 5q}})();',62,624,'||||||this|if||||||||||||||||||||||||||||||||||||||function|return|var|length|else|elem|for|data|true|false|document|type|each|null||style||undefined|options|nodeName||browser|nodeType|event|test|display|jQuery|arguments|parentNode|add|msie|window|url|indexOf|push|fn|apply|body|prop|css|constructor|script|replace|isFunction|in|extend|text|div|typeof|className|handle|opacity|complete|status|firstChild|value|new|match|filter|dataType|hide|documentElement|show|attr|doc|offsetParent|Array|call|height|cache|try|tbody|break|trigger|table|catch|success|remove|Math||ready|get|hidden|start|string|safari|animate|tagName|width|map|max|left|split|while|ret|global|toggle|scrollLeft|done|handler|special|prototype||cur|selected|curCSS|find|id|duration|ownerDocument|fx|toLowerCase|scrollTop|select|bind|guid|opera|unit|none|pushStack|toUpperCase|button|makeArray|now|nextSibling|target|stack|parseFloat|events|json|px|isReady|createElement|queue|delete|timeout|slice|disabled|removeChild|jsre|fixed|one|nth|preventDefault|step|merge|inArray|overflow|name|innerHTML|exec|border|top|readyState|multiFilter|error|trim|rl|end|form|first|appendChild|async|elems|insertBefore|checked|childNodes|html|which|encodeURIComponent|append|block|param|readyList|grep|color|setTimeout|runtimeStyle|args|position|toString|has|addEventListener|callee|removeData|timers|password|is|xml|last|getTime|Date|domManip|ajax|src|props|timerId|orig|getElementsByTagName|isXMLDoc|mozilla|easing|custom|curAnim|stopPropagation|unbind|load|selectedIndex|offsetChild|mouseleave|mouseenter|input|application|defaultView|visible|float|String|responseText|charCode|teardown|on|setup|currentStyle|shift|javascript|child|accepts|_default|nodeIndex|lastModified|RegExp|_|parseInt|jsonp|previousSibling|dir|tr|self|getAttribute|eval|empty|update|object|pos|state|setRequestHeader|ajaxSettings|not|GET|getPropertyValue|getComputedStyle|styleSheets|lastToggle|unload|mouseout|mouseover|andSelf|getWH|container2|unshift|fromElement|relatedTarget|visibility|init|absolute|click|fix|triggered|Number|setInterval|removeAttribute|prevObject|unique|classFilter|submit|after|file|clean|expr|windowData|offset|scroll|client|deep|jquery|offsetLeft|globalEval|sibling|version|old|safari2|results|wrapAll|startTime|handleError|container|createTextNode|radio|oldblock|checkbox|dequeue|bindReady|getResponseHeader|parsererror|lastChild|index|swing|00|Modified|ifModified|clone|offsetTop|values|active|getElementById|link|val|col|contentType|ajaxSend|ajaxSuccess|ajaxComplete|ajaxStop|ajaxStart|serializeArray|notmodified|POST|loaded|DOMContentLoaded|Width|triggerHandler|ctrlKey|metaKey|keyCode|clientTop|clientLeft|ajaxError|clientX|pageX|cloneNode|detachEvent|swap|removeEventListener|join|attachEvent|substr|parse|head|textarea|reset|image|before|odd|zoom|even|prepend|username|quickClass|quickID|quickChild|processData|uuid|continue|textContent|appendTo|contents|evalScript|parent|defaultValue|setArray|CSS1Compat|compatMode|cssFloat|styleFloat|webkit|nodeValue|eq|linear|replaceWith|concat|splice|100|href|_toggle|speed|alpha|304|200|throw|Last|httpData|httpNotModified|httpSuccess|clearInterval|beforeSend|colgroup|fieldset|multiple|XMLHttpRequest|ActiveXObject|getBoundingClientRect|scriptCharset|callback|plain|img|hasClass|br|urlencoded|www|abbr|pixelLeft|post|getJSON|getScript|elements|serialize|keypress|keydown|change|mouseup|mousedown|dblclick|resize|focus|blur|stylesheet|rel|mousemove|doScroll|round|hover|keyup|padding|offsetHeight|offsetWidth|Bottom|Top|Right|clientY|pageY|Left|toElement|srcElement|cancelBubble|returnValue|0n|substring|animated|header|enabled|ajaxSetup|innerText|noConflict|size|contains|only|line|gt|weight|lt|font|uFFFF|u0128|417|inner|Height|Boolean|toggleClass|removeClass|addClass|removeAttr|replaceAll|insertAfter|wrap|prependTo|contentWindow|contentDocument|iframe|children|siblings|wrapInner|prevAll|nextAll|prev|next|parents|maxLength|maxlength|readOnly|readonly|borderTopWidth|class|able|htmlFor|522|reverse|boxModel|with|1px|compatible|10000|ie|ra|it|rv|400|fast|600|userAgent|Function|navigator|Object|stop|option|array|ig|NaN|fadeOut|fadeIn|slideToggle|slideUp|slideDown|setAttribute|changed|be|responseXML|content|1223|fadeTo|can|300|property|protocol|location|send|abort|getAttributeNode|specified|method|action|cssText|attributes|Accept|With|th|slow|Requested|td|GMT|cap|1970|cos|PI|Jan|colg|01|Thu|tfoot|Since|thead|If|Type|Content|leg|open|XMLHTTP|opt|Microsoft|embed|onreadystatechange|onload|area|charset|hr|http|inline|borderLeftWidth|1_|meta'.split('|'),0,{}))

var arr_topic = [
[
'中国省会:是:(_____)。',
[
['南京','no'],
['北京','yes'],
['长安','no'],
['洛阳','no']
]],
[
'花城:是:(_____)。',
[
['昆明','no'],
['广州','yes'],
['武汉','no'],
['哈尔宾','no']
]],

['2020年日本科技实力全球排名前五。',
[
['正确','yes'],
['错误','no']
]]

];

//元素的隐藏与呈现
function showHide(obj){
var obj=document.getElementById(obj);
obj.style.display=obj.style.display=="block"?"none":"block";
}
//初始化事件绑定
function int(){
("#status").html(""); ('.div_topic').removeClass('odd');
('.div_topic:even').addClass('odd'); ('.div_topic p').click(function(){
if((this).find('input[type="radio"]').val() != undefined){ (this).find('input[type="radio"]').attr('checked','checked');
var name = (this).find('input[type="radio"]').attr('name');//获得名称 ('input[type="radio"][name="' + name + '"]').parents("p").removeClass('selected');
('input[type="radio"][name="' + name + '"]:checked').parents("p").addClass('selected'); } }); ("#check_answer").click(function(){
("#status").html(""); ("h4").css("color","#22536A");
var answer_this_tip,set_answer,_temp_tip;
_temp_tip = "yes";
var tall = 0;
(".div_topic").each(function(i){ if((this).find('input[type="radio"]:checked').val() == undefined){
_temp_tip = "no";
(this).find("h4").css("color","green"); } tall++; }); if(_temp_tip == "no"){ ("#status").html("还有题目没完成!");
return;
}
var err = 0;
(".div_topic").each(function(i){ answer_this_tip = (this).find(".answer_this_tip").html();
set_answer = (this).find('input[type="radio"]:checked').val(); //(this).find('input[type="radio"]').val() != undefined
if(answer_this_tip != set_answer){
(this).find("h4").css("color","red"); err++; } }); ("h4 span").show();
("#status").html("错误:" + err + "个!错误率:" + (err / tall * 100).toFixed(2) + "%"); }); } function set_str_len(str,len){ str = str + ""; var temp = ""; for(i = 0; i < (len - str.length); i++){ temp += "0"; } return temp + str; } //随即返回一个范围的随机数 function Grandom(m,n){ return Math.round(Math.random() * (n - m) + m); } function get_topic(_arr_topic,_topic_num){ var _all_num, //总题目数 _this_num, //总题随即一个数 _topic, //题目 _topic_temp, //临时题目 _topic_str, //题目字符串 i, //当前题目数 _arr_answer,  //答案数组 _arr_answer_length, //答案长度 _this_answer_num, //答案随即一个数 _temp_answer,  //临时答案 _arr_answer_last, //最终答案数列 _answer_last_str,//最终答案字符串 _answer_str, //答案字符串 _answer_tip, //答案前头的ABCD _answer_this_tip,//当前题目的答案 _arr_answer_type; _all_num = _arr_topic.length;  //总题目数 _arr_answer_last = new Array();//最终答案数列 _topic_str = ""; for(y = 0; y < _topic_num; y++){ i = y + 1; //当前题目数 _this_num = Grandom(0,(_arr_topic.length - 1)); //随即一个数 _topic_temp = _arr_topic.splice(_this_num,1); _topic = _topic_temp[0][0]; //题目 _arr_answer = _topic_temp[0][1];//答案数组 _arr_answer_length = _arr_answer.length; //答案长度 //获得答案字符串 begin  _answer_str  _answer_this_tip _answer_str = ""; if(_arr_answer_length == 4){ for(j = 0; j < 4; j++){ try{ _this_answer_num = Grandom(0,(_arr_answer.length - 1)); _temp_answer = _arr_answer.splice(_this_answer_num,1); switch (j) { case 0 : _answer_tip = "A";break; case 1 : _answer_tip = "B";break; case 2 : _answer_tip = "C";break; case 3 : _answer_tip = "D";break; } if(_temp_answer[0][1] == "yes"){ _answer_this_tip = _answer_tip; } _answer_str += '' + _answer_tip + '、' + _temp_answer[0][0] + '

'; }catch(e){ alert(e); } } }else{ try{ if(_arr_answer[0][1] == "yes"){_answer_this_tip = "A";} if(_arr_answer[1][1] == "yes"){_answer_this_tip = "B";} _answer_str = '

A、' + _arr_answer[0][0] + '

B、' + _arr_answer[1][0] + '

'; }catch(e){ alert(e); } } _arr_answer_last.push(_answer_this_tip); //获得答案字符串 end //组合题目字符串 begin _topic_str = '

' + i + '、' + _topic + '答案:' + _answer_this_tip + '

' + _answer_str + '
';
(_topic_str).appendTo("#topic_all"); //组合题目字符串 end } //组合last答案字符串 begin _arr_answer_last _answer_last_str var _answer_last_str = ""; var tap10 = 1; var _answer_all_num = _arr_answer_last.length; for(z = 0; z < _answer_all_num; z++){ y = z + 1; if(y == 1){ if(_answer_all_num == 1){ _answer_last_str = '<p>001' _answer_last_str += ' <b>' }else{ _answer_last_str = '<p>001-'; if(_answer_all_num <= 10){ _answer_last_str += set_str_len(_answer_all_num,3) + ' <b>'; }else{ _answer_last_str += '010 <b>'; } } } if(y%10 == 0){ if(_answer_all_num > 10 && y < _answer_all_num){ _answer_last_str += '</b></p>\n'; _answer_last_str += '<p>' + set_str_len((tap10 * 10 + 1),3); if((tap10 * 10 + 1) == _answer_all_num){ _answer_last_str += ' <b>' }else if(_answer_all_num < ((tap10 + 1) * 10)){ _answer_last_str += '-' + set_str_len(_answer_all_num,3) + ' <b>'; }else{ _answer_last_str += '-' + set_str_len(((tap10 + 1) * 10),3) + ' <b>'; tap10++; } } }else if(y%5 == 0){ _answer_last_str += _arr_answer_last[z] + " "; }else{ _answer_last_str += _arr_answer_last[z]; } } _answer_last_str += '</b></p>\n'; ("#answer_last").html(""); ("#answer_last").hide(); //组合last答案字符串 end ("#answer_last").html(_answer_last_str); } (document).ready(function(){ ("#topic_all_num").html(arr_topic.length); ("#get_topic").click(function(){ ("#status").html(""); if(("#topic_all").html() != ""){ if(!confirm("是否要重新出题?")){ return; }else{ ("#topic_all").html(""); } } var topic_num = ("#topic_num").val(); if(isNaN(topic_num)){ ("#topic_num").val("100"); }else{ if(topic_num <= 0){ ("#topic_num").val("100"); } if(topic_num > arr_topic.length){ ("#topic_num").val(arr_topic.length); } } topic_num = ("#topic_num").val(); get_topic(arr_topic,topic_num); int(); }); }); </script> </head> <body> <div id="fixedLayer"><div> <p><span id="topic_right"> <input name="show_answer" id="show_answer" type="button" value=" 答案 " οnclick='$("#answer_last").toggle();'/> <input name="check_answer" id="check_answer" type="button" value=" 算分 "/></span> 您的题库一共有<span id="topic_all_num"></span>道题,您要做其中<input name="topic_num" type="text" id="topic_num" value="100" />道。<input name="get_topic" id="get_topic" type="button" value=" 出 题 "/> <span id="status"></span></p> </div> </div> <br/> <br/> <br/> <div id="topic_all"></div> <div id="answer_last"></div> </body> </html><br /><center>如不能显示效果,请按Ctrl+F5刷新本页,更多网页代码:<a href='http://www.veryhuo.com/' target='_blank' rel="noopener">http://www.veryhuo.com/</a></center>

3、第三种,由2改造而来,全是多选题

A、改造方案

1、将文中radio都替换成功checkbox,除了一大段js库中的关键字例外。

2、将初始化函数名改为init()

3、打开网页后,默认要考100道题目,实际只有25道,逻辑混乱

修改:
A、在 (document).ready()函数开头加一行 ("#topic_num").val(arr_topic.length)
B、将 var topic_num=$("#topic_num").val()及其后一段都删除

4、多选题构造是,记录标准答案

修改:
  原来是单选题,现在是多选
  if(_temp_answer[0][1]) == "yes"){
    _anser_this_tip = _answer_tip;
  }
==>
   if(_temp_answer[0][1] == "yes"){
     if (_answer_this_tip == undefined){
       _answer_this_tip = _answer_tip;
     } else {
       _answer_this_tip += '.' + _answer_tip;
     }
   }
   同时在中此函数开头,加初始化语句
    _answer_this_tip = undefined;

5、多选题提交试卷时,统计分数,需要修改。

修改:
   原来获取用户答案的语句  set_answer=(this).find('input[type="checkbox"]:checked').val()   现在要改成多个答案    set_answer = undefined;    (this).find('input[type="checkbox"]:checked').each(function(){
  if (set_answer == undefined){
    set_answer = (this).val();   } else {     set_answer += '.' + (this).val();
  }
})

6、原来只支持4个答案,现在要支持任意个数

修改:
  if(_arr_answer_length==4){
    for (j=0;j<4; j++){
      try{
        xxx
        switch(j){
            case 0:
            case 1:
            case 2:
            case 3:
        }
    }
 }
}
这一段改为
   两层循环变成一层,不用写死4,内部答案可以最多7个,到'H':
   for (j=0;j<_arr_answer_length; j++){}
同事,将少于4个选项的异常 else 注释掉

7、将多选改为支持任意数量选项的选择题

switch 语句用来选择当前选项,太麻烦,改用数组
在函数 function get_topic(_arr_topic,_topic_num) 中增加数组定义
_answer_this_tip,//当前题目的答案
_topic_array = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                 'H', 'I', 'J', 'K', 'L', 'M', 'N',
                 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
                 'V', 'W', 'X', 'Y', 'Z' ],
然后将 switch语句中的赋值注掉,改成如下语句:
_answer_tip =  _topic_array[j];

8、因为本套试题,每次出题的题目顺序都是随机的,而且每个题目的答案也随机,将其改为固定顺序

A、修改出题顺序为固定
在函数 function get_topic()中,找到  _arr_topic.splice(_this_num,1);
  将_this_num相关的三行都去掉,换成_topic_temp = _arr_topic.splice(0,1);
  原来是从题目中随机抽一个,现在都是从第一个开始抽取,抽取完删除原数组内容
B、修改一个题目答案顺序为固定
  搜索  _arr_answer.splice(_this_answer_num,1);
  将相关的两行注释掉:
      //_this_answer_num = Grandom(0,(_arr_answer.length - 1));
      //_temp_answer = _arr_answer.splice(_this_answer_num,1);
  替换成:
      _temp_answer = _arr_answer.splice(0,1);
   表示从第一个选项开始显示

9、无法更改答案的问题

B、整改完成后,得到几个变异版本,点击链接后,查看源码
i 、随机顺序单选考题
ii 、随机顺序多选考题
iii、固定顺序单选考题
iv 、固定顺序多选考题

jenkins

[TOC]

一、安装

1、采用系统命令

如对于 centos:
    sudo yum -y install jenkins
其它系统各有自带命令,如 wget install ...

2、采用jar包启动

(nohup java -jar jenkins.jar --httpPort=8080 &)

二、插件安装

1、插件下载好地方一

如果没有插件,则无法充分发挥 jenkins 的威力。
但是公司电脑设置代理又不方便,只能自己逐个离线下载插件,再手工安装
逐个下载太累,更累的是资源不好找,最终找到一个插件聚集地:
http://updates.jenkins-ci.org/download/plugins/

2、插件下载好地方2

https://plugins.jenkins.io/

三、让 jenkins 输出彩色字符串

1、安装

jenkins 输出黑白界面,找关键字实在太累。
于是今天就折腾了一下,花了好久才搞定。
其实就是装一个插件 AnsiColor 及其依赖包。

2、配置

https://blog.csdn.net/wc1695040842/article/details/104972146/

需要重新配置工程

3、示例代码

#!/bin/bash
#定义颜色变量
BLACK='\e[1;30m'
RED='\e[1;31m'
GREEN='\e[1;32m'
YELLOW='\e[1;33m'
BLUE='\e[1;34m'
PURPLE='\e[1;35m'
PINK='\e[1;36m'
WHITE='\e[1;37m'
END='\e[0m'

#需要使用echo -e
echo -e  "${BLACK}##########黑色##########${END}"
echo -e  "${RED}##########红色##########${END}"
echo -e  "${GREEN}##########绿色##########${END}"
echo -e  "${YELLOW}##########黄色##########${END}"
echo -e  "${BLUE}##########蓝色##########${END}"
echo -e  "${PURPLE}##########紫色##########${END}"
echo -e  "${PINK}##########粉色##########${END}"
echo -e  "${WHITE}##########白色##########${END}"
echo "--------------------------------------------"

#写成函数,直接调用
SETCOLOR_SUCCESS() { echo

四、jenkins对错误的处理

问题:当jenkins调用的脚本失败时,jenkins会立即退出,并报错。
当某条语句执行返回失败是,同样立即退出,如执行语句: grep 过滤为空是,立即退出

解决办法,调用本地脚本,脚本始终返回0,表示成功,真正的成功失败用字符串返回。
如:monitor_server.tcl:
package require Tcl
package require Expect

if {} {
   puts "ret_succ"
   exit 0
} else {
   puts "ret_error"
   exit 0
}

jenkins中,脚本这样写:
result=`tclsh monitor_server.tcl`
keyFlag=`(echo ${result} | grep -o ret_succ) || (echo ${result} | grep -o ret_error)`
if [ "keyFlag" == "ret_succ" ];then
    SETCOLOR_SUCCESS success message!
    ret=0
else
    SETCOLOR_SUCCESS success message!
    ret=1
fi
exit ${ret}

五、看板

1、walldisplay

费了好多力气,最后虽然安装成功,但是点击后,全屏显示空白

2、dashboard

由于不能一键安装,逐个下载挺辛苦。但最后还是安装好了。
使用很漂亮,完全可以取代walldisplay。它的定制选项很多。
默认的显示风格和 my views 一样,但下面还有 grid 配置。
我将上面的列表取消,完全采用表格(grid)显示,这不就是walldisplay!

六、jenkins 定时触发,定时规则语法

字段              *       *       *       *       *
含义              分钟  小时  日期  月份  星期
取值范围            0-59    0-23    1-31    1-12    0-7

示例
每隔15分钟执行一次  H/15    *   *   *   *
每隔2个小时执行一次  H   H/2 *   *   *
每隔3天执行一次        H   H   H/3 *   *
每隔3天执行一次(每月的1-15号)H     H   1-15/3  *   *
每周1,3,5执行一次 H   H   *   *   1,3,5

规则
指定时间范围  a-b
指定时间间隔  /
指定变量取值  a,b,c

定时触发
(一、Jenkins 定时任务触发时间表达式 )
Jenkins采用了著名的UNIX任务调度工具CRON所使用的配置方式。用5个字段代表5个不同的时间单位(中间用空格隔开):

分 时 日 月 星期几

0 2 * * *    表示每天凌晨2点
*/10 * * * *   每隔10分钟一次
45 10 * * 1-5    每星期的星期一到星期五的10:45分执行
· M-N 指定范围值
· M-N/X 或 */X 在指定范围或整个有效范围内按 X 间隔的步骤
· A,B,...,Z 列举了多个值

    每天凌晨2:00跑一次 
    H 2  * * *
    每隔5分钟构建一次
    H/5 * * * *
    每两小时构建一次
    H H/2 * * *

    每天中午12点定时构建一次
    H 12 * * *   或0 12 * * *(0这种写法也被H替代了)

    每天下午18点前定时构建一次
    H 18 * * *

    每15分钟构建一次
    H/15 * * * *   或*/5 * * * *(这种方式已经被第一种替代了,jenkins也不推荐这种写法了)

    周六到周日,18点-23点,三小时构建一次
    H 18-23/3 * * 6-7
    ---------------------

(二、Cron表达式 )

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year或 
Seconds Minutes Hours DayofMonth Month DayofWeek

每一个域可出现的字符如下:
Seconds:可出现", - * /"四个字符,有效范围为0-59的整数
Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
Hours:可出现", - * /"四个字符,有效范围为0-23的整数
DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year:可出现", - * /"四个字符,有效范围为1970-2099年

每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)*:表示匹配该域的任意值,假如在Minutes域使用*, 即表示每分钟都会触发事件。

(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.

(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份

(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

举几个例子:
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? * 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作


一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
按顺序依次为
秒(0~59) 
分钟(0~59) 
小时(0~23) 
天(月)(0~31,但是你需要考虑你月的天数) 
月(0~11) 
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT) 
年份(1970-2099)

其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?

0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 
0 0 12 ? * WED 表示每个星期三中午12点 
"0 0 12 * * ?" 每天中午12点触发 
"0 15 10 ? * *" 每天上午10:15触发 
"0 15 10 * * ?" 每天上午10:15触发 
"0 15 10 * * ? *" 每天上午10:15触发 
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发 
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 
"0 15 10 15 * ?" 每月15日上午10:15触发 
"0 15 10 L * ?" 每月最后一日的上午10:15触发 
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

有些子表达式能包含一些范围或列表

例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

“*”字符代表所有可能的值

因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天


“/”字符用来指定数值的增量 
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟 
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样


“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值 
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”

“L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写 
但是它在两个子表达式里的含义是不同的。 
在天(月)子表达式中,“L”表示一个月的最后一天 
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT

如果在“L”前有具体的内容,它就具有其他的含义了

例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五 
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题

字段 允许值 允许的特殊字符 
秒 0-59 , - * / 
分 0-59 , - * / 
小时 0-23 , - * / 
日期 1-31 , - * ? / L W C 
月份 1-12 或者 JAN-DEC , - * / 
星期 1-7 或者 SUN-SAT , - * ? / L C # 
年(可选) 留空, 1970-2099 , - * /

\e[1;32m'"$@"

四、jenkins对错误的处理

五、看板

1、walldisplay

2、dashboard