1、进程的概念

一、进程的创建和调度

进程创建

普通函数调用完成后,最多返回(return)一次,但fork/vfork会返回二次,一次返回给父进程,一次返回给子进程
父进程的返回值为子进程的进程ID,子进程的返回值为0
1.pid_t fork(void)
父子进程共享代码段,fork之后子进程获得父进程数据空间、堆和栈的副本,然后各自独立操作自己的数据,但他们共享文件描述符

2.pid_t vfork(void)
父子进程共享资源不分家;子进程比父进程先运行,子进程结束后再运行父进程,像普通的函数调用一样

Linux操作系统是面向多用户的.在同一时间可以有许多用户向操作系统发出各种命令.那么操作系统是怎么实现多用户的环境呢?
在现代的操作系统里面,都有程序和进程的概念.那么什么是程序,什么是进程呢?
通俗的讲程序是一个包含可以执行代码的文件,是一个静态的文件.而进程是一个开始执行但是还没有结束的程序的实例.就是可执行文件的具体实现.
一个程序可能有许多进程,而每一个进程又可以有许多子进程.依次循环下去,而产生子孙进程.
当程序被系统调用到内存以后,系统会给程序分配一定的资源(内存,设备等等)然后进行一系列的复杂操作,使程序变成进程以供系统调用.在系统里面只

相关概念:
  • 最基础的计算机动作被称为指令(instruction)。
  • 程序(program),就是一系列指令的所构成的集合。通过程序,我们可以让计算机完成复杂的操作。程序大多数时候被存储为可执行的文件。

进程同步

1.pid_t wait(int *statloc)
由父进程调用,阻塞式等待任一子进程结束

2.pid_t waitpid(pid_t pid, int *statloc, int options)
可以选择等待指定子进程结束,可设置成非阻塞式等待

有进程没有程序,为了区分各个不同的进程,系统给每一个进程分配了一个ID(就象我们的身份证)以便识别.
为了充分的利用资源,系统还对进程区分了不同的状态.将进程分为新建,运行,阻塞,就绪和完成五个状态.
新建表示进程正在被创建,运行是进程正在运行,阻塞是进程正在等待某一个事件发生,就绪是表示系统正在等待CPU来执行命令,而完成表示进程已经结束了系统正在回收资源.
关于进程五个状态的详细解说我们可以看《操作系统》上面有详细的解说。

1.进程

进程(process)是操作系统的概念,为程序的一次执行在操作系统中或内存中的映像,同时伴随着资源的分配和释放。并具有以下特征的活动单元。

  • 一组执行的指令序列
  • 一个当前状态
  • 相关的系统资源集合

提示:
进程是程序的一个具体实现,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,进程是执行程序的过程,同一个程序可以执行多次,每次都可以在内存中开辟独立的空间来装载,从而产生多个进程。不同的进程还可以拥有各自独立的IO接口。操作系统的一个重要功能就是为进程提供方便,比如说为进程分配内存空间,管理进程的相关信息等等。

进程和程序的区别:
程序是态的,它是一些保存在磁盘上得指令的有序集合,没有任何执行的概念。
进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡。

调用外部程序

1.exec函数家庭
exec函数族不创建新进程,只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆和栈段
特别注意的是exec执行成功时不返回(就是不再执行exec下面的语句),在载入的程序执行完后就退出了

2.int system(char *cmdstring) 系统调用
system适合拿来调用系统内置的命令或shell脚本

2、进程的标志

2.进程控制块

进程在操作系统中都有一个户口,用于表示这个进程。这个户口操作系统被称为进程控制块PCB(process
control block),在Linux中具体实现是
task_struct数据结构,它记录了一下几个类型的信息:

  • 进程描述信息:
    进程标识符用于唯一的标识一个进程(pid,ppid)。
  • 进程控制信息:
    进程当前状态 //如这个进程处于可执行状态,休眠,挂起等。
    进程优先级
    程序开始地址
    各种计时信息
    通信信息
  • 资源信息:
    占用内存大小及管理用数据结构指针
    交换区相关信息
    I/O设备号、缓冲、设备相关的数结构
    文件系统相关指针
    资源的限制和权限
  • 现场保护信息(cpu进行进程切换时):
    寄存器
    PC
    程序状态字PSW
    栈指针

