ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux应用开发(四)——进程

Linux应用开发(四)——进程 一、进程的概念1、进程的概念进程是一个独立的可调度的任务进程是一个抽象实体。当系统在执行某个程序时分配和释放的各种资源进程是一个程序的一次执行的过程进程是程序执行和资源管理的最小单位2、进程与程序的区别程序是静态的它是一些保存在磁盘上的指令的有序集合没有任何执行的概念。进程是一个动态的概念它是程序执行的过程包括创建、调度和消亡。进程包含正文段 (指令, c语言中的语句)就是elf的.text段用户数据段全局变量const修饰的常量字符串, static 修饰的局部变量在进程中叫静态区静态生存期。系统数据段堆区 动态内存区, malloc 申请内存的区域栈区函数内部定义的局部变量, 在函数被调用时, 会创建这些局部变量, 在栈区创建函数调用结束后所有的局部变量都会被销毁 动态生存期程序包含正文段(指令, c语言中的语句)就是elf的.text段用户数据段: 全局变量const修饰的常量字符串, static 修饰的局部变量在进程中叫静态区3、主要的进程标识进程号(Process Identity NumberPID)父进程号(Parent Process IDPPID)PID唯一的标识一个进程4、Linux系统中的进程类型交互进程该类进程是由shell控制和运行的。交互进程既可以在前台运行也可以在后台运行。例如: 正常的程序批处理进程该类进程不属于某个终端它被提交到一个队列中以便顺序执行。例如: shell脚本程序守护进程该类进程在后台运行。它一般在Linux启动时开始执行系统关闭时才结束。例如: window中的服务5、进程运行状态运行态此时进程或者正在运行或者准备运行。等待态此时进程在等待一个事件的发生或某种系统资源。停止态此时进程被中止。死亡态(僵尸态)这是一个已终止的进程但还在进程向量数组中占有一个task_struct结构。例如: 使用top查看系统进程的状态6、任务调度的算法原则 微观上串行 宏观上并行。时间片轮转的算法对系统的进程进行调度 。把所有要执行的进程保存到一个队列内按照固定大小的时间 这个时间有系统的宏HZ来确定一般定义100 执行一次的时间是10ms 也就是说每个进程只能获取最多不超过10ms 的时间片 如果10ms内完成任务 即可立即结束任务。如果10ms内进程没有完成任务 也必须放弃对cpu的执行权限 让下一个进程获取cpu 去执行程序 依次类推完成系统中多个任务的调度。7、进程的模式进程的执行模式分为用户模式和内核模式二、进程的系统调用1、fork 创建进程fork函数的理解就是1个细胞分裂成2个细胞的过程。pid_t fork(void);函数功能创建一个子进程函数参数无参数函数返回值特别注意: fork后会有2个进程 一个是父进程另外一个是子进程 , 会有2个fork函数的返回值。子进程返回0父进程返回子进程的进程号fork创建错误返回-1 并设置errno , 在父进程返回-1 , 子进程不创建2、getpid 函数pid_t getpid(void);函数功能获取当前进程的进程号函数参数无参数函数返回值成功返回当前进程的进程号这个函数总是成功 , 没有错误的情况3、getppid 函数pid_t getppid(void);函数功能获取当前进程的父进程号函数参数无参数函数返回值成功返回当前进程的父进程号这个函数总是成功 , 没有错误的情况4、实例fork创建父子进程#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid while (1) { printf(子进程process is running\n); sleep(1); } } else // 父进程 返回值 为 子进程的 pid { printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid while(1) { printf(父进程process is running\n); sleep(1); } } return 0; }5、深入理解fork函数#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h int main(int argc, char const *argv[]) { int count 100; pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid while (1) { count ; printf(子进程process is running: %d\n, count); sleep(1); } } else // 父进程 返回值 为 子进程的 pid { printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid while(1) { count --; printf(父进程process is running: %d\n, count); sleep(1); } } return 0; }6、exec 函数簇exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后原调用进程的内容除了进程号外其他全部都被替换了。可执行文件既可以是二进制文件也可以是任何Linux下可执行的脚本文件。int execl(const char *path, const char *arg, ... /* (char *) NULL */);函数功能在进程中启动另一个程序执行的方法l : list , 列表形式函数参数【path】文件所在的路径名 例如/home/linux/1.txt【arg】要执行的程序... 运行时带入的参数可以没有, 也可以很多个, 以NULL作为最后一个函数返回值成功不返回失败返回-1 并设置errnoThe exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error.int execlp(const char *file, const char *arg, ... /* (char *) NULL */);函数功能在进程中启动另一个程序执行的方法p path函数参数【file】文件的名称 , 不带路径, 使用系统环境变量PATH的路径【arg】要执行的程序... 运行时带入的参数, 可以没有, 也可以很多个, 以NULL作为最后一个函数返回值成功不返回失败返回-1 并设置errnoThe exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error.int execv(const char *path, char *const argv[]);函数功能在进程中启动另一个程序执行的方法函数参数【path】文件的名称 , 带路径【argv】要执行的程序, 所有参数都在里面, 等同于main函数参数实现函数返回值成功不返回失败返回并设置errnoThe exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error.int execvp(const char *file, char *const argv[]);函数功能在进程中启动另一个程序执行的方法函数参数【file】文件的名称 , 不带路径, 使用系统环境变量PATH的路径【argv】要执行的程序, 所有参数都在里面, 等同于main函数参数实现函数返回值成功不返回失败返回 : -1 并设置errnoThe exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error.7、实例execl 执行myls程序#include stdio.h #include stdlib.h #include string.h #include sys/types.h #include unistd.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); exit(-1); } else if (pid 0) // 子进程 { printf(child: pid%d\n, getpid()); // 获取当前进程的进程号 printf(child:ppid%d\n, getppid()); // 获取当前进程的父进程号 sleep(1); execl(/home/linux/emb/06-linuxapp/33-myls, 33-myls, -l, /home/linux, NULL); exit(0); } else // 父进程 返回子进程的进程号 { printf(parent: pid%d\n, getpid()); // 获取当前进程的进程号 printf(parent:ppid%d\n, getppid()); // 获取当前进程的父进程号 while (1) { printf(parent: process is running\n); sleep(1); } } return 0; }8、实例execlp 执行ls程序#include stdio.h #include stdlib.h #include string.h #include sys/types.h #include unistd.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); exit(-1); } else if (pid 0) // 子进程 { printf(child: pid%d\n, getpid()); // 获取当前进程的进程号 printf(child:ppid%d\n, getppid()); // 获取当前进程的父进程号 sleep(1); // 在PATH 环境变量的路径中去搜索可执行程序 execlp(ls, ls, -l, /home/linux, NULL); exit(0); } else // 父进程 返回子进程的进程号 { printf(parent: pid%d\n, getpid()); // 获取当前进程的进程号 printf(parent:ppid%d\n, getppid()); // 获取当前进程的父进程号 while (1) { printf(parent: process is running\n); sleep(1); } } return 0; }9、实例execv 执行opendir程序#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid char *argvv[] {32-opendir, /home/linux, NULL}; execv(/home/linux/work/12-linuxapp/32-opendir, argvv); exit(0); // 子进程执行 execl 后 就不会执行下面的代码了 } else // 父进程 返回值 为 子进程的 pid { printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid while(1) { printf(父进程process is running\n); sleep(1); } } return 0; }10、实例execvp 执行ls程序#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid char *argvv[] {ls, -l, NULL}; execvp(ls, argvv); exit(0); // 子进程执行 execl 后 就不会执行下面的代码了 } else // 父进程 返回值 为 子进程的 pid { printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid while(1) { printf(父进程process is running\n); sleep(1); } } return 0; }三、进程回收1、僵尸进程的产生父进程创建子进程后子进程运行结束后退出父进程还在并且没有回收子进程 此时会产生僵尸进程。父进程创建子进程后子进程运行结束后退出父进程还在并回收子进程 此时不会产生僵尸进程。父进程创建子进程后子进程运行结束后退出父进程也退出 此时系统1号进程回收。父进程创建子进程后父进程退出子进程变成孤儿进程 孤儿进程有系统1号进程托管。2、实例僵尸进程#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid sleep(5); printf(子进程process is exit\n); exit(0) ; // 子进程退出 } else // 父进程 返回值 为 子进程的 pid { printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid while(1) { printf(父进程process is running\n); sleep(1); } } return 0; }3、避免产生僵尸进程的方法第1种: 回收进程来避免产生僵尸进程第2种: 创建子进程父进程退出让子进程变成孤儿进程系统1号进程托管 服务就是这个思路创建的第3种: 创建子进程后就脱离父子关系使用信号的机制来实现4、wait 函数pid_t wait(int *wstatus);函数功能调用该函数使进程阻塞直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束wait函数会立即返回。函数参数【wstatus 】是一个整型指针指向的对象用来保存子进程退出时的状态。wstatus若为空表示忽略子进程退出时的状态status若不为空表示保存子进程退出时的状态另外子进程的结束状态可由Linux中一些特定的宏来测定。函数返回值成功返回子进程的进程号失败返回-1wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.5、waitpid 函数pid_t waitpid(pid_t pid, int *wstatus, int options);函数功能功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式阻塞或非阻塞函数参数【pid】pid0只等待进程ID等于pid的子进程不管已经有其他子进程运行结束退出了只要指定的子进程还没有结束waitpid就会一直等下去。pid-1等待任何一个子进程退出此时和wait作用一样。pid0等待其组ID等于调用进程的组ID的任一子进程。pid-1等待其组ID等于pid的绝对值的任一子进程。wstatus 是一个整型指针指向的对象用来保存子进程退出时的状态。【wstatus】若为空表示忽略子进程退出时的状态status若不为空表示保存子进程退出时的状态另外子进程的结束状态可由Linux中一些特定的宏来测定。【options】WNOHANG不阻塞的方式 , 若由pid指定的子进程并不立即可用则waitpid不阻塞此时返回值为00同wait阻塞父进程等待子进程退出。函数返回值成功返回子进程的进程号失败返回-1wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.6、实例回收子进程#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h #include sys/wait.h int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid sleep(5); printf(子进程process is exit\n); exit(0) ; // 子进程退出 } else // 父进程 返回值 为 子进程的 pid { printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid pid_t child_pid wait(NULL); // 等待子进程退出 printf(父进程子进程 %d 退出\n, child_pid); system(ps -ef | grep 41-wait); while(1) { printf(父进程process is running\n); sleep(1); } } return 0; }四、守护进程 也叫服务1、守护进程的概念守护进程也就是通常所说的Daemon进程是Linux中的后台服务进程。它是一个生存期较长的进程通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件守护进程常常在系统启动时开始运行在系统关闭时终止Linux系统有很多守护进程大多数服务都是用守护进程实现的在Linux中每一个系统与用户进行交流的界面称为终端。从该终端开始运行的进程都会依附于这个终端这个终端称为这些进程的控制终端。当控制终端被关闭时相应的进程都会被自动关闭。守护进程能够突破这种限制它从开始运行直到整个系统关闭才会退出。如果想让某个进程不会因为用户或终端的变化而受到影响就必须把这个进程变成一个守护进程。2、创建守护进程的步骤第1步: 创建子进程父进程退出 目的是为了避免产生僵尸进程第2步在子进程中创建新会话 , 让子进程独立于终端 脱离终端的控制 使用setsid函数实现第3步改变当前目录为根目录 为了防止目录被删除后出现的错误第4步重设文件权限掩码 创建新的文件时 可以不受限制 umask第5步关闭文件描述符 把已经打开的文件描述符进行关闭 防止错误3、在子进程中创建新会话进程组进程就是班级中的成员进程组就是班级中的一个小组进程组是一个或多个进程的集合。进程组由进程组ID来唯一标识。每个进程组都有一个组长进程进程组ID就是组长进程的进程号。会话组就是一个班级的概念会话组是一个或多个进程组的集合。对话期 就是会话组proc1 和 proc2 属于后台进程组proc3 proc4 proc5 属于前进程组想要让一个进程独立 必须要创建一个新的会话组把学校理解为系统 学生想要独立 必须脱离班级的管理 就要创建一个新的班级 班级接受学校的管理4、实例守护进程的创建#include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.h #include syslog.h #include errno.h // 创建1个守护进程 int main(int argc, char const *argv[]) { pid_t pid fork(); if (pid 0) { perror(fork); return -1; } else if (pid 0) // 父进程 返回值 为 子进程的 pid { // 步骤1 创建子进程父进程退出 printf(父进程pid %d\n, getpid()); // 父进程的 pid printf(父进程ppid %d\n, getppid()); // 父进程的 ppid printf(父进程子进程 %d 退出\n, pid); exit(0); // 父进程退出 子进程成为孤儿进程被 init 进程收养 避免成为僵尸进程 } else if (pid 0) // 子进程 返回值 为 0 { printf(子进程pid %d\n, getpid()); // 子进程的 pid printf(子进程ppid %d\n, getppid()); // 子进程的 ppid // 步骤2 子进程成为新的会话组长和进程组长 setsid(); // 子进程成为新的会话组长和进程组长 // 步骤3 改变子进程的工作目录 chdir(/); // 改变子进程的工作目录 防止目录被删除后导致守护进程无法正常运行 // 步骤4 关闭子进程的文件描述符 close(STDIN_FILENO); // 关闭标准输入 close(STDOUT_FILENO); // 关闭标准输出 close(STDERR_FILENO); // 关闭标准错误输出 // 步骤5 重设文件掩码 umask(0); // 重设文件掩码 防止创建的文件权限被限制 while(1) { system(date /tmp/daemon.log); // 向文件写入当前时间 sleep(1); } } return 0; }
返回列表