2015年8月26日 星期三

transmission too many open file problem on synology is solved

試過網路上許多方法 ulimit、/etc/security/limit.conf、fs.file-max、sysctl 等許多指令都無效

查看 /proc/$PID/limits  open file 限制一樣都是  1024 4096

後來終於發現Linux library 的函數 rlimit 有支援動態更改process open file 的方法

參照網址是:

http://xiezhenye.com/2013/02/%E5%8A%A8%E6%80%81%E4%BF%AE%E6%94%B9%E8%BF%90%E8%A1%8C%E4%B8%AD%E8%BF%9B%E7%A8%8B%E7%9A%84-rlimit.html

由他提供的C程序做些微修改,以下是我的C 程序

#############################################################################
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>

int main(int argc, char** argv) {
    pid_t pid;
    struct rlimit new_limit;
    int result;
    if (argc < 2) {
        return 1;
    }
    pid = atoi(argv[1]);
    new_limit.rlim_cur = 8192;
    new_limit.rlim_max = 65535;
    result = prlimit(pid, RLIMIT_NOFILE, &new_limit, NULL);
    return result;
    }
#############################################################################

基本上 limit.rlim_cur  <  limit.rlim_max 數字即可 ,不夠大可自行斟酌修改 ;

利用GCC編譯完執行指令即可順利更改open file 限制 ,不用在擔心too many open file 問題
# rlimit  $PID

查看一下 /proc/$PID/limits 內容已修改成功。


註:要在Synology 上編譯需開啟 ssh 功能安裝 ipkg 跟 gcc 套件

ipkg安裝參考網址:
http://blog.allenchou.cc/dsm-bootstrap-ipkg/

Update -- 20170910

可能是DSM更新的關係,舊有的ipkg 套件安裝的gcc 一直無法編譯成功
目前可直接改用 synologc community 提供的 debian chroot 安裝 gcc
安裝完gcc之後,直接編譯檔案即可成功。

debian chroot 的預設環境是 /volume1/@appstore/debian-chroot/var/chroottarget
編譯完可直接執行或拉到其他目錄下使用。


2015年2月5日 星期四

消滅C++編譯警告(Warning)

當編譯程序發現程序中某個地方有疑問,可能有問題時就會给出一個警告信息。警告信息可能意味着程序中隱含的大錯誤,也可能確實沒有問題。對於警告的正確處理方式應該是:盡可能地消除之。對於編譯程序给出的每個警告都應該仔細分析,看看是否真的有問題。只有那些確實無問題的警告才能放下不管。
說明:
由於編譯的警告各種各样,根本不可以一一罗列出來,下面只是列舉出比較典型的一些警告,還有一些警告,大家只要根據字面意思,就可以很快的查找出來,並解决之。

類型1:

顯示:warning: implicit declaration of function `Example()'。
警告原因:(1)在你的.c文件中調用了函數Example(),可是你並沒有把聲明這個函數的相應的.h文件包含進來。
         (2)有可能你在一個.c文件中定義了這個函數體,但並沒有在.h中進行聲明。
解决方法:(1)你可以在調用這種函數的.c文件的一開始處加上:extern Example();
         (2)你可以在調用這種函數的.c文件中包含進聲明了函數Example()的頭文件。
         (3)如果你在一個.c文件中定義了這個函數體,但並沒有在.h中進行聲明,不嫌麻煩的話,你也可以去生成一個.h文件,加上你的函數聲明。
類似的警告:warning: type mismatch with previous implicit declaration
warning: type mismatch with previous implicit declaration
warning: previous implicit declaration of `Example()'

 

