Andy Niu �����ĵ�

Andy Niu

Andy Niu Help  1.0.0.0
进程管理

变量

 如何杀掉僵尸进程
 
 孤儿进程与僵尸进程
 
 父进程监听的端口转移到子进程
 
 父进程监听的端口转移到子进程__测试
 
 linux程序崩溃,没有产生dmp文件
 
 杀死进程
 
 proc目录
 
 ps查看进程
 
 pstree查看进程树
 
 pstack查看线程死锁
 
 Linux生成core文件
 
 进程在后台运行
 
 Linux下进程间通信
 
 如何判断一个进程是谁启动的
 
 理解nohup
 

详细描述

变量说明

Linux下进程间通信
1、进程间的通信方法有:管道、消息队列、共享内存、信号、信号量、套接口。
2、管道
    管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种。
    前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 
3、消息队列
    消息队列用于运行于同一台机器上的进程间通信,它和管道很相似。
    是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现,消息链表中节点的结构用msg声明。
    事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它。
4、共享内存 
    共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。
    通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。
    前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是实际的物理内存。
    在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。
    常用的方式是通过shmXXX函数族来实现利用共享内存进行存储的。 
5、信号
    发送信号给某个进程,这个进程接收到信号后,进行相应的处理。
6、信号量
    通过PV操作来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。
    本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。
7、套接字
    套接字(socket)是进程间通信的主要方式,分为网络套接字(TCP/IP套接字)和UNIX域套接字。
    UNIX域套接字解决什么样的问题?
    对于CS架构,一般情况下客户端和服务端在不同的机器上,因此,二者需要TCP/IP网络连接来交互,也就是网络套接字。
    但是有些情况下,客户端和服务端在同一台机器上,二者没有必要再经过网络来交互,这就是UNIX域套接字要解决的问题。
    UNIX域套接字比网络套接字的效率更高,UNIX域数据报服务是可靠的,不会丢失消息,也不会传递出错。
    实际上,UNIX域套接字是套接字和管道之间的混合物,同时与网络套接字保持了一致的接口。
Linux生成core文件
1、linux程序崩溃,发生segmentation fault段错误,会把程序运行时的内存,堆栈指针,寄存器状态,
    函数调用堆栈等信息保存为一个coredump文件,通过gdb可以查看这些信息,方便我们定位问题。
2、首先一点,要生成coredump文件,必须设置 ulimit -c unlimited。
    ulimit -c unlimited 表示生成coredump文件,不限制生成的coredump文件大小。
    否则会因为要生成的coredump文件过大,导致系统不生成。
3、生成的coredump文件,生成的位置在执行文件的当前目录,如果要修改生成的目录怎么办?
    程序可能会多次崩溃,如果每次生成的文件名相同,会导致这一次崩溃生成的coredump文件覆盖掉上一次的coredump文件。怎么办?
4、这就需要设置生成的coredump文件目录和文件名,通过/proc/sys/kernel目录下面的文件core_pattern来设置。如下:
    echo "./core.%p.%e" > /proc/sys/kernel/core_pattern 覆盖文件core_pattern的内容 
    选项表示的意义如下:
    %% 单个%字符
    %p 所dump进程的进程ID
    %u 所dump进程的实际用户ID
    %g 所dump进程的实际组ID
    %s 导致本次core dump的信号
    %t core dump的时间 (由1970年1月1日计起的秒数)
    %h 主机名
    %e 程序文件名
    
    一个启动脚本如下:
    ulimit -c unlimited
    ulimit -n 65535
    PROXY_HOME=`dirname $0`
    cd ${PROXY_HOME}
    if [ -d "/mnt/data_bank" ]
    then
        echo "/mnt/data_bank/core.%p.%e" > /proc/sys/kernel/core_pattern
    else
        echo "./core.%p.%e" >  /proc/sys/kernel/core_pattern
    fi
    export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
    ./vmu_main -f vmu_linux.xml
5、注意:在/proc/sys/kernel目录,除了文件core_pattern,还有两个文件core_pipe_limit和core_uses_pid
    [root@localhost kernel]# pwd
    /proc/sys/kernel
    [root@localhost kernel]# ll |grep core
    -rw-r--r-- 1 root root 0 2015-10-21 15:18 core_pattern
    -rw-r--r-- 1 root root 0 2015-10-21 15:13 core_pipe_limit
    -rw-r--r-- 1 root root 0 2015-10-21 15:13 core_uses_pid
    [root@localhost kernel]# more core_pattern 
    ./core.%p.%e
    [root@localhost kernel]# more core_pipe_limit 
    0
    [root@localhost kernel]# more core_uses_pid 
    1
6、对于文件core_uses_pid,如果这个文件的内容被配置成1,那么即使core_pattern中没有设置%p,
    最后生成的coredump文件名仍会加上进程ID。
7、core_pipe_limit和core_pattern结合使用,当core_pattern以”|“ 开头,也就是管道模式,才起作用。
    这种情况,coredump不是生成文件,而是作为一个程序的标准输入,这个程序充当了coredump的处理程序。
    
    当配置成管道core文件模式时,内核通过管道搜集core信息,偶尔部分有用的数据信息要从崩溃程序的/proc/pid目录中活动。
    为了能够安全的获得数据,不能过早的清除崩溃程序的/proc/pid目录,而必须等待搜集数据信息完毕。
    反过来,如果用户空间的一个行为不正确的数据搜集程序从/proc/pid目录中获得数据,
    就可能一直阻止内核对/proc/pid的崩溃进程进行回收。
    
    core_pipe_limit这个文件定义了,coredump处理程序可以并发处理多少个崩溃的程序。
    也就是有多少个并发的崩溃程序可以通过管道模式传递给指定的coredump处理程序。
    如果超过了指定数,则后续的程序将不会处理,只在内核日志中做记录。
    0是个特殊的值,当设置为0时,不限制并行捕捉崩溃的进程,但不会等待用户程序搜集完毕方才回收/proc/pid目录
    (就是说,崩溃程序的相关信息可能随时被回收,搜集的信息可能不全)。发布时,默认值为0
linux程序崩溃,没有产生dmp文件
1、一般是没有设置 ulimit -c unlimited
2、如果设置了 ulimit -c unlimited,程序崩溃,可执行程序的当前目录没有产生dmp文件,检查这个目录是否有写文件的权限。
3、如果目录有写文件的权限,则很大概率是生成在其它目录。
    检查文件 /proc/sys/kernel/core_pattern
    [root@HikvisionOS VTDUTest]# more /proc/sys/kernel/core_pattern                         
    /opt/components_core/core-%e-%p-%t