对于操作系统来说PCB即找到整个过程。

进程优先级

1.int nice(int incr)
主动降低使用cpu的频率

2.int getpriority(int which, id_t who)
获取nice值

3.int setpriority(int which, id_t who, int value);
可以为进程、进程组和特定用户的所有进程设置优先级

上面我们知道了进程都有一个ID,那么我们怎么得到进程的ID呢?系统调用getpid可以得到进程的ID,而getppid可以得到父进程(创建调用该函数进程的进程)的ID.

3.进程主要管理执行所需的资源

执行单元由线程管理。

进程终止

1.正常终止

  • 从main返回:return 0,关闭io流等资源文件
  • exit:同return 0
  • _exit:仅将自身设置成不可运行,由父进程调用wait/waitpid进行资源回收

2.异常终止

  • abort:因特殊情况主动终止
  • 由一个信号终止:被其它程序kill或运行期间产生错误(越界内存访问/除零等)被系统中止
#include <unistd> 
pid_t getpid(void); 
            pid_t getppid(void);

4.进程id

inux内核通过唯一的进程标识符PID来标识每个进程。PID存放进程描述符的pid字段中,新创建的PID通常是前一个进程的PID加1,不过PID的值有上限。当系统启动后,内核通常作为一个进程的代表。一个指向task_struct的宏current用来记录正在运行的进程。current经常作为进程描述符结构指针的形式出现在内核代码中,例如,current->pid表示处理器正在执行进程的PID。

例子

1.wait/waitpid/fork基本用法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(){
    pid_t pid;
    if((pid=fork()) <0){
        perror("fork error");
        return -1;
    }else if(pid==0){
        if(system("ls -l")<0){
            puts("system error");
            _exit(-1);
        }
        _exit(0);
    }

    if(waitpid(pid,NULL,0) != pid)
        puts("wait error");

    if((pid=fork()) <0){
        perror("fork error");
        return -1;
    }else if(pid==0){
        execlp("date","date",(char *)0);
        puts("if execlp goes wrong,you will see me!");
        _exit(-1);
    }

    if(wait(NULL)<0)
        puts("wait error");

    return 0;
}

2.退出状态

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

void pr_exit(int status){
    if(WIFEXITED(status))
        printf("normal exit, exit status=%dn",WEXITSTATUS(status));
    else if(WIFSIGNALED(status))
        printf("abnormal exit, signal number=%dn",WTERMSIG(status));
    else if(WIFSTOPPED(status))
        printf("child stoped, signal number=%dn",WSTOPSIG(status));
    else
        printf("unknown exitn");
}

int main(){
    pid_t pid;
    int status;
//_exit
    if((pid=fork())<0){
        perror("fork error");
        return -1;
    }else if(pid==0)
        _exit(7);

    if(wait(&status) != pid){
        perror("wait error");
        return -1;
    }
    pr_exit(status);
//abort
    if((pid=fork())<0){
        perror("fork error");
        return -1;
    }else if(pid==0)
        abort();

    if(wait(&status) != pid){
        perror("wait error");
        return -1;
    }
    pr_exit(status);
//divide by 0   
    if((pid=fork())<0){
        perror("fork error");
        return -1;
    }else if(pid==0)
        status /=0;

    if(wait(&status) != pid){
        perror("wait error");
        return -1;
    }
    pr_exit(status);

    return 0;
}

进程是为程序服务的,而程序是为了用户服务的.系统为了找到进程的用户名,还为进程和用户建立联系.这个用户称为进程的所有者.相应的每一个用户也有一个用户ID.通过系统调用getuid可以得到进程的所有者的ID.由于进程要用到一些资源,而Linux对系统资源是进行保护的,为了获取一定资源进程还有一个有效用户ID.这个ID和系统的资源使用有关,涉及到进程的权限.
通过系统调用geteuid我们可以得到进程的有效用户ID.
和用户ID相对应进程还有一个组ID和有效组ID系统调用getgid和getegid可以分别得到组ID和有效组ID。