類型2:

   顯示:warning: unused variable `param’。
   警告原因:很明顯,是您定義了變量‘param’,卻根本沒有使用它。
   解决方法:不需要用的話,就刪了它吧。

類型3:

   顯示:warning: statement with no effect。
   警告原因:可能的情況是,在你的文件中,你這麼幹--#define MACROPRINT
             然後在某一處又定義了--#define MACROPRINT printf。然後你在各處引用
MACROPRINT(“HELLO”),這样不會出錯,但是發生了警告“這個聲明是沒有用的”。
   解决方法:把#define MACROPRINT刪掉。

類型4:

   顯示:warning: int format, long int arg (arg 3)
   警告原因: 象這样printf("%s%d, szDebugString, ulGwId);你的ulGwId是一個unsigned long 型的,而你为它選擇的輸出形式卻是 “%d”(這個格式是为整數型服務的-int)。
   解决方法: 這样的錯誤你只要做到参數類型一致就可以了,象上面的現象,你只要把“%d”改成“%ld”就可以了。
   類似警告:warning:comparison between pointer and integer
          

類型5:

   顯示:warning: comparison is always 0 due to limited range of data type
   警告原因:有可能你定義了unsigned int uParam;但是你去做了if(uparam<0)的判斷,
因为unsigned int 型的數據總是>=0的,因此這样的比較由於數據類型限制了它的範圍,因此也就给出了警告。
 解决方法:可以去掉這样的判斷。

類型6:

   顯示:warning: control reaches end of non-void function
   警告原因: 出現這样的警告,有可能是你寫了一個
unsigned long FuncA()
{
if()
{return ulValue;}
if()
{ return ulValue;}
}的函數,可能在兩個if語句中,你都沒有進入,這時,退出函數之前,你就根本沒有值可以返回。
   解决辦法: 如果一個函數有返回值,確保在任何情況下該函數都有一個返回值。
   類似警告: warning :`return' with no value, in function returning non-void

 

類型7:

   顯示:warning: overflow in implicit constant conversion
   警告原因:變量的變換有可能導致數值的越界。
#define RET_PRODUCTID                    0x10000000
#define ERR_RET_GLOBAL                RET_PRODUCTID+5000
#define RET_USER                         ERR_RET_GLOBAL+5000
#define USER_OK                          RET_USER+0
#define USER_FAIL                         RET_USER+1
如果這样定義,碰到short Func(){return USER_OK},就會警告有出現越界。
   解决辦法:確定好值的範圍。

類型8:

   顯示:warning: `ulParam' might be used uninitialized in this function
   警告原因: 當ulParam做为表達式的右值時,而在此之前,你又沒有對這個参數進行初始化。
              例如:void Func()
                   {
                      ulong ulParam;
                      ulong ulRetCode;
                      if(…)
                      {
                         ulParam = ……..;
                      }
                      if(….)
                      {
                         ulParam = ……;
}
ulRetCode = ulParam;
}
              在這種情況下,當兩個if()都執行不到的時候,ulParam根本沒有被賦值過,這样又去给ulRetCode賦值,就比較危險了。
   解决辦法: 多留個神,細心一點就可以了。

類型9:

   顯示: warning: passing arg 1 of `free' makes pointer from integer without a cast
   警告原因: 你free(a),但a是一個unsigned long,你可能把一個指針的數值放在了a裏面了。
   解决辦法: 在free(a)時,需要強制轉換a为指針類型的即可。即:free((char*)a)。
   類似警告:warning: assignment from incompatible pointer type
warning: initialization from incompatible pointer type
warning:passing arg 2 of `AOS_MemCopy_X' makes pointer from integer without a cast

 

類型10:

   顯示: warning: `MY_DEBUG'  redefined
warning: this is the location of the previous definition
   警告原因: 連續出現這種兩個警告,可能的一種情況是,你在你的.c文件中包含了兩個.h
文件,而這兩個.h文件都對MY_DEBUG進行了聲明。
   解决辦法:只在一個文件中聲明這種東東。

 

類型11:

   顯示:warning: value computed is not used
   警告原因:参與運算的值是沒有作用的。比如你這样幹:
char* p;
*p++;
這样對p根本一點影響也沒有。
   解决方法:請確定究竟要進行什麼運算。

 