4、通过下面的信息,确认崩溃如下:
    [root@HikvisionOS niuzb]# cat /var/log/messages |grep main
    Jan 29 14:52:38 localhost kernel: traps: main[10196] trap divide error ip:4005c1 sp:7fff383a1a60 error:0 in main[400000+1000]
    [root@HikvisionOS niuzb]# ll /opt/components_core/        
    -rw------- 1 root root     413696 1月  29 14:52 core-main-10196-1517208758
proc目录
1、proc文件系统是伪文件系统,不存在硬盘上,而是在内存中。它以文件系统的方式,对外暴露接口,
    允许调用者访问系统内核数据。
2、比如查看cpu信息,如下:
    [root@localhost proc]# more /proc/cpuinfo 
    processor       : 0
    vendor_id       : GenuineIntel
    cpu family      : 6
    model           : 60
    model name      : Intel(R) Core(TM) i7-4712MQ CPU @ 2.30GHz
    stepping        : 3
    cpu MHz         : 2294.740
    cache size      : 6144 KB
    fdiv_bug        : no
    hlt_bug         : no
    f00f_bug        : no
    coma_bug        : no
    fpu             : yes
    fpu_exception   : yes
    cpuid level     : 13
    wp              : yes
    flags           : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss nx rdtscp constant_t
    sc up ida nonstop_tsc arat pni ssse3 sse4_1 sse4_2 popcnt [8]
    bogomips        : 4589.48
3、查看进程信息,需要使用进程Id,如下:
    [root@localhost proc]# pgrep cmu
    8155
    [root@localhost proc]# ll ./8155
    total 0
    dr-xr-xr-x  2 root root 0 Aug 28 11:02 attr
    -r--------  1 root root 0 Aug 28 11:02 auxv
    -r--r--r--  1 root root 0 Aug 28 11:01 cmdline
    -rw-r--r--  1 root root 0 Aug 28 11:02 coredump_filter
    -r--r--r--  1 root root 0 Aug 28 11:02 cpuset
    lrwxrwxrwx  1 root root 0 Aug 28 11:02 cwd -> /usr/local/IBP/lib_linux/cmu
    -r--------  1 root root 0 Aug 28 11:02 environ
    lrwxrwxrwx  1 root root 0 Aug 28 11:02 exe -> /usr/local/IBP/lib_linux/cmu/cmu_main
    dr-x------  2 root root 0 Aug 28 11:01 fd
    -r--r--r--  1 root root 0 Aug 28 11:02 io
    -r--------  1 root root 0 Aug 28 11:02 limits
    -rw-r--r--  1 root root 0 Aug 28 11:02 loginuid
    -r--r--r--  1 root root 0 Aug 28 11:02 maps
    -rw-------  1 root root 0 Aug 28 11:02 mem
    -r--r--r--  1 root root 0 Aug 28 11:01 mounts
    -r--------  1 root root 0 Aug 28 11:02 mountstats
    -rw-r--r--  1 root root 0 Aug 28 11:02 oom_adj
    -r--r--r--  1 root root 0 Aug 28 11:02 oom_score
    lrwxrwxrwx  1 root root 0 Aug 28 11:02 root -> /
    -r--r--r--  1 root root 0 Aug 28 11:02 schedstat
    -r--r--r--  1 root root 0 Aug 28 11:02 smaps
    -r--r--r--  1 root root 0 Aug 28 11:01 stat
    -r--r--r--  1 root root 0 Aug 28 11:02 statm
    -r--r--r--  1 root root 0 Aug 28 11:01 status
    dr-xr-xr-x 10 root root 0 Aug 28 11:02 task
    -r--r--r--  1 root root 0 Aug 28 11:01 wchan
pstack查看线程死锁
1、测试代码如下:
    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    pthread_mutex_t mutexA;
    pthread_mutex_t mutexB;
    
    void* thread_func1(void*)
    {
            pthread_mutex_lock(&mutexA);
            printf("thread1 lock A\n");
            sleep(5);
    
            {
                    pthread_mutex_lock(&mutexB);
                    printf("thread1 lock B\n");
                    sleep(5);
                    pthread_mutex_unlock(&mutexB);
            }
    
            pthread_mutex_unlock(&mutexA);
    }
    void* thread_func2(void*)
    {
            pthread_mutex_lock(&mutexB);
            printf("thread2 lock B\n");
            sleep(5);
    
            {
                    pthread_mutex_lock(&mutexA);
                    printf("thread2 lock A\n");
                    sleep(5);
                    pthread_mutex_unlock(&mutexA);
            }
    
            pthread_mutex_unlock(&mutexB);
    }
    
    int main()
    {
            pthread_t thread1;
            pthread_t thread2;
            pthread_mutex_init(&mutexA,NULL);
            pthread_mutex_init(&mutexB,NULL);
    
    
            if(pthread_create(&thread1,NULL,thread_func1,NULL) == -1)
            {
                    printf("create thread1 error !\n");
                    exit(1);
            }
    
            if(pthread_create(&thread2,NULL,thread_func2,NULL) == -1)
            {
                    printf("create thread2 error!\n");
                    exit(1);
            }
    
            pthread_join(thread1,NULL);
            pthread_join(thread2,NULL);
    
            pthread_mutex_destroy(&mutexA);
            pthread_mutex_destroy(&mutexB);
    
            return 0;
    }
2、编译
    g++ -o main main.cpp -lpthread
3、运行,使用pstack查看,每个线程的调用堆栈。
    [root@localhost ~]# pstack 26438
    Thread 3 (Thread 0xb7f4db90 (LWP 26439)):
    #0  0x00f53402 in __kernel_vsyscall ()
    #1  0x0084c6e9 in __lll_lock_wait () from /lib/libpthread.so.0
    #2  0x00847d9f in _L_lock_885 () from /lib/libpthread.so.0
    #3  0x00847c66 in pthread_mutex_lock () from /lib/libpthread.so.0
    #4  0x08048822 in thread_func1(void*) ()
    #5  0x00845832 in start_thread () from /lib/libpthread.so.0
    #6  0x0079ae0e in clone () from /lib/libc.so.6
    Thread 2 (Thread 0xb754cb90 (LWP 26440)):
    #0  0x00f53402 in __kernel_vsyscall ()
    #1  0x0084c6e9 in __lll_lock_wait () from /lib/libpthread.so.0
    #2  0x00847d9f in _L_lock_885 () from /lib/libpthread.so.0
    #3  0x00847c66 in pthread_mutex_lock () from /lib/libpthread.so.0
    #4  0x080487ba in thread_func2(void*) ()
    #5  0x00845832 in start_thread () from /lib/libpthread.so.0
    #6  0x0079ae0e in clone () from /lib/libc.so.6
    Thread 1 (Thread 0xb7f4e6d0 (LWP 26438)):
    #0  0x00f53402 in __kernel_vsyscall ()
    #1  0x00846a77 in pthread_join () from /lib/libpthread.so.0
    #2  0x0804874a in main ()