使用函数取得相关的进程识别码
  • 手册文件 man getpid / man getuid / man getgid

函数头文件及函数原型
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void) ; //获取目前调用进程的进程ID
pid_t getppid(void) ; //获取目前调用进程的父进程ID
uid_t getuid(void) ; //获取目前调用进程的实际用户ID
gid_t getgid(void) ; //获取目前调用进程的实际组ID

函数参数:
函数说明及返回值:许多程序利用取到的id来建立临时文件,
以避免临时文件相同带来的问题。

#include <unistd> 
#include <sys/types.h> 
uid_t getuid(void); 
uid_t geteuid(void); 
gid_t getgid(void); 
            git_t getegid(void);

5.使用fork()函数复制创建新进程

  • 手册文件 man fork

函数头文件及函数原型
#include <unistd.h>
pid_t fork(void);

函数参数:
函数说明及返回值:在Linux中创建一个新进程的唯一方法是使用fork()函数。fork()函数是Linux中一个非常重要的函数,用于从已存在的进程中创建一个新进程。新进程称为子进程,而原进程称为父进程。使用fork()函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间,包括进程的上下文、代码段、进程堆栈、内存信息、打开的文件描述符、符号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、信号处理方式和控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。
实际是在父进程中执行fork()函数时,父进程会复制一个子进程,而且父子进程的代码从fork()函数的返回开始分别在两个地址空间中同时运行,从而使两个进程分别获得所属fork()函数的返回值,其中在父进程中的返回值是子进程的进程号,而在子进程中返回0,若出错返回-1。

有时候我们还会对用户的其他信息感兴趣(登录名等等),这个时候我们可以调用getpwui

提示:

父进程子进程间的前后顺序,要看系统进程调度策略。同时可以看出,使用fork()函数的代价是很大的,它复制了父进程中的代码段、数据段和堆栈段里的大部分内容,使得fork()函数的系统开销比较大,而且执行速度也不是很快。为了加快fork()的执行速度,很多UNIX系统设计者创建了vfork()。vfork()也能创建新的进程,但它不产生父进程的副本。它是通过允许父子进程可访问相同物理内存,从而伪装了对进程地址空间的真实复制,当子进程需要改变内存中的数据时才复制父进程。这既是著名的“写操作时复制”(copy-on-write)技术。现在大部分嵌入式Linux系统的fork()函数调用已经采用vfork()函数的实现方式,例如uCLinux所有的多进程管理都通过vfork()来实现。

Linux下fork函数及pthread函数的总结

fork()后一定exec()替换

d来得到。

6.使用exec()函数族替换成为新进程

  • 手册文件 man exec / man execve

函数头文件,函数原型及参数说明

      #include <unistd.h>
      extern char **environ;
      int execl(const char *path, const char *arg, ...);
      int execv(const char *path, char *const argv[]);
      int execle(const char *path, const char *arg, ..., char *const envp[]);
      int execlp(const char *file, const char *arg, ...);
      int execvp(const char *file, char *const argv[]);
      int execve(const char *filename, char *const argv[], char *const envp[]);
      int execvpe(const char *file, char *const argv[],char *const envp[]);

函数参数:

第一个参数为查找方式。前面3个函数的查找方式都是完整的文件目录路径,
而后4个函数(也就是带p的函数)可以只给出文件名,系统就会自动安装环境变量"$PATH"所指定的路径进行查找。
第二个参数为传递方式。exec函数族的参数传递有两种方式:一种是逐个列举方式,
而另一种则是将所有参数整体构造指针数组传递。在这里是以函数名的第5个字母来区别的,
- 带"**l**"(list)的表示逐个列举参数的方式,
其语法为const char *arg;
- 带“**v**”(vetor)的表示将所有参数整体构造指针数组传递,
其语法为char *const argv[]。
参数实际上就是用户在使用这个可执行文件时所需要的全部命令选项字符串(包括该可执行程序命令本身)。
要注意的是,这些参数必须以NULL结束。
第三个参数为环境变量。exec函数族可以默认系统的环境变量,也可以传入指定的环境变量。
这里以“e”(environment)结尾的两个函数execle()和execve()
就可以在envp[]中指定当前进程所使用的环境变量。