類型12:

   顯示:warning: `#ifdef' argument starts with a digit
   警告原因:出現了#ifdef 0這样的錯誤
   解决方法:應該是#if 0 吧

 

類型13:

   顯示:warning: unknown escape sequence `\R'
   警告原因:編譯器不認識‘\R’。
   解决方法:一時筆誤,應該是‘\r’。

 

類型14:

   顯示:warning:too few arguments for format
   警告原因:你有可能這样幹了:printf(“%d%s”,uParam);
   解决方法:把要的留下,不要的去掉。

類型15:

    顯示:warning: ‘Func’ defined but not used
    警告原因:Func 函數你定義了,但是你根本沒有使用它。
解决方法:不要的就去掉。

 

類型16:

顯示:warning: suggest parentheses around && within ||
警告原因:有人這麼用了if(( *p >= 'a' ) && ( *p <= 'z' ) || ( *p >= 'A' ) && ( *p <= 'Z' ) || ( *p >= '0' ) && ( *p <= '9' ))
解决方法:你最好這样if((( *p >= 'a' ) && ( *p <= 'z' )) || (( *p >= 'A' ) && ( *p <= 'Z' )) || (( *p >= '0' ) && ( *p <= '9' )))

2015年1月28日 星期三

Bash 實用技巧。


&  后台运行程序 
()   使用子shell, 比如 (cd ../../commlib/; make) 
$()  命令替换,和 ``的作用是一样的 
<(命令)  把命令的输出到一个临时文件 
<< HereDoc
使用举例:
比如你要在 shell 脚本中 使用 awk 脚本 
awk -f <(cat <<EOF
  /abc/{
   print $0;

EOF 
)
$(())  执行整数计算 $((66/2)) 
if (( 算术运算 )) 
if [[ 字符串运算 ]]

alias 定义命令别名
dot .  或 source 命令, 在当前shell中执行脚本
exec 可以重定向当前shell的文件描述符, 或运行另一个程序。

trap 可以捕获信号
nohup 防止ssh 挂起导致的问题 
screen 可以用来保持 会话,  不受ssh的关闭影响
export 导出变量给子shell使用
tee 可以 把 输出 分流
ENV_VAR=VALUE your_program 这样可以 为这一个程序 修改它环境变量,外部shell的环境变量没有被更改

tac 倒置文件

目录跳转

cd -   快速回到前一个路径
cd  回到用户的home目录
pushd, popd, dirs 实现多目录跳转
pushd 命令用来更改您的当前目录并将其存储在堆栈中。 popd 命令用来从堆栈的顶部移除目录并使您返回该位置。 dirs 命令来显示当前目录堆栈。(dir –v –p)
pushd +n; popd +n 可以操作虚拟目录堆栈

快速跳至常用目录

     你可能已经知道$PATH变量可以列出 bash的“搜索路径”——当在当前目录不能找到请求的文件时,bash会自动搜索的目录。不过,bash也支持$CDPATH变量,当试图改变目录时该变量列出cd命令转向的目录。为了使用这个特性,我们要对$CDPATH变量赋值一个目录列表,如下面的例子所示:
bash> CDPATH='.:~:/usr/local/apache/htdocs:/disk1/backups'
bash> export CDPATH
现在,无论何时使用cd命令,bash将会检查$CDPATH列表中的所有目录来查找要转向的目录名。

特殊参数

1) $*: 代表所有参数,其间隔为IFS内定参数的第一个字元 
2) $@: 与*星号类同。不同之处在於不参照IFS 
3) $#: 代表参数数量 
4) $?: 执行上一个指令的返回值 
5) $-: 最近执行的foreground pipeline的选项参数 
6) $$: 本身的Process ID 
7) $!: 执行上一个背景指令的PID 
8) $_: 显示出最後一个执行的命令

bash快捷键