4、一共3个线程,每个线程从下到上,显示调用堆栈,可以看到Thread 2 和 Thread 3都死锁在 __lll_lock_wait ()
pstree查看进程树
1、pstree查看进程树,如下:
    [root@localhost ~]# pstree
    init─┬─atd
        ├─auditd───{auditd}
        ├─crond
        ├─dbus-daemon
        ├─ibpctrl───hau_main───5*[{hau_main}]
        ├─ibpctrl───dmu_main───27*[{dmu_main}]
        ├─ibpctrl───vtdu_main───19*[{vtdu_main}]
        ├─ibpctrl───vru_main───24*[{vru_main}]
        ├─ibpctrl───uas_main───14*[{uas_main}]
        ├─ibpctrl───lku_main───7*[{lku_main}]
        ├─ibpctrl───cmu_main───5*[{cmu_main}]
        ├─ibpctrl───uac_main───26*[{uac_main}]
        ├─ibplogbk.sh───sleep
        ├─java───93*[{java}]
        ├─login───bash
        ├─5*[mingetty]
        ├─mysqld_safe───mysqld───13*[{mysqld}]
        ├─rsyslogd───3*[{rsyslogd}]
        ├─sshd─┬─5*[sshd───bash]
        │      ├─sshd───bash───7*[tcpdump]
        │      └─sshd───bash───pstree
        ├─udevd───2*[udevd]
        └─watch_dog
2、查看进程已经子进程,如下:
    [root@localhost init.d]# pstree -p 3677
    sshd(3677)───sshd(10819)─┬─bash(9700)───ping(11719)
                            ├─bash(10822)
                            └─bash(13914)───pstree(11834)
    注意:p后面必须有一个空格
ps查看进程
1、ps列出进程,常用的两个选项为 aux 和 -ef
2、使用aux
    [root@localhost ~]# ps aux|grep main
    root      2972  0.0  0.0   3808   716 pts/3    S+   20:00   0:00 grep main
    root     15577  0.6  0.1  85148  3964 ?        Sl   Oct19  10:29 ./hau_main /f hau_linux.xml
    root     19929  3.0  0.4 317540 16952 ?        Sl   17:26   4:45 ./dmu_main -f dmu_linux.xml
    root     19989  1.7  0.4 226520 15824 ?        Sl   17:26   2:43 ./vtdu_main -f vtdu_linux.xml
    root     20032  1.1  0.4 281116 14948 ?        Sl   17:26   1:47 ./vru_main -f vru_linux.xml
    root     20133  0.3  0.5 171728 19020 ?        Sl   17:26   0:28 ./uas_main -f uas_linux.xml
    root     20174  0.7  0.3 102256 13064 ?        Sl   17:26   1:12 ./lku_main -f lku_linux.xml
    root     20379  0.4  0.4  81384 16220 ?        Sl   17:26   0:45 ./cmu_main -f cmu_linux.xml
    root     27523  2.8  0.7 307272 26228 ?        Sl   17:38   4:02 ./uac_main
    表示意思分别如下:
    USER:该 process 属于那个使用者账号的
    PID :该 process 的号码
    %CPU:该 process 使用掉的 CPU 资源百分比
    %MEM:该 process 所占用的物理内存百分比
    VSZ :该 process 使用掉的虚拟内存量 (Kbytes)
    RSS :该 process 占用的固定的内存量 (Kbytes)
    TTY :该 process 是在那个终端机上面运作,若与终端机无关,则显示 ?,另外, tty1-tty6 是本机上面的登入者程序,
            若为 pts/0 等等的,则表示为由网络连接进主机的程序。
    STAT:该程序目前的状态,主要的状态有
            R:该程序目前正在运作,或者是可被运作
            S:该程序目前正在睡眠当中 (可说是 idle 状态),但可被某些讯号 (signal) 唤醒。
            T:该程序目前正在侦测或者是停止了
            Z:该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸) 程序的状态
    START:该 process 被触发启动的时间
    TIME :该 process 实际使用 CPU 运作的时间
    COMMAND:该程序的实际指令
3、使用-ef,可以查看父子进程之间的关系
    [root@localhost ~]# ps -ef|grep main
    root      6397 29980  0 20:08 pts/3    00:00:00 grep main
    root     15577 15574  0 Oct19 ?        00:10:32 ./hau_main /f hau_linux.xml
    root     19929 19926  3 17:26 ?        00:04:58 ./dmu_main -f dmu_linux.xml
    root     19989 19986  1 17:26 ?        00:02:49 ./vtdu_main -f vtdu_linux.xml
    root     20032 20029  1 17:26 ?        00:01:52 ./vru_main -f vru_linux.xml
    root     20133 20130  0 17:26 ?        00:00:29 ./uas_main -f uas_linux.xml
    root     20174 20171  0 17:26 ?        00:01:16 ./lku_main -f lku_linux.xml
    root     20379 20376  0 17:26 ?        00:00:47 ./cmu_main -f cmu_linux.xml
    root     27523 27520  2 17:38 ?        00:04:14 ./uac_main
4、查看进程启动时间和运行时间,如下:
    先找到进程pid:
    [root@localhost ~]# ps aux|grep ping
    root      3131  0.0  0.0   3808   668 pts/2    S+   16:20   0:00 ping 10.36.65.80
    root     18421  0.0  0.0   3812   740 pts/3    S+   16:35   0:00 grep ping
    然后列出启动时间和运行时间:
    [root@localhost ~]# ps -eo pid,lstart,etime|grep 3131
    3131 Sun Sep 18 16:20:25 2016       16:07
    [root@localhost ~]# date
    Sun Sep 18 16:36:33 CST 2016
    运行时间为16:07,也就是当前时间【16:36:33】减去启动时间【16:20:25】,多了1秒是因为执行date有个延迟时间。
如何判断一个进程是谁启动的
1、进程A启动进程B,进程A就是进程B的父进程。
2、使用ps -ef|grep dmu列出父子进程之间的关系,就可以找到一个进程是由谁启动的。
如何杀掉僵尸进程
1、僵尸进程是指:杀掉了当前进程,但是父进程没有等待wait,导致当前进程的进程描述仍然保留在系统中,称之为僵尸进程。
2、僵尸进程已经不是一个正常的进程了,只关联一些进程描述符,因此使用kill -9不能杀掉僵尸进程。
3、注意:僵尸的父进程还是存在的,只不过它没有等待僵尸进程。
    因此要杀掉僵尸进程,解决办法是杀掉它的父进程。
孤儿进程与僵尸进程
1、我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。
    子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。
    当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
2、孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
    孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
3、僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,
    那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
4、unix/linux提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是: 
    在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process
    ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。
    直到父进程通过wait/waitpid来取时才释放。
5、但这样就导致了问题,如果进程不调用wait/waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,
    但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。
    此即为僵尸进程的危害,应当避免。
6、孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。
    每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。
    这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
7、也就是说,孤儿进程没有危害,因为init会管理。僵尸进程有危害,僵尸进程释放了进程所拥有的资源,但是进程本身的描述符没有释放,
    等待父进程处理,进程描述符一直没有释放。系统所能使用的进程是有限的,大量的僵尸进程导致系统不能产生新的进程。
8、任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。
    这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是"Z"。
    如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。
9、如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
10、僵尸进程危害场景:
    例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了。
    因此这个子进程的生命周期很短。但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问。
    这样,系统运行上一段时间之后,系统中就会存在很多的僵尸进程。倘若用ps命令查看的话,就会看到很多状态为Z的进程。
    严格地来说,僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵尸进程的那个父进程。
    因此,当我们寻求如何消灭系统中大量的僵尸进程时,答案就是把产生大量僵尸进程的那个元凶枪毙掉
    (也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵尸进程就变成了孤儿进 程,
    这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,
    这样,这些已经僵尸的孤儿进程 就能瞑目而去了。
11、测试孤儿进程,如下:
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    
    int main()
    {
        pid_t pid;
        //创建一个进程
        pid = fork();
        //创建失败
        if (pid < 0)
        {
            perror("fork error:");
            exit(1);
        }
        //子进程
        if (pid == 0)
        {
            printf("I am the child process.\n");
            //输出进程ID和父进程ID
            printf("pid: %d\tppid:%d\n",getpid(),getppid());
            printf("I will sleep five seconds.\n");
            //睡眠5s,保证父进程先退出
            sleep(5);
            printf("pid: %d\tppid:%d\n",getpid(),getppid());
            printf("child process is exited.\n");
        }
        //父进程
        else
        {
            printf("I am father process.\n");
            //父进程睡眠1s,保证子进程输出进程id
            sleep(1);
            printf("father process is  exited.\n");
        }
        return 0;
    }
12、测试僵尸进程,如下:
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <stdlib.h>
    
    int main()
    {
        pid_t pid;
        pid = fork();
        if (pid < 0)
        {
            perror("fork error:");
            exit(1);
        }
        else if (pid == 0)
        {
            printf("I am child process.I am exiting.\n");
            exit(0);
        }
        printf("I am father process.I will sleep two seconds\n");
        //等待子进程先退出
        sleep(2);
        //输出进程信息
        system("ps -o pid,ppid,state,tty,command");
        printf("father process is exiting.\n");
        return 0;
    }
13、父进程循环创建子进程,子进程退出,造成多个僵尸进程。
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    
    int main()
    {
        pid_t  pid;
        //循环创建子进程
        while(1)
        {
            pid = fork();
            if (pid < 0)
            {
                perror("fork error:");
                exit(1);
            }
            else if (pid == 0)
            {
                printf("I am a child process.\nI am exiting.\n");
                //子进程退出,成为僵尸进程
                exit(0);
            }
            else
            {
                //父进程休眠20s继续创建子进程
                sleep(20);
                continue;
            }
        }
        return 0;
    }
14、怎么解决僵尸进程?
15、通过信号机制,子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。如下:
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    static void sig_child(int signo);
    
    int main()
    {
        pid_t pid;
        //创建捕捉子进程退出信号
        signal(SIGCHLD,sig_child);
        pid = fork();
        if (pid < 0)
        {
            perror("fork error:");
            exit(1);
        }
        else if (pid == 0)
        {
            printf("I am child process,pid id %d.I am exiting.\n",getpid());
            exit(0);
        }
        printf("I am father process.I will sleep two seconds\n");
        //等待子进程先退出
        sleep(2);
        //输出进程信息
        system("ps -o pid,ppid,state,tty,command");
        printf("father process is exiting.\n");
        return 0;
    }
    
    static void sig_child(int signo)
    {
        pid_t        pid;
        int        stat;
        //处理僵尸进程
        while ((pid = waitpid(-1, &stat, WNOHANG)) >0)
        {
            printf("child %d terminated.\n", pid);
        }           
    }
16、fork两次,原理是A进程fork出来B进程,同时在B进程中fork出来C,D,E等进程,B进程退出,A进程在外部处理B进程的退出。
    这样的话,C,D,E等进程进程成为孤儿进程,由init来管理。
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    int main()
    {
        pid_t  pid;
        //创建第一个子进程
        pid = fork();
        if (pid < 0)
        {
            perror("fork error:");
            exit(1);
        }
        //第一个子进程
        else if (pid == 0)
        {
            //子进程再创建子进程
            printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
            pid = fork();
            if (pid < 0)
            {
                perror("fork error:");
                exit(1);
            }
            //第一个子进程退出
            else if (pid >0)
            {
                printf("first procee is exited.\n");
                exit(0);
            }
            //第二个子进程
            //睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里
            sleep(3);
            printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
            exit(0);
        }
        //父进程处理第一个子进程退出
        if (waitpid(pid, NULL, 0) != pid)
        {
            perror("waitepid error:");
            exit(1);
        }
        exit(0);
        return 0;
    }
杀死进程
1、ps 结合管道,使用grep找到pid,kill -9 pid 强制杀死进程,如下:
    [root@localhost lib_linux]# ps aux|grep cmu
    root      4971  0.0  0.1   4492   792 pts/2    S    09:32   0:00 /bin/sh ./ibpctrl start cmu
    root      4973  0.4  2.3  97156 12156 pts/2    Sl   09:32   0:00 ./cmu_main -f cmu_linux.xml
    root      4988  0.0  0.1   3920   708 pts/2    R+   09:32   0:00 grep cmu
    [root@localhost lib_linux]# kill -9 4973
    [root@localhost lib_linux]# ps aux|grep cmu
    root      5004  0.0  0.1   3920   688 pts/2    R+   09:32   0:00 grep cmu
    注:kill的工作原理是,向Linux系统的内核发送一个系统操作信号(signal)和进程标识号(pid),
    然后系统内核就可以对指定的进程进行操作。通常终止前台进程我们使用ctrl+c,终止后台进程使用kill命令。
2、使用pidof 找到进程id,然后杀死,需要注意的是,pidof 必须给出进程的全名。如下:
    [root@localhost lib_linux]# pidof cmu_main
    5549
    [root@localhost lib_linux]# kill -9 5549
3、使用pgrep直接找到pid,杀死进程,pgrep就是 process grep,只要包含模式【也就是关键字】就行,如下:
    [root@localhost lib_linux]# pgrep cmu
    5240
    [root@localhost lib_linux]# kill -9 5240
    [root@localhost lib_linux]# pgrep cmu
4、使用xargs对找到pid直接杀死,如下:
    [root@localhost lib_linux]# pgrep mu           
    6175
    6212
    [root@localhost lib_linux]# pgrep mu|xargs kill -9
    [root@localhost lib_linux]# pgrep mu
    注意:xargs相当于遍历前面的输出,作为下一个命令的输入,逐个执行。
5、使用pkill,pkill=pgrep+kill,根据进程名称,杀死进程,如下:
    [root@localhost lib_linux]# pgrep dmu          
    7407
    [root@localhost lib_linux]# pkill -9 dmu
    [root@localhost lib_linux]# pgrep dmu
6、使用killall,根据进程名称杀死进程,如下:
    [root@localhost lib_linux]# pgrep dmu          
    7453
    [root@localhost lib_linux]# killall -9 dmu
    dmu: no process killed
    [root@localhost lib_linux]# killall -9 dmu_main
    [root@localhost lib_linux]# pgrep dmu
    需要注意的是:和pidof类似,使用killall必须给出进程的全名称。
父进程监听的端口转移到子进程
1、存在的问题:
    virgo调用rpu_sdk接口启动其它服务(比如cmu),rpu_sdk的接口实现是调用脚本启动cmu。当前virgo监听的端口是9875,
    当关闭virgo之后,virgo的监听端口9875转移到脚本进程上(即ibpctrl),关闭脚本进程,监听端口9875又转移到cmu进程上(即cmu_main)。
    9875端口被占用,导致virgo不能启动。
    我在linux下模拟了场景,测试结果一样。
2、原因是:
    rpu_sdk使用system或者popen来执行脚本,system或者popen的实现都有一个执行过程,大致是fork->execl->return
    问题出在fork,fork后子进程会对父进程的数据段,堆和栈信息,做一个副本。
    (注意:由于子进程fork之后经常跟着exec执行另一个程序,从父进程拷贝的内容并不使用,因此,fork的实现会使用COW技术)。
    对于文件描述符,fork的一个特性是父进程所有打开的文件描述符会被复制到子进程中。文件描述符(FD)包含FD标志和文件表指针。
    对于父子进程中相同的文件描述符,复制了FD标志和文件表指针,FD标志相同,文件表指针指向同一个文件表,对打开的文件共享。
    
    对于从父进程复制过来的文件描述符,在子进程中可以使用,接着在exec执行的程序里,文件描述符还是可以使用的。
    这个时候kill掉父进程,子进程成为孤儿进程,由init管理,但是子进程上的文件描述符还是打开的。
    表现为父进程的文件描述符转移到子进程上面。
3、怎么解决这个问题?
    在fork子进程之后,exec之前,在子进程上,close关闭掉无用的文件描述符,父进程的文件描述符并不关闭。
4、上面的方法存在一个问题,在复杂的系统中,fork子进程的时候,不知道父进程已经打开了多少个文件描述符,逐一清理很难。
    我们期望的是能在fork子进程前,也就是打开某个文件的时候,指定好:这个文件描述符,在fork子进程后,对于子进程,执行exec时就关闭。
    这就是所谓的 close-on-exec,意为如果对文件描述符设置了FD_CLOEXEC,子进程也复制了FD_CLOEXEC,在fork的子进程中使用exec执行的程序里,
    此描述符被关闭,不能再使用它。但是在使用fork调用的子进程中,此描述符并不关闭,仍可使用。
5、close-on-exec的功能,只需要调用系统的fcntl就能实现,如下:
    int fd=open("foo.txt",O_RDONLY);  
    int flags = fcntl(fd, F_GETFD);  
    flags |= FD_CLOEXEC;  
    fcntl(fd, F_SETFD, flags);  
    这样,当fork子进程后,仍然可以使用fd。但执行exec后系统就会字段关闭子进程中的fd了。
6、但是我们的场景是,virgo调用rpu_sdk,rpu_sdk并不知道监听9875的文件描述符是多少。
    文件描述符一般从3开始,因为按照惯例0,1,2分别是标准输入,标准输出,错误输出。
    因此遍历到1000,每个数值FD都执行一把fcntl
父进程监听的端口转移到子进程__测试

1、测试代码: linux_socket.h

/*
* 文件功能: linux 下套接字简化操作函数
* 文件名称: linux_socket.h
* 建立时间: 2007 年 07 月 19 号
* 创建作者: wlzqi
* 使用语言: C 或 C++ 语言
* 使用环境: Linux + Windows
* 函数要求: 
*    + 函数相对比较底层
*    + 只使用系统API和C库,不能使用任何第三方库
*    + 不可以用全局变量
*    + 使用比较频繁
*    + 具有模块化(函数不要嵌套)
*    +   要有足够强壮性和高效
*    +   要经过一定强度的本地测试
*    + 尽量不使用动态分配内存(特殊情况可慎重添加)
*    + 所有变量必须字节对齐
* 代码要求:
*    + 尽量减少临时变量
*    + 算法要精炼
*    + 临时变量名要使用英文或英文缩写或英文词组单词首字母,并且英文要准确(不要超过12个字母)
*    + 函数名要求将函数功能描述清楚,单词之间要用 _ 连接。例如:analyze_string 分解字符串、
*     execute_sql 执行SQL语句、get_database_field_name 得到数据库字段名称.....等
*    + 所有函数均使用小写字母拼写
*    + 符号 ,和; 后要有一空格、符号 & * 要紧挨右边的变量、符号++ 要紧挨左边的变量、所有运算符
*     两边都要留有空格
*    + if 和 for 、where 等要和紧挨的 '(' 符号间留有一空格
*    + 合理运用空行使代码清晰易读
* 注释风格:
*    + 注释要得当风格要统一
*    + 注释只能在代码之上
*    + 如果注释和代码同行,则要求必须空出两个 Tab 键
*    + 注释内容和注释符之间要有一空格
*    + 如果注释要分行写则要求具有以下样子

      *    + 函数功能注释必须有:函数说明、参数说明、返回说明、注意事项、使用举例
      * 注意事项:
      *    + 如遇本文件中的函数与系统函数功能相同时,因优先考虑使用本文件中的函数
      *    + '必须'保证编译时不出现警告信息
      *   + 缺省情况下,给一个已经断开连接(非法断开)的地址发送数据程序会退出,所以建议不要使用默认的
      *    缺省设置。建议应用根据需要处理 SIGPIPE 信号,至少不要用系统缺省的处理方式处理这个信号
      *    ,系统缺省的处理方式是退出进程,这样你的应用就很难查处处理进程为什么退出。
      *    如果调用 signal(SIGPIPE, SIG_IGN); 那么程序在对方已断开的情况下发送数据就会返回 -1,errno 号为 EPIPE(32)
      *    如果调用 signal(SIGPIPE, function_name); 那么程序在对方已断开的情况下发送数据首先会 SIGPIPE 响应函数,然后返回 -1,errno 号为 EPIPE(32)
      *    如果在发送数据中对方断开,那么发送端会先返回已发送的数据字节数,在下次再调用发送时返回 -1,errno 号为 ECONNRESET(104) 
      *    建议使用 signal 处理信号,避免进程莫名退出和描述符泄露和死连接
      * 已加模块:
      *    + socket_format_err   格式化 socket 错误信息
      *   + socket_format_herr 自定义格式化 socket 错误信息
      *    + socket_create     创建 Socket
      *    + socket_connect    连接 Socket
      *   + socket_bind      邦定本地端口
      *   + socket_listen     监听本地端口描述符
      *   + socket_accept     接受 Socket 连线
      *    + socket_send      发送数据包
      *    + socket_recv      接收数据包
      *    + socket_send_pack   打包发送数据包
      *    + socket_recv_pack   接收打包的数据包
      *    + close_socket     关闭套接字
      * */

#ifndef LINUX_SOCKET_H_
#define LINUX_SOCKET_H_


#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
      /*---------------------------------------------------------------------------*/
      /*---------------------------------------------------------------------------*/

      /* 定义 SOCKET 变量 */
      typedef int SOCKET;

#ifndef boolean
#define boolean
      /* 定义布尔型变量 */
#endif

#ifndef FALSE
#define FALSE   (0) 
#endif

#ifndef TRUE
#define TRUE (!FALSE)
#endif

      /* 定义布尔型变量 */
#ifndef BOOLLEAN
#define BOOLLEAN
      typedef int BOOL;
#endif

      /* 定义NULL字符 */
#ifndef NULL
#define NULL 0
#endif

      /*---------------------------------------------------------------------------*/
      /* 函数功能: 格式化 socket 错误信息
      * 参数说明: 无
      * 返回说明: 返回错误描述信息文本
      * 注意事项: 
      * 使用举例:

      * */
      char * socket_format_err()
      {
          return (char *)hstrerror(h_errno);
      }

      /*---------------------------------------------------------------------------*/
      /* 函数功能: 自定义格式化 socket 错误信息
      * 参数说明: pszErr 要在系统错误信息描述前添加的自己的文字
      * 返回说明: 无
      * 注意事项: 
      * 使用举例: socket_format_herr("Warning");
      *     输出 Warning: xxxx.....

      * */
      void socket_format_herr(const char * pszErr)
      {
          herror(pszErr);
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 创建 Socket
      * 参数说明: nTimeR   接收超时(秒),0表示不受限
      *       nTimeS   发送超时(秒),0表示不受限
      * 返回说明: 返回-1表示失败。否则返回被创建的socket
      * 注意事项: 函数内使用了端口重新绑定
      * 使用举例: 1. socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 10, 10) TCP
      *       2. socket_create(AF_INET, SOCK_DGRAM, IPPROTO_IP, 0, 0)     UDP 
      * */
      SOCKET socket_create(const int nAf, const int nType, const int nProtocol, const int nTimeS, const int nTimeR)
      {
          static SOCKET m_sock;

#ifndef WIN32
          struct timeval tv;
#endif

          m_sock = -1;
          m_sock = socket(nAf, nType, nProtocol);
          if (m_sock == -1) return -1;


#ifdef WIN32

          nTimeS *= 1000;
          nTimeR *= 1000;
          /* 发送时限 */
          if (setsockopt(m_sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&nTimeS, sizeof(int)) < 0)
              return -1;
          /* 接收时限 */
          if (setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&nTimeR, sizeof(int)) < 0)
              return -1;
#else
          tv.tv_usec = 0;
          tv.tv_sec = nTimeS;
          /* 发送时限 */
          if (setsockopt(m_sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
              return -1;
          tv.tv_sec = nTimeR;
          /* 接收时限 */
          if (setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
              return -1;
#endif

          return m_sock;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 连接 Socket
      * 参数说明: socket   套接字
      *        pcszIp   IP
      *        nPort    端口
      * 返回说明: 返回 false 表示失败。
      * 注意事项: 
      * 使用举例: 1. SOCKET socke;
      *      if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0)) == -1) {
      *  
      *       return false;
      *      }
      *      if (socket_connect(socke, "192.168.12.111", 9000) == false) return false;
      * */
      bool socket_connect(const SOCKET socket, const char * pcszIp, const unsigned nPort)
      {
          struct sockaddr_in svraddr;

          svraddr.sin_family = AF_INET;
          svraddr.sin_addr.s_addr = inet_addr(pcszIp);
          svraddr.sin_port = htons(nPort);
          if (connect(socket, (struct sockaddr *)&svraddr, sizeof(svraddr)) == -1) return false;
          return true;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能:邦定本地端口到描述符
      * 参数说明:nPort 需要邦定的本地端口
      * 返回说明:返回 false 表示失败。
      * 注意事项:
      * 使用举例:
      *     if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0)) == -1) {
      *  
      *       return false;
      *      }
      *     if (socket_bind(socke, 9000) == false) return false;
      * */
      bool socket_bind(const SOCKET socket, const unsigned nPort)
      {
          int nOpt = 1;
          struct sockaddr_in svraddr;

          svraddr.sin_family = AF_INET;
          svraddr.sin_addr.s_addr = INADDR_ANY;
          svraddr.sin_port = htons(nPort);

          if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char*)&nOpt, sizeof(nOpt)) < 0) return false;
          if (bind(socket, (struct sockaddr*)&svraddr, sizeof(svraddr)) == -1) return false;

          return true;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能:监听本地端口描述符
      * 参数说明:nBacklog 设置请求排队的最大长度,常用量为 5
      * 返回说明:返回 false 表示失败。
      * 注意事项:
      * 使用举例:
      *     if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0)) == -1) {
      *  
      *       return false;
      *      }
      *     if (socket_bind(socke, 9000) == false) return false;
      *     if (socket_listen(socke, 5) == false) return false;
      * */
      bool socket_listen(const SOCKET socket, const int nBacklog)
      {
          if (listen(socket, nBacklog) == -1) return false;
          return true;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能:接受 Socket 连线 
      * 参数说明:pszCliIp [OUT] 对方IP地址
      * 返回说明:成功返回客户 socket 描述符,否则返回 -1。
      * 注意事项:
      * 使用举例:
      *     SOCKET m_sock;
      *     char szCliIP[16];
      *     if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0)) == -1) {
      *  
      *       return false;
      *      }
      *     if (socket_bind(socke, 9000) == false) return false;
      *     if (socket_listen(socke, 5) == false) return false;
      *     memset(szCliIP, 0, 16);
      *     m_sock = socket_accept(socke, szCliIP);
      * */
      SOCKET socket_accept(const SOCKET socket, char * pszCliIp)
      {
          static SOCKET m_sock;
          struct sockaddr_in cliaddr;

          socklen_t addrlen = sizeof(cliaddr);
          m_sock = accept(socket, (struct sockaddr*)&cliaddr, &addrlen);
          if (m_sock == -1) return -1;

          if (pszCliIp != NULL) sprintf(pszCliIp, "%s", inet_ntoa(cliaddr.sin_addr));
          return m_sock;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 发送 Socket 包
      * 参数说明: socket   套接字
      *        pszBuff   待发送缓冲区
      *        nLen   待发送包长度
      * 返回说明: 返回-1表示失败。否则返回实际已发送的字节数
      * 注意事项: nFlags一般取 0
      * 使用举例: 1. SOCKET socke;
      *      char szBuff[512];
      *      memset(szBuff, 0, 512);
      *
      *      if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0)) == -1) {
      *  
      *       return false;
      *      }
      *      if (socket_connect(socke, "192.168.12.111", 9000) == false) return false;
      *      // 拷贝待发送字节到缓冲区
      *      socket_send(socke, szBuff, strlen(szBuff), 0);
      * */
      int socket_send(const SOCKET socket, const char * pszBuff, const int nLen, const int nFlags)
      {
          int nBytes = 0;
          int nCount = 0;

          while (nCount < nLen) {

              nBytes = send(socket, pszBuff + nCount, nLen - nCount, nFlags);
              if (nBytes <= 0) {

                  return -1;
              }
              nCount += nBytes;
          }

          return nCount;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 接收 Socket 包
      * 参数说明: socket   套接字
      *     pszBuff   待接收缓冲区
      *     nLen   待接收包长度
      * 返回说明: 返回-1表示失败。否则返回实际接收的字节数
      * 注意事项: nFlags一般取 0
      * 使用举例: 1. SOCKET socke;
      *      char szBuff[512];
      *      memset(szBuff, 0, 512);

      *      if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 10)) == -1) {
      *  
      *       return false;
      *      }
      *      if (socket_connect(socke, "192.168.12.111", 9000) == false) return false;
      *      // 拷贝待发送字节到缓冲区
      *      socket_send(socke, szBuff, strlen(szBuff), 0);
      *      socket_recv(socke, szBuff, strlen(szBuff), 0);
      * */
      int socket_recv(const SOCKET socket, char * pszBuff, const int nLen, const int nFlags)
      {
          return recv(socket, pszBuff, nLen, nFlags);
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 打包发送 Socket 包
      * 参数说明: socket   套接字
      *     pszBuff   待发送缓冲区
      *     nLength_all 待发送包总长度
      *     unPack_Size 一次发送的字节数
      * 返回说明: 返回-1表示失败。否则返回实际发送的字节数
      * 注意事项: 
      * 使用举例: 1. SOCKET socke;
      *      char szBuff[512];
      *      memset(szBuff, 0, 512);

      *      if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 10)) == -1) {
      *  
      *       return false;
      *      }
      *      if (socket_connect(socke, "192.168.12.111", 9000) == false) return false;
      *      // 拷贝待发送字节到缓冲区
      *      socket_send_pack(socke, szBuff, strlen(szBuff), 512);
      * */
      unsigned long socket_send_pack(const SOCKET socket, const char * pszBuff, const unsigned long nLength_all, const unsigned unPack_Size)
      {
          int nLength = 0;
          long nAddLengths = 0;

          while(nAddLengths < nLength_all) {

              if ((nLength = socket_send(socket, pszBuff + nAddLengths, unPack_Size, 0)) < 0) {

                  break;
              }
              nAddLengths += nLength;
          }
          return nAddLengths;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 接收打包 Socket 包
      * 参数说明: socket   套接字
      *     pszBuff   待接收缓冲区
      *     nLength_all 待接收包总长度
      *     unPack_Size 一次接收的字节数
      * 返回说明: 返回-1表示失败。否则返回实际接收的字节数
      * 注意事项: 
      * 使用举例: 1. SOCKET socke;
      *      char szBuff[512];
      *      memset(szBuff, 0, 512);

      *      if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 10)) == -1) {
      *  
      *       return false;
      *      }
      *      if (socket_connect(socke, "192.168.12.111", 9000) == false) return false;
      *      // 拷贝待发送字节到缓冲区
      *      socket_send_pack(socke, szBuff, strlen(szBuff), 512);
      *      socket_recv_pack(socke, szBuff, 2048, 512);
      * */
      int socket_recv_pack(const SOCKET socket, char *pszBuff, const unsigned long nLength_all, const unsigned unPack_Size)
      {

          int nLength = 0;
          unsigned long nAddLengths;
          nAddLengths = 0;

          while (nAddLengths < nLength_all) {

              if ((nLength = socket_recv(socket, pszBuff + nAddLengths, unPack_Size, 0)) < 0) {

                  break;
              }
              nAddLengths += nLength;
          }
          return nAddLengths;
      }
      /*---------------------------------------------------------------------------*/
      /* 函数功能: 关闭 Socket
      * 参数说明: socket   套接字
      * 返回说明: 返回false表示失败。否则返回实际接收的字节数
      * 注意事项: 
      * 使用举例: 1. SOCKET socke;
      *      char szBuff[512];
      *      memset(szBuff, 0, 512);
      * 
      *      if ((socke = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 10)) == -1) {
      *  
      *       return false;
      *      }
      *      if (socket_connect(socke, "192.168.12.111", 9000) == false) return false;
      *      // 拷贝待发送字节到缓冲区
      *      socket_send_pack(socke, szBuff, strlen(szBuff), 512);
      *      socket_recv_pack(socke, szBuff, 2048, 512);
      *      socket_close(socke);
      * */
      bool socket_close(const SOCKET socket)
      {
          return close(socket) == 0 ? true : false;
      }
      /*---------------------------------------------------------------------------*/
      /*---------------------------------------------------------------------------*/

#endif /*LINUX_SOCKET_H_*/

main.cpp

int main(int argc, char* argv[])
{
    SOCKET servSocket = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0);
    if(servSocket == -1)
    {
        return -1;
    }
    
    if(socket_bind(servSocket, 9000) == false) 
    {
        return -1;
    }
    
    if(socket_listen(servSocket, 5) == false)
    {
        return -1;
    }   

    system("./test.sh");    
    getchar();
    return 0;
}

test.sh

#! /bin/sh
ping 10.36.65.80

2、测试

    编译运行
    [root@localhost socket]# g++ -o main main.cpp
    [root@localhost socket]# ./main
    
    查看监听端口转移
    [root@localhost test2]# netstat -anp|grep 9000  
    tcp        0      0 0.0.0.0:9000                0.0.0.0:*                   LISTEN      17826/main          
    [root@localhost test2]# kill -9 17826
    [root@localhost test2]# netstat -anp|grep 9000  
    tcp        0      0 0.0.0.0:9000                0.0.0.0:*                   LISTEN      17827/sh            
    [root@localhost test2]# kill -9 17827
    [root@localhost test2]# netstat -anp|grep 9000  
    tcp        0      0 0.0.0.0:9000                0.0.0.0:*                   LISTEN      17828/ping          
    [root@localhost test2]# kill -9 17828
    [root@localhost test2]# netstat -anp|grep 9000  