函数返回值:如果执行成功则函数不会返回, 执行失败则直接返回-1。

struct passwd { 
char *pw_name; /* 登录名称 */ 
char *pw_passwd; /* 登录口令 */ 
uid_t pw_uid; /* 用户ID */ 
gid_t pw_gid; /* 用户组ID */ 
char *pw_gecos; /* 用户的真名 */ 
char *pw_dir; /* 用户的目录 */ 
char *pw_shell; /* 用户的SHELL */ 
}; 
#include <pwd.h> 
#include <sys/types.h> 
            struct passwd *getpwuid(uid_t uid);
提示:

事实上,这6个函数中真正的系统调用只有execve(),其他5个都是库函数,它们最终都会调用execve()这个系统调用。在使用exec函数族是,一定要加上错误判断语句。

下面我们学习一个实例来实践一下上面我们所学习的几个函数:

7.使用:wait()/waitpid() 暂时停止目前进程的执行, 直到有信号来到或子进程结束。

  • 手册文件 man wait

函数头文件及函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

函数说明、参数及返回值:
wait()函数用于使父进程(也就是调用wait()的进程)阻塞,直到一个子进程结束或该进程接收到一个指定的信号为止。如果该父进程没有子进程或它的子进程已经结束,则wait()就会立即返回。waitpid()的作用和wait()一样,但它并不一定要等待一个终止的子进程,它还有若干选项,如可提供一个非阻塞版本的wait()功能,也能支持作用控制。实际上,wait()函数只是waitpid()函数的一个特例,在Linux内部实现wait()函数时直接调用的就是waitpid()函数。

status 是一个整型指针,是该子程序退出的状态。```
如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值。子进程的结束状态值会由不为空的参数status 返回, 而子进程的进程识别码也会一快返回。如果不在意结束状态值, 则参数status 可以设成NULL。
```c
参数pid 为欲等待的子进程识别码, 其他数值意义如下:
1、pid<-1 等待进程组识别码为pid 绝对值的任何子进程。
2、pid=-1 等待任何子进程, 相当于wait()。
3、pid=0 等待进程组识别码与目前进程相同的任何子进程。
4、pid>0 只等待任何子进程识别码等于pid 的子进程,不管是否有其他子进程结束,
         只要指定子进程未结束,一直等。```

参数option 可以为0 或下面的OR 组合:
```c
WNOHANG:  如果没有任何已经结束的子进程则马上返回, 不予以等待。
WUNTRACED:如果子进程进入暂停执行情况则马上返回, 但结束状态不予以理会. 
           子进程的结束状态返回后存于status。
0:        阻塞父进程,等待子进程退出。```

底下有几个宏可判别结束情况:
```c
WIFEXITED(status):  用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。
WEXITSTATUS(status):取得子进程exit()返回的结束代码, 
                     一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。
                     可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,
                     WEXITSTATUS(status)就会返回5;如果子进程调用exit(7)退出,
                     WEXITSTATUS(status)就会返回7。如果进程不是正常退出,
                     也就是说,WIFEXITED返回0,这个值就毫无意义了。
WIFSIGNALED(status):如果子进程是因为信号而结束则此宏值为真。
WTERMSIG(status):   取得子进程因信号而中止的信号代码, 
                     一般会先用WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status): 如果子进程处于暂停执行情况则此宏值为真. 
                     一般只有使用WUNTRACED时才会有此情况。