Emacs风格
ctrl+p: 方向键 上 ↑ 
ctrl+n: 方向键下 ↓ 
ctrl+b: 方向键 ← 
alt+f: 光标右移一个单词 
ctrl+f :方向键 → 
alt+b: 光标左移一个单词 
ctrl+a:光标移到行首 
ctrl+e:光标移到行尾 
ctrl+k:清除光标后至行尾的内容。 
ctrl+d: 删除光标所在字母;注意和backspace以及ctrl+h的区别,这2个是删除光标前的字符 
ctrl+r:搜索之前打过的命令。会有一个提示,根据你输入的关键字进行搜索bash的history 
ctrl+m : 输入回车 
ctrl+i : 输入tab 
ctrl+[ : 输入esc
其它 ctrl+h:删除光标前一个字符,同 backspace 键相同。 
alt + p 非增量方式反向搜索历史 
alt + > 历史命令列表中的最后一行命令开始向前 
ctrl+u: 清除光标前至行首间的所有内容。 
ctrl+w: 移除光标前的一个单词 
ctrl+t: 交换光标位置前的两个字符 
ctrl+y: 粘贴或者恢复上次的删除 
ctrl+l:清屏,相当于clear。 
ctrl + xx 光标在行头与行尾进行跳转 
alt+r 撤销当前行的所有内容 
ctrl+z : 把当前进程转到后台运行 
ctrl+s : 锁住屏幕 
ctrl+q : 恢复屏幕 
ctrl+v key: 输入特殊字符 
alt + l 将当前光标处之后的字母转化成小写字母 
alt + u 将当前光标处之后的字母转化成大写字母 
ctrl + Alt + e 扩展命令行的内容(例如:ls  =>  ls  -l  --color=tty) 
ctrl+c:杀死当前进程, 输入模式下,中断输入的命令。 
ctrl+d:退出当前 Shell 
esc + . 快捷键可以轮询历史命令的参数或选项。 
esc + t 快捷键可以 置换前两个单词。 
输入重复字母 Esc {100} e 可以输入100个e字符
按多次{esc}可以补全 
{esc}{~}可以补全本机上的用户名 
{esc}{/}可以补全文件名 
{esc}{@}可以补全主机名,localhost可以方便地用 lo补全.

Bang Bang 历史命令

!!    重新执行上一条命令 
!N  重新执行第N条命令。比如 !3 
!-N 重新执行倒数第N条命令。!-3 
!string  重新执行以字符串打头的命令。 比如 !vim 
!?string?  重新执行包含字符串的命令。 比如 !?test.cpp? 
!?string?%  替换为: 最近包含这个字符串的命令的参数。比如:   vim !?test.cpp?% 
!$   替换为:上一条命令的最后一个参数。比如 vim !$ 
!!string  在上一条命令的后面追加 string ,并执行。 
!Nstring  在第N条指令后面追加string,并执行。 
^old^new^  对上一条指令进行替换 
修饰
:s/old/new/  对第N条指令中第一次出现的new替换为old。 比如 vim !?test.cpp?:s/cpp/c/ 
:gs/old/new/  全部替换 
:wn  w为数字, 取指令的第w个参数. 
:p 回显命令而不是执行, 比如 !ls:p  。 这个很有用, 你可以先查看你选的命令对不对,要执行时再使用!!

Bash相关文件

     /etc/profile 设置环境变量(所有用户) 
     ~/.bash_profile 设置环境变量(当前用户) 
     ~/.bashrc 
     ~/.bash_history 
     ~/.bash_logout

2015年1月26日 星期一

Qt中ui文件的使用uic

    用designer设计的*.ui文件可以通过uic工具转换为*.h文件(在编译时也会自动生成这样一个ui_*.h文件),有了这个.h文件就可以直接按照纯C++的方式对其中的类进行调用。ui文件的使用就是利用默认工具uic自动产生一个类,然后用该类的setui函数加载界面到相应的对象上。
      .ui文件的使用有三种形式:第一种是直接使用,第二种是定义一个新类,声明一个ui子对象,利用该对象来加载界面,第三种是将ui作为基类派生新的类。
      借用一个例程分析如下:                                                                                                

#ifndef DIALOG_H
       #define DIALOG_H
#include
namespace Ui {
                class Dialog;      
}

class Dialog : public QDialog {        //又定义了一个Dialog类
                Q_OBJECT
        public:
                Dialog(QWidget *parent = 0);
                ~Dialog();
protected:
                void changeEvent(QEvent *e);
private:
                
Ui::Dialog *ui;        // 声明一个子类
private slots:
                void on_pushButton_clicked();
        };
#endif // DIALOG_H
红色部分声明一个类,将设计出来的ui界面作为该类的一个子对象,在其构造函数中,先完成对子对象的构造,再使用子对象ui调用其setupUi(this)函数实现ui的现实。
Dialog::Dialog(QWidget *parent) :
                QDialog(parent),
                ui(new Ui::Dialog)
        {
                ui->setupUi(this);   //加载界面
        }

看完上面的代码,我们来分析下到底为什么要这样来使用ui文件。
在没有qtcreator之前,给了我们一个ui文件,该如何调用?
针对于ui文件,不知道大家知不知道uic这个工具,这是qt继承的一个工具,它可以利用ui生产.h文件。
uic  dialog.ui  –o  ui_dialog.h
就生产了下面的ui_dialog.h文件:

#ifndef UI_DIALOG_H
       #define UI_DIALOG_H
#include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
QT_BEGIN_NAMESPACE
class Ui_Dialog
        {
        public:
                QLabel *label;
                QPushButton *pushButton;
        void setupUi(QDialog *Dialog)
                {
                        if (Dialog->objectName().isEmpty())
                        Dialog->setObjectName(QString::fromUtf8("Dialog"));
                        Dialog->resize(115, 148);
                        label = new QLabel(Dialog);
                        label->setObjectName(QString::fromUtf8("label"));
                        label->setGeometry(QRect(10, 30, 91, 21));
                        QFont font;
                        font.setPointSize(12);
                        font.setBold(true);
                        font.setWeight(75);
                        label->setFont(font);
                        pushButton = new QPushButton(Dialog);
                        pushButton->setObjectName(QString::fromUtf8("pushButton"));
                        pushButton->setGeometry(QRect(20, 80, 75, 23));
                retranslateUi(Dialog);
                QMetaObject::connectSlotsByName(Dialog);
                } // setupUi
        void retranslateUi(QDialog *Dialog)
                {
                        Dialog->setWindowTitle(QApplication::translate("Dialog",        "Dialog",        0,        QApplication::UnicodeUTF8));
                        label->setText(QApplication::translate("Dialog",        "hello,wang",        0,        QApplication::UnicodeUTF8));
                        pushButton->setText(QApplication::translate("Dialog",        "close",        0,        QApplication::UnicodeUTF8));
                } // retranslateUi
};
namespace Ui {
                class Dialog: public Ui_Dialog {};    
//此处定义了命名空间,其中定义了一个Dialog类,继承自Ui_Dialog类
        } // namespace Ui
QT_END_NAMESPACE
#endif // TT_H
通过观察我们会发现uic自动将我们设计的ui文件,生成了一个类,在此例中为class Ui_Dialog。事实上也是这样,uic会自动会利用设计好的ui生成一个包含类Ui_**的ui_**.h文件。那么在此例中,就会将我们设计好的dialog就会被uic文件解析,生成一个叫做ui_dialog.h的文件,此文件中包含Ui_Dialog的类。
那么总结出来,要让ui design设计出来的界面显示出来,只要能设法调用Ui_Dialog类的setupUi函数就行了。
一种简单的方法,直接使用,重新写一个这样的main函数。
#include
        #include
#include "ui_dialog.h"
        int main(int argc, char *argv[])
        {
        QApplication a(argc, argv);
        Ui::Dialog ui;
        QDialog *d=new QDialog;
        ui. setupUi(d);
                d->show();
                return a.exec();
        }

第二种方法相对比较简单一点,就是将Ui::Dialog ui或Ui::Dialog *ui写成一个新定义类的一个数据成员,也就是qtcreator提供的那种方法。(也叫单继承方法,只继承了QDialog类)
#include
        #include "ui_dialog.h"
class Dialog : public QDialog {
                Q_OBJECT
        public:
                Dialog(QWidget *parent = 0);
                ~Dialog();
protected:
                void changeEvent(QEvent *e);
private:
                Ui::Dialog *ui;
private slots:
                void on_pushButton_clicked();
        };
这样使用的时候需要注意的是在初始化的时候要先完成子对象的初始化,在其构造函数中重写构造函数。
Dialog::Dialog(QWidget *parent) :
                QDialog(parent),
                ui(new Ui::Dialog)
        {
                ui->setupUi(this);
        }

第三种方法是以Ui_Dialog类为基类,派生一个新类,在该类的初始化函数中调用setupUi。(也叫多重继承方法,继承了QDialog类和Ui::Dialog类)
#ifndef DIALOG_H
        #define DIALOG_H
#include
        #include "ui_dialog.h"
class Dialog : public QDialog ,public Ui::Dialog
        {
                Q_OBJECT
        public:
                Dialog(QWidget *parent = 0);
        };
实现如下:
#endif // DIALOG_H
        #include "dialog.h"
        #include "ui_dialog.h"
Dialog::Dialog(QWidget *parent) :
                QDialog(parent),
                Ui::Dialog()       
        {
                setupUi(this);
        }

2015年1月16日 星期五

inet_ntoa 产生的段错误

inet_ntoa()在CENTOS532位版和64为机器上结果不一样。
 
32位正常打印,64位却是段错误,错在haddrp这个参数上,报错内容是说inet_ntoa的返回值是int,因此%s无法输出。
解决方法:
1. 如果用inet_ntoa()的话需要#include <arpa/inet.h> ,就正常了。
2. 使用inet_ntop()

2015年1月11日 星期日

Linux信号处理机制

在Linux中,信号是进程间通讯的一种方式,它采用的是异步机制。当信号发送到某个进程中时,操作系统会中断该进程的正常流程,并进入相应的信号处理函数执行操作,完成后再回到中断的地方继续执行。
需要说明的是,信号只是用于通知进程发生了某个事件,除了信号本身的信息之外,并不具备传递用户数据的功能。

1 信号的响应动作

每个信号都有自己的响应动作,当接收到信号时,进程会根据信号的响应动作执行相应的操作,信号的响应动作有以下几种:
  • 中止进程(Term)
  • 忽略信号(Ign)
  • 中止进程并保存内存信息(Core)
  • 停止进程(Stop)
  • 继续运行进程(Cont)
用户可以通过signalsigaction函数修改信号的响应动作(也就是常说的“注册信号”,在文章的后面会举例说明)。另外,在多线程中,各线程的信号响应动作都是相同的,不能对某个线程设置独立的响应动作。

2 信号类型

Linux支持的信号类型可以参考下面给出的列表。

2.1 在POSIX.1-1990标准中的信号列表

信号动作说明
SIGHUP1Term终端控制进程结束(终端连接断开)
SIGINT2Term用户发送INTR字符(Ctrl+C)触发
SIGQUIT3Core用户发送QUIT字符(Ctrl+/)触发
SIGILL4Core非法指令(程序错误、试图执行数据段、栈溢出等)
SIGABRT6Core调用abort函数触发
SIGFPE8Core算术运行错误(浮点运算错误、除数为零等)
SIGKILL9Term无条件结束程序(不能被捕获、阻塞或忽略)
SIGSEGV11Core无效内存引用(试图访问不属于自己的内存空间、对只读内存空间进行写操作)
SIGPIPE13Term消息管道损坏(FIFO/Socket通信时,管道未打开而进行写操作)
SIGALRM14Term时钟定时信号
SIGTERM15Term结束程序(可以被捕获、阻塞或忽略)
SIGUSR130,10,16Term用户保留
SIGUSR231,12,17Term用户保留
SIGCHLD20,17,18Ign子进程结束(由父进程接收)
SIGCONT19,18,25Cont继续执行已经停止的进程(不能被阻塞)
SIGSTOP17,19,23Stop停止进程(不能被捕获、阻塞或忽略)
SIGTSTP18,20,24Stop停止进程(可以被捕获、阻塞或忽略)
SIGTTIN21,21,26Stop后台程序从终端中读取数据时触发
SIGTTOU22,22,27Stop后台程序向终端中写数据时触发
:其中SIGKILLSIGSTOP信号不能被捕获、阻塞或忽略。

2.2 在SUSv2和POSIX.1-2001标准中的信号列表

信号动作说明
SIGTRAP5CoreTrap指令触发(如断点,在调试器中使用)
SIGBUS0,7,10Core非法地址(内存地址对齐错误)
SIGPOLLTermPollable event (Sys V). Synonym for SIGIO
SIGPROF27,27,29Term性能时钟信号(包含系统调用时间和进程占用CPU的时间)
SIGSYS12,31,12Core无效的系统调用(SVr4)
SIGURG16,23,21Ign有紧急数据到达Socket(4.2BSD)
SIGVTALRM26,26,28Term虚拟时钟信号(进程占用CPU的时间)(4.2BSD)
SIGXCPU24,24,30Core超过CPU时间资源限制(4.2BSD)
SIGXFSZ25,25,31Core超过文件大小资源限制(4.2BSD)
:在Linux 2.2版本之前,SIGSYSSIGXCPUSIGXFSZ以及SIGBUS的默认响应动作为Term,Linux 2.4版本之后这三个信号的默认响应动作改为Core。

2.3 其它信号

信号动作说明
SIGIOT6CoreIOT捕获信号(同SIGABRT信号)
SIGEMT7,-,7Term实时硬件发生错误
SIGSTKFLT-,16,-Term协同处理器栈错误(未使用)
SIGIO23,29,22Term文件描述符准备就绪(可以开始进行输入/输出操作)(4.2BSD)
SIGCLD-,-,18Ign子进程结束(由父进程接收)(同SIGCHLD信号)
SIGPWR29,30,19Term电源错误(System V)
SIGINFO29,-,-电源错误(同SIGPWR信号)
SIGLOST-,-,-Term文件锁丢失(未使用)
SIGWINCH28,28,20Ign窗口大小改变时触发(4.3BSD, Sun)
SIGUNUSED-,31,-Core无效的系统调用(同SIGSYS信号)
注意:列表中有的信号有三个值,这是因为部分信号的值和CPU架构有关,这些信号的值在不同架构的CPU中是不同的,三个值的排列顺序为:1,Alpha/Sparc;2,x86/ARM/Others;3,MIPS。
例如SIGSTOP这个信号,它有三种可能的值,分别是17、19、23,其中第一个值(17)是用在Alpha和Sparc架构中,第二个值(19)用在x86、ARM等其它架构中,第三个值(23)则是用在MIPS架构中的。

3 信号机制

文章的前面提到过,信号是异步的,这就涉及信号何时接收、何时处理的问题。
我们知道,函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换,过程可以先看一下下面的示意图:
信号处理机制示意图
接下来围绕示意图,将信号分成接收、检测和处理三个部分,逐一讲解每一步的处理流程。

3.1 信号的接收

接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。
注意,此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。

3.2 信号的检测

进程陷入内核态后,有两种场景会对信号进行检测:
  • 进程从内核态返回到用户态前进行信号检测
  • 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
当发现有新信号时,便会进入下一步,信号的处理。

3.3 信号的处理

信号处理函数是运行在用户态的,调用处理函数前,内核会将当前内核栈的内容备份拷贝到用户栈上,并且修改指令寄存器(eip)将其指向信号处理函数。
接下来进程返回到用户态中,执行相应的信号处理函数。
信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。
至此,一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。