程序是平台相关的二进制文件,只占用磁盘空间。编写完程序代码后,编译为可执行的二进制文件便可。web
进程是运行中的程序,占用 CPU、内存等系统资源。shell
经过 Shell 命令,能够在终端启动进程,例如执行 ls
命令:api
并发 concurrent:在一个时间段内,处理的请求总数。个数越多,并发越大。
并行 parallel:任意时刻可以同时处理的请求数。一般跟 CPU 内核数量相关。数组
Linux 的进程有如下 6 种状态:bash
经过 ps aux
能够查看当前机器上的进程,其中 STAT 列的第一个字符就是进程的状态:数据结构
# ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.2 190764 2140 ? Ss 2018 12:35 /usr/lib/systemd/systemd --system --deserialize 20 root 2 0.0 0.0 0 0 ? S 2018 0:00 [kthreadd] root 3 0.0 0.0 0 0 ? S 2018 1:24 [ksoftirqd/0]
kill 命令用于结束进程,语法以下:并发
kill [-s signal|-p] [-q sigval] [-a] [--] pid... kill -l [signal]
执行 kill 命令,系统会发送一个 SIGTERM 信号给对应的进程,请求进程正常关闭。SIGTERM 是有可能会被阻塞的。kill -9
命令用于强制杀死进程,系统给对应程序发送的信号是 SIGKILL,即 exit。exit 信号不会被系统阻塞。
示例:异步
kill -9 11235
每一个进程在建立的时候,内核都会为之分配一个全局惟一的进程号。svg
经过 getpid 函数能够获取当前进程的 PID。getppid 函数能够获取父进程的 PID。函数
经过 ps aux
能够查看进程的 PID,资源消耗状况,经过 ps -ef
能够查看当前进程及其父进程的 PID。经过 pstree
命令能够以树状关系查看全部进程。
函数原型:
#include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void);
除了经过 main 函数的第三个参数获取环境变量,还能够经过 environ 全局变量或 getenv() 函数来获取。
getenv 函数原型:
#include <stdlib.h> char *getenv(const char *name); char *secure_getenv(const char *name);
#include <stdio.h> #include <stdlib.h> extern char** environ; int main(int argc, char* argv[], char* env[]) { int i = 0; char* myenv = NULL; while(env[i]) { printf("env[%d] is: %s\n", i, env[i++]); } i = 0; while(environ[i]) puts(environ[i++]); myenv = getenv("PATH"); puts(myenv); return 0; }
PCB(Process Control Block,进程控制块)是每一个进程都有的数据结构,用于保存进程运行所需的信息,例如文件描述符表。
wait() 函数用来帮助父进程获取其子进程的退出状态。当进程退出时,内核为每个进
程保存了退出状态信息。若是父进程未调用 wait() 函数,则子进程的退出信息将一直保存在内存中。
Linux 中,每一个进程在退出的时候,能够释放用户区空间,可是没法释放进程自己的 PCB 所占用的内存资源。PCB 必须由父进程释放。
父进程在建立子进程后退出,子进程变成孤儿进程。为防止内存泄漏,孤儿进程被 init 进程领养,init 进程变成孤儿进程的父进程。
下面示例中,父进程先退出:
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> int main() { pid_t pid = fork(); if (pid == 0) { printf("child pid is: %d, ppid is: %d\n", getpid(), getppid()); sleep(1); printf("child pid is: %d, ppid is: %d\n", getpid(), getppid()); } else if (pid > 0) { sleep(0.5); printf("parent pid is: %d, ppid is: %d\n", getpid(), getppid()); printf("parent exit\n"); } return 0; }
输出:
parent pid is: 3348, ppid is: 713 parent exit child pid is: 3349, ppid is: 1 child pid is: 3349, ppid is: 1
子进程退出了,父进程一直未调用 wait 或 waitpid 函数,子进程就变成了僵尸进程。
可执行的二进制文件,都是从 main 函数开始执行的。main 函数有 3 种原型定义:
int main(); int main(int argc, char *argv[]); int main(int argc, char *argv[], char *env[]);
参数:
注意,Shell 终端没法检测进程是否建立了子进程。在进程执行完毕后,Shell 会当即回到交互状态,此时若是子进程还在输出数据,会打印在 Shell 的命令提示符以后。能够在父进程中 sleep 一下。
Linux 中用 fork() 函数建立新进程,函数原型以下:
#include<unistd.h> pid_t fork(void);
返回值:
成功建立进程时,会对父子进程各返回一次,对父进程返回子进程的 PID,对子进程返回 0。经过条件分支语句能够分别进行不一样处理。失败则返回小于 1 的错误码。
fork 函数执行的时候,会将当前正在运行的进程完整的复制一份,提供给子进程。子进程从 fork 函数以后开始执行。
经过 for 循环,能够建立多个子进程,只须要在每次 fork 以后判断若是是子进程则结束循环便可。经过循环下标能够判断当前子进程是第几回建立的:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int i = 0; int number = 5; pid_t pid; for (i = 0; i < number; i++) { pid = fork(); if (pid == 0) break; } // 子进程 if (i == 0) printf("first process, pid = %d\n", getpid()); if (i == 1) printf("second process, pid = %d\n", getpid()); //... // 父进程 if (i == number) printf("parent process, pid = %d\n", getpid()); return 0; }
进程的终止分为两种:
exit 函数原型以下:
#include <stdlib.h> void exit(int status);
exit(0)
等价于 return 0
。
fork 函数能够复制一份父进程,获得的子进程跟父进程有彻底同样的代码跟数据。以后两个进程各自执行,互不影响。
实际上咱们一般须要子进程执行不一样的代码,这时就须要经过 exec 函数加载代码段,并跳转到新代码段的 main 入口执行。
函数原型:
#include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);
exec 函数组是在 exec 上加 l、v、p、e 四个后缀造成的,这些函数做用相同,只是在参数列表上存在差异。
返回值:exec 族函数报错时才有返回值 -1,不然无返回值。若是执行到后面的代码,就是出错了。
示例:
char* myenv[] = {"TEST=666", "HOME=/home/kika", NULL}; execle("home/kika/test", "hello", "world", myenv);
完整示例:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int pid = fork(); if (pid > 0) { exit(0); } else if (pid == 0) { execle("home/kika/test", "hello", "world", myenv); perror("execle error"); exit(-1); } else { perror("fork error"); } return -1; }
一般,在父进程中调用 wait 函数,能够查看子进程的退出信息,让子进程撤单结束。wait 函数是阻塞式的,waitpid 能够设置为非阻塞的。父进程根据建立的子进程个数,在循环中经过 wait 函数逐个回收子进程。而 waitpid 函数则能够经过 PID 等待指定的子进程退出。
wait 函数调用一次只会回收一个子进程。多个子进程须要调用屡次 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 参数:
waitpid 参数:
-1
:等价于 wait,等待任意子进程退出0
:等待组 ID 等于调用进程的组 ID 的任一子进程退出> 0
:等待 PID 等于该数值的进程退出< -1
:等待其组 ID 等于该数值的任一子进程退出返回值:成功时返回退出子进程的 PID,没有子进程时返回 -1.
建立三个子进程,分别运行自定义程序,shell 程序,未定义程序(段错误)。而后在父进程中经过 wait 回收全部子进程,并分别判断退出缘由:
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/stat.h> #include <errno.h> int main(int argc, char* argv[]) { int num = 3; int i; pid_t pid; for (i = 0; i < 3; i++) { pid = fork(); if (pid == 0) break; } if (i == 0) { execlp("ps", "ps", "aux", NULL); perror("execlp ps error"); exit(1); } else if (i == 1) { execl("/root/test/api/process/myls", "", NULL); perror("execl myls error"); exit(1); } else if (i == 2) { execl("./error", "", NULL); perror("execl ./error"); exit(1); } else { int status; pid_t pid; while (pid = wait(&status) != -1) { printf("children PID is: %d\n", pid); if (WIFEXITED(status)) { printf("return value is: %d\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("died by signal: %d\n", WTERMSIG(status)); } } } return 0; }