WSTOPSIG(status):   取得引发子进程暂停的信号代码, 
                    一般会先用WIFSTOPPED 来判断后才使用此宏。```

**如果执行成功则返回已结束的子进程识别码(PID), 如果有错误发生则返回-1,使用选项WNOHANG且没有子进程退出则返回0。
当status为NULL时,只要有子进程退出,wait()退出阻塞(且返回值为退出的子进程的进程号),否则一直阻塞直到有子进程退出。当调用wait()函数的进程没有子进程时,返回-1。**
######提示:
如果参数status的值不是NULL,wait()就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是非正常结束的(一个进程也可以被其他进程用信号结束),以及正常结束时返回值,或被哪一个信号结束的等信息。


####8.进程的执行状态:新建 就绪 运行 阻塞 退出(睡眠)

![进程状态模型](http://upload-images.jianshu.io/upload_images/3963687-14b236efa2b7e931.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
####9.僵尸进程
进程运行结束,父进程尚未使用wait()函数族(如使用waitpid()函数)等系统调用来“收尸”,即等待父进程销毁它。处于该状态下的进程“实体”已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能调度,仅仅在进程列表保留一个位置,记载该进程的退出状态等信息供其他进程收集,该子进程将会持续处于僵尸状态。僵尸进程将会导致资源浪费。
####10.孤儿进程
父进程在子进程之前退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作,最终还是会死掉。

##二、信号
信号signal处理是Linux程序的一个特色,用信号处理来模拟操作系统的中断功能,对于系统程序员来说是最好的一个选择了。
**信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称软中断**。从它的命名可以看出,它的实质和使用很像中断,所有,信号可以说是进程控制的一部分。
信号只是异步通知某进程发生了什么事件,并不给该进程传递任何数据。

####1.收到信号的进程对各种信号有不同的处理方法。
处理方法可以分为三类:

    第一种是类似中断的处理程序,对于需要处理的信号,进程可以自定义信号处理函数。
    第二种是忽略某个信号,收到信号但对该信号不做任何处理,就像从未发生过一样。
    第三种是对该信号的处理保留系统的默认值,对大部分的信号的缺省操作是使得进程终止。

常用信号及其处理:
SIGINT/SIGQUIT/SIGALRM/SIGCHLD
详情请见:[ Linux下C语言开发(信号signal处理机制)](http://blog.csdn.net/thanksgining/article/details/41824475)

####2.使用signal()函数注册设定某个信号的处理方法
- 手册文件 man signal 
>函数头文件及函数原型
```c
 #include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
或
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);```

*函数说明及参数:*
```c
表达式一:
int (*p)();这是一个函数指针, p所指向的函数是一个不带任何参数, 且返回值为int的函数。
int (*fun())();这个式子与上面式子的区别在于用fun()代替了p,而fun()是一个函数,
    所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,
    此函数指针(暨上面的p)所指向的函数是一个不带任何参数,且返回值为int的某一函数。
void (*signal(int signo, void (*handler)(int)))(int);
    就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数),
    而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,
    并且返回值为void的一个函数。
    在写信号处理函数时对于信号处理的函数也是void sig_fun(int signo);
    恰好与上面signal()函数所返回的函数指针所指向的函数是一样的
    void ( *signal() )( int );
表达式二:
在调用中,signal()会依参数signum 指定的信号编号来设置该信号的处理函数。
当指定的信号到达时就会跳转到参数handler 指定的函数执行。
如果参数handler不是函数指针, 则必须是下列两个常数之一:
1、SIG_IGN 忽略参数signum 指定的信号。
2、SIG_DFL 将参数signum 指定的信号重设为核心预设的默认信号处理方式。```

*函数返回值:* 返系统调用signal返回值的指定信号signum前一次的处理例程,暨信号处理函数指针, 如果有错误则返回SIG_ERR(-1)。
######提示:
在信号发生跳转到自定的 handler 处理函数执行后, 系统会自动将此处理函数换回原来系统预设的处理方式, 如果要改变此操作请改用sigaction()。

####3.使用sigaction()检查或修改与指定信号相关联的处理动作(可同时两种操作)。
- 手册文件 man sigaction 
>函数头文件及函数原型
```c
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);```

*函数说明及参数:*
依参数signum指出要捕获的信号类型,暨指定设置新的信号编号来设置该信号的处理函数act,暨指定新的信号处理方式, 同时保留该信号原有的信号处理函数oldact,暨输出先前信号的处理方式(如果不为NULL的话)。

参数signum 可以指定SIGKILL 和SIGSTOP 以外的所有信号。

参数结构sigaction 定义如下:
``` c
struct sigaction
{ 
   void (*sa_handler) (int); 
   sigset_t sa_mask;
   int sa_flags; 
   void (*sa_restorer) (void);
}
1、sa_handler 此参数和signal()的参数handler 相同, 代表新的信号处理函数。
2、sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。
3、sa_restorer 此参数没有使用。
4、sa_flags用来设置信号处理的其他相关操作, 下列的数值可用:   
SA_NOCLDSTOP: 如果参数signum 为SIGCHLD, 则当子进程暂停时并不会通知父进。  
SA_RESETHAND: 当调用新的信号处理函数前, 将此信号处理方式改为系统预设的方式。   
SA_RESTART:   被信号中断的系统调用会自行重启。  
SA_NODEFER:   在处理此信号未结束前不理会此信号的再次到来。
SA_INTERRUPT: 由此信号中断的系统调用不会自动重启。
SA_SIGINFO: 提供附加信息,一指向siginfo结构的指针以及一指向进程上下文标识符的指针。

如果参数oldact 不是NULL 指针,则原来的信号处理方式会由此结构sigaction返回。```

*函数返回值:*执行成功则返回0, 如果有错误则返回-1。

######提示:
sigaction()是POSIX的信号接口,而signal()是标准C的信号接口。

发送信号方式:kill(实际是向指定的进程,发送信号)、组合键、硬件出故障。
####4.使用kill()函数向指定的进程发送一个信号

- 手册文件 man 2 kill
>函数头文件及函数原型
   ```c
   #include <sys/types.h>
   #include <signal.h>
   int kill(pid_t pid, int sig);```

*函数说明及参数:*该系统调用可以用来向任何进程或进程组发送任何信号。参数sig 指定的信号给参数pid 指定的进程。参数pid 有几种情况:
```c
1、pid>0 将信号传给进程识别码为pid 的进程。
2、pid=0 将信号传给和目前进程相同进程组的所有进程。
3、pid=-1 将信号广播传送给系统内所有的进程。
4、pid<-1 将信号传给进程组识别码为pid 绝对值的所有进程参数 sig 代表的信号。
          即该信号发送给一个组,则该错误表示组中有成员进程不能接收该信号。
5.如果参数sig为0,将不发送信号。```

*函数返回值:*执行成功则返回0, 如果有错误则返回-1。
######提示:
一个进程被允许将信号发送到进程pid时,必须拥有root权限,或者是发出的进程的UID或EUID指定接收的进程的UID或保护用户ID(savedset-user-ID)相同。

#######小提醒:
数组传参,实际传的是,数组首元素的地址和长度
typedef,实际是typerelay,重命名




##参考资料
刘老师上课资料及网上前辈资料
[Linux进程基础](http://www.cnblogs.com/vamei/archive/2012/09/20/2694466.html)
[linux 进程(一)---基本概念](http://blog.chinaunix.net/uid-26833883-id-3193588.html)
[Linux下C语言开发(多任务编程之任务、进程、线程)](http://blog.csdn.net/Thanksgining/article/details/41890293)
[Linux下的进程控制块的数据结构](http://blog.csdn.net/yaoyepeng/article/details/5368516)
[进程生命周期与进程控制块](http://www.cnblogs.com/mickole/p/3185889.html)
[进程列表相关解释](http://blog.csdn.net/fenglibing/article/details/6958745)
[UNIX/Linux-进程控制(实例入门篇) ](http://blog.csdn.net/yang_yulei/article/details/17404021)
[浅谈Linux环境下并发编程中C语言fork()函数的使用](http://www.jb51.net/article/87166.htm)
[linux中fork()函数详解(原创!!)](http://www.cnblogs.com/bastard/archive/2012/08/31/2664896.html)

(或待继续整理...)
#include <unistd.h> 
#include <pwd.h> 
#include <sys/types.h> 
#include <stdio.h> 
int main(int argc,char **argv) 
{ 
pid_t my_pid,parent_pid; 
uid_t my_uid,my_euid; 
gid_t my_gid,my_egid; 
struct passwd *my_info; 
my_pid=getpid(); 
parent_pid=getppid(); 
my_uid=getuid(); 
my_euid=geteuid(); 
my_gid=getgid(); 
my_egid=getegid(); 
my_info=getpwuid(my_uid); 
printf("Process ID:%ld ",my_pid); 
printf("Parent ID:%ld ",parent_pid); 
printf("User ID:%ld ",my_uid); 
printf("Effective User ID:%ld ",my_euid); 
printf("Group ID:%ld ",my_gid); 
printf("Effective Group ID:%ld ",my_egid): 
if(my_info) 
{ 
printf("My Login Name:%s " ,my_info->pw_name); 
printf("My Password :%s " ,my_info->pw_passwd); 
printf("My User ID :%ld ",my_info->pw_uid); 
printf("My Group ID :%ld ",my_info->pw_gid); 
printf("My Real Name:%s " ,my_info->pw_gecos); 
printf("My Home Dir :%s ", my_info->pw_dir); 
printf("My Work Shell:%s ", my_info->pw_shell); 
} 
            }

3、进程的创建

创建一个进程的系统调用很简单.我们只要调用fork函数就可以了.

#include <unistd.h> 
            pid_t fork();

当一个进程调用了fork以后,系统会创建一个子进程.这个子进程和父进程不同的地方只有他的进程ID和父进程ID,其他的都是一样.就象符进程克隆(clone)自己一样.当然创建两个一模一样的进程是没有意义的.为了区分父进程和子进程,我们必须跟踪fork的返回值.
当fork掉用失败的时候(内存不足或者是用户的最大进程数已到)fork返回-1,否则fork的返回值有重要的作用.对于父进程fork返回子进程的ID,而对于fork子进程返回0.我们就是根据这个返回值来区分父子进程的.
父进程为什么要创建子进程呢?前面我们已经说过了Linux是一个多用户操作系统,在同一时间会有许多的用户在争夺系统的资源.有时进程为了早一点完成任务就创建子进程来争夺资源.
一旦子进程被创建,父子进程一起从fork处继续执行,相互竞争系统的资源.有时候我们希望子进程继续执行,而父进程阻塞直到子进程完成任务.这个时候我们可以调用wait或者waitpid系统调用.

#include <sys/types.h> 
#include <sys/wait.h> 
pid_t wait(int *stat_loc); 
            pid_t waitpid(pid_t pid,int *stat_loc,int options);

 

wait系统调用会使父进程阻塞直到一个子进程结束或者是父进程接受到了一个信号.如果没有父进程没有子进程或者他的子进程已经结束了wait回立即返回.成功时(因一个子进程结束)wait将返回子进程的ID,否则返回-1,并设置全局变量errno.stat_loc是子进程的退出状态.子进程调用exit,_exit
或者是return来设置这个值.
为了得到这个值Linux定义了几个宏来测试这个返回值。

WIFEXITED:判断子进程退出值是非0

WEXITSTATUS:判断子进程的退出值(当子进程退出时非0).

WIFSIGNALED:子进程由于有没有获得的信号而退出.

WTERMSIG:子进程没有获得的信号号(在WIFSIGNALED为真时才有意义).

waitpid等待指定的子进程直到子进程返回.如果pid为正值则等待指定的进程(pid).如果为0则等待任何一个组ID和调用者的组ID相同的进程.为-1时等同于wait调用.小于-1时等待任何一个组ID等于pid绝对值的进程.
stat_loc和wait的意义一样. options可以决定父进程的状态.可以取两个值
WNOHANG:父进程立即返回当没有子进程存在时.
WUNTACHED:当子进程结束时waitpid返回,但是子进程的退出状态不可得到.父进程创建子进程后,子进程一般要执行不同的程序.为了调用系统程序,我们可以使用系

统调用exec族调用.exec族调用有着5个函数.

#include <unistd.h> 
int execl(const char *path,const char *arg,...); 
int execlp(const char *file,const char *arg,...); 
int execle(const char *path,const char *arg,...); 
int execv(const char *path,char *const argv[]); 
            int execvp(const char *file,char *const argv[]):

exec族调用可以执行给定程序.关于exec族调用的详细解说可以参考系统手册(man
exec

下面我们来学习一个实例.注意编译的时候要加 -lm以便连接数学函数库.

#include <unistd.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <stdio.h> 
#include <errno.h> 
#include <math.h> 
void main(void) 
{ 
pid_t child; 
int status; 
printf("This will demostrate how to get child status "); 
if((child=fork())==-1) 
{ 
printf("Fork Error :%s ",strerror(errno)); 
exit(1); 
} 
else if(child==0) 
{ 
int i; 
printf("I am the child:%ld ",getpid()); 
for(i=0;i<1000000;i++) sin(i); 
i=5; 
printf("I exit with %d ",i); 
exit(i); 
} 
while(((child=wait(&status))==-1)&(errno==EINTR)); 
if(child==-1) 
printf("Wait Error:%s ",strerror(errno)); 
else if(!status) 
printf("Child %ld terminated normally return status is zero ", 
child); 
else if(WIFEXITED(status)) 
printf("Child %ld terminated normally return status is %d ", 
child,WEXITSTATUS(status)); 
else if(WIFSIGNALED(status)) 
printf("Child %ld terminated due to signal %d znot caught ", 
child,WTERMSIG(status));  
            }

strerror函数会返回一个指定的错误号的错误信息的字符串。

4、守护进程的创建

如果你在DOS时代编写过程序,那么你也许知道在DOS下为了编写一个常驻内存的程序我们要编写多少代码了.相反如果在Linux下编写一个”常驻内存”的程序却是很容易的。我们只要几行代码就可以做到.
实际上由于Linux是多任务操作系统,我们就是不编写代码也可以把一个程序放到后台去执行的.我们只要在命令后面加上&符号SHELL就会把我们的程序放到后台去运行的.
这里我们”开发”一个后台检查邮件的程序.这个程序每个一个指定的时间回去检查我们的邮箱,如果发现我们有邮件了,会不断的报警(通过机箱上的小喇叭来发出声音).
后面有这个函数的加强版本加强版本后台进程的创建思想:
首先父进程创建一个子进程.然后子进程杀死父进程(是不是很无情?).
信号处理所有的工作由子进程来处理.

#include <unistd.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <stdio.h> 
#include <errno.h> 
#include <fcntl.h> 
#include <signal.h> 
/* Linux 的默任个人的邮箱地址是 /var/spool/mail/用户的登录名 */ 
#define MAIL "/var/spool/mail/hoyt" 
/* 睡眠10秒钟 */ 
#define SLEEP_TIME 10 
main(void) 
{ 
pid_t child; 
if((child=fork())==-1) 
{ 
printf("Fork Error:%s ",strerror(errno)); 
exit(1); 
} 
else if(child>0) 
while(1); 
if(kill(getppid(),SIGTERM)==-1) 
{ 
printf("Kill Parent Error:%s ",strerror(errno)); 
exit(1); 
}  
{ 
int mailfd; 
while(1) 
{ 
if((mailfd=open(MAIL,O_RDONLY))!=-1) 
{ 
fprintf(stderr,"%s","07"); 
close(mailfd); 
}  
sleep(SLEEP_TIME); 
} 
} 
            }

你可以在默认的路径下创建你的邮箱文件,然后测试一下这个程序.当然这个程序还有很多地方要改善的.我们后面会对这个小程序改善的,再看我的改善之前你可以尝试自己改善一下.比如让用户指定邮相的路径和睡眠时间等等.相信自己可以做到的.动手吧,勇敢的探险者.