3、解决办法,如下:

#include "linux_socket.h"
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char* argv[])
{
    SOCKET servSocket = socket_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0);
    if(servSocket == -1)
    {
        return -1;
    }
    
    if(socket_bind(servSocket, 9000) == false) 
    {
        return -1;
    }
    
    if(socket_listen(servSocket, 5) == false)
    {
        return -1;
    }   

    printf("servSocket[%d]\n",servSocket);
    int flags = fcntl(servSocket,F_GETFL,0);
    fcntl(servSocket,F_SETFD,flags|FD_CLOEXEC); 
    printf("exe[%d]\n",FD_CLOEXEC); 

    
    system("./test.sh");
    getchar();
    return 0;
}
理解nohup
1、在终端(包括仿真终端)上启动进程,当用户退出或者关闭终端,这个终端上启动的进程都会收到SIGHUP信号。
    而对于这个信号,默认的处理方式是进程退出。表现为终端上的进程都被关闭。
2、如何解决这个问题?
    使用nohup命令启动进程,让这个进程忽略SIGHUP信号,从而不被关闭。
进程在后台运行
Linux 技巧:让进程在后台可靠运行的几种方法
1、考虑下面的需求,通过ssh连接10.65.40.186,然后ping 10.65.200.168
    Ctrl+c 结束ping程序。
2、Ctrl+c 会终止ping程序,现在不想终止ping程序,怎么办?
    使用 ping 10.65.200.168 & 在后台运行。
3、ping 10.65.200.168 & 在后台运行,但是结果还是打印在标准输出,影响在ssh的输入,怎么办?
    把程序的输出,重定向到设备黑洞,如下:
    ping 10.65.200.168 >/dev/null &
    注意:设备黑洞是/dev/null,而不是nul,通过 ll |grep null 查看,这个文件大小不会变化 
4、上面的方式存在一个问题:通过ssh连接,然后执行 ping 10.65.200.168 >/dev/null &
    也就是说,ping程序是ssh连接的子进程,因为ping是通过ssh连接来执行的。
    当ssh连接断开,ping程序也会自动终止。
5、具体如下:
    建立一个ssh连接A,执行ping,如下:
    [root@localhost ~]# ping 10.65.200.168 >/dev/null &
    [1] 10995
    再建立一个ssh连接B,查看进程关系,10977是ssh连接A,10995是ping程序,如下:
    [root@localhost dev]# ps -ef|grep ping
    root     10995 10977  0 17:11 pts/0    00:00:00 ping 10.65.200.168
    root     12035  4157  0 17:15 pts/2    00:00:00 grep ping

    [root@localhost dev]# pstree -p
    init(1)─┬─atd(1040)
            ├─auditd(956)───{auditd}(957)
            ├─crond(1029)
            ├─dbus-daemon(989)
            ├─login(1071)───bash(1083)─┬─tailf(2299)
            │                          └─tailf(3032)
            ├─mingetty(1073)
            ├─rsyslogd(972)─┬─{rsyslogd}(973)
            │               ├─{rsyslogd}(974)
            │               └─{rsyslogd}(975)
            ├─sshd(1021)─┬─sshd(4155)─┬─bash(4157)───pstree(12993)
            │            │            └─bash(10977)───ping(10995)
            │            ├─sshd(7687)───bash(7691)
            │            ├─sshd(10495)───bash(10501)─┬─ibpctrl(15286)───tail(15407)
            │            │                           ├─tailf(29764)
            │            │                           └─tailf(30139)
            │            └─sshd(11553)───sftp-server(11559)
            ├─udevd(143)─┬─udevd(741)
            │            └─udevd(1075)
            └─watch_dog(1057)
    进程关系如下:init(1)─sshd(1021)─sshd(4155)─bash(10977)───ping(10995)
    特别注意:sshd下面是bash,然后才是ping
6、也就是说,关闭10977,10995也会终止。怎么办?
7、使用小括号,让ping程序成为init的子进程,而不是当前终端的子进程。如下:
    (ping 10.65.200.168 >/dev/null &)
    查看进程的关系,如下:
    [root@localhost dev]# ps -ef|grep ping
    root     15484     1  0 17:26 pts/0    00:00:00 ping 10.65.200.168
    root     15508  4157  0 17:26 pts/2    00:00:00 grep ping
    通过pstree,也可以看到15484直接挂在init下面
    通过这种方式,关闭终端连接A,也不会影响到ping程序。
8、上面情况的原理是:当用户注销或者网络断开,终端收到HUP(hangup)信号,从而关闭所有子进程。
    使用小括号,相当于让ping程序不是终端的子进程,而是init的子进程,从而不受终端的影响。
    有没有其他的办法呢?
9、使用nohup,让提交的ping命令忽略 hangup 信号,如下:
    在终端A,如下:
    [root@localhost ~]# nohup ping 10.65.200.168 >/dev/null &
    [1] 18524
    [root@localhost ~]# ps -ef |grep ping
    root     18524 18506  0 17:36 pts/0    00:00:00 ping 10.65.200.168
    root     18543 18506  0 17:36 pts/0    00:00:00 grep ping
    
    在终端B,如下:
    [root@localhost dev]# ps -ef|grep ping
    root     18524 18506  0 17:36 pts/0    00:00:00 ping 10.65.200.168
    root     19013  4157  0 17:37 pts/2    00:00:00 grep ping
    关闭终端A,如下:
    [root@localhost dev]# ps -ef|grep ping
    root     18524     1  0 17:36 ?        00:00:00 ping 10.65.200.168
    root     19018  4157  0 17:37 pts/2    00:00:00 grep ping
10、使用nohup,特别注意:
    a、关闭终端A,ping的父进程变为init,也就是说,init会收留没有父进程的孤儿。
    b、如果不输出到设备黑洞,nohup ping 10.65.200.168 & 会输出到当前目录的nohup.out文件(没有的话会新建)
11、nohup是忽略hangup信号的影响,另一种思路,让ping程序不要成为终端的子进程,这和括号的本质一样。
    使用setsid,如下:
    setsid ping 10.65.200.168 >/dev/null &
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.