进程控制
进程标识
每个进程者阴一个非负整形表示的唯一进程ID. 虽然唯一,但是是可以复用,延时复用算法:避免防止将新建进程误认为是使用同一ID的某个已终止的先前进程
ID号为1的是init进程,init进程决不会终止,/etc/init或/sbin/init
pid_t getpid(void);
调用进程的idpid_t getppid(void);
调用进程的父进程iduid_t getuid(void);
调用进程的实际用户iduid_t geteuid(void);
调用进程的有效用户idgid_t getgid(void);
调用进程的实际组idgid_t getegid(void);
调用进程的有效组id
// 对应单词简写:
// p --> process
// p --> parent
// u --> user
// e --> effect
// g --> grou
pid_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
// p --> process
// p --> parent
// u --> user
// e --> effect
// g --> group
int main(int argc, char* argv[]) {
// pid_t getpid(void); 调用进程的id
// pid_t getppid(void); 调用进程的父进程id
// uid_t getuid(void); 调用进程的实际用户id
// uid_t geteuid(void); 调用进程的有效用户id
// gid_t getgid(void); 调用进程的实际组id
// gid_t getegid(void); 调用进程的有效组id
printf("pid = %d\n", getpid());
printf("ppid = %d\n", getppid());
printf("uid = %d\n", getuid());
printf("euid = %d\n", geteuid());
printf("gid = %d\n", getgid());
printf("egid = %d\n", getegid());
return 0;
}
fork函数
用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程
pid_t fork(void);
正常这个函数会返回两次,一次是父进程调用返回,返回子进程id; 在一次是子进程调用返回,返回0,表示子进程创建成功
fork_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
printf("enter main function\n");
int ret = fork();
if (ret == -1) {
// error
perror("Failed to create child process\n");
return ret;
} else if (ret == 0) {
// child process;
printf("success to create child process, pid = %d, parent pid = %d\n", getpid(), getppid());
} else if (ret > 0) {
// parent process;
sleep(1);
printf("in parent process, child process id is %d, pid = %d, parent id = %d\n", ret, getpid(), getppid());
}
printf("exit main function\n");
return 0;
}
wait/waitpid函数
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等.
一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号
父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
wait() 和 waitpid() 函数的功能一样,区别在于,wait() 函数会阻塞,waitpid() 可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数wstatus:
是一个传出参数,表示退出信息:是异常退出,还是正常退出
用宏函数去判断:
- WIFEXITED(status) 为非0 → 进程正常结束
- WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
- WIFSIGNALED(status) 为非0 → 进程异常终止
- WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
- WIFSTOPPED(status) 为非0 → 进程处于暂停状态
- WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
- WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
参数options: options 提供了一些额外的选项来控制 waitpid()。
- 0:同 wait(),阻塞父进程,等待子进程退出。
- WNOHANG:没有任何已经结束的子进程,则立即返回。
- WUNTRACED:如果子进程暂停了则此函数马上返回,并且不予以理会子进程的结束状态。(由于涉及到一些跟踪调试方面的知识,加之极少用到)
孤儿进程
父进程运行结束,但子进程还在运行(未运行结束)的子进程就称为孤儿进程(Orphan Process)。
每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。
因此孤儿进程并不会有什么危害。
僵尸进程
子进程终止,父进程尚未回收,变成僵尸(Zombie)进程。
这样就会导致一个问题,如果进程不调用wait() 或 waitpid() 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。
exec函数族
执行另一个程序
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 execve(const char *path, char *const argv[], char *const envp[]);
// 单词缩写
// l --> list
// p --> path
// v --> argument vector
// e --> environment
exec_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
// 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 execve(const char *path, char *const argv[], char *const envp[]);
// ls -hal ~/code
pid_t pid = fork();
if (pid == 0) {
// child
execl("/bin/date", "date", "-u", NULL); // 程序路径, NULL不能少
execlp("date", "date", "-u", NULL); // 会去PATH环境变量里去找date
perror("Failed to exec date ");
} else if (pid > 0) {
// parent
sleep(1);
}
return 0;
}
进程调度
基于调度优先级的粗粒度的控制. 调度的策略和调用优先级是由内核确定,
nice值0 ~ (2*NZERO)-1
; nice值越小优先级越高;NZERO是系统默认的nice值
int nice(int inc);
获取或更改进程的nice值int getpriority(int which, id_t who);
获取进程的nice值int setpriority(int which, id_t who, int prio);
设置进程的nice值
参数which:
PRIO_PROCESS
:表示进程PRIO_PGRP
:表示进程组PRIO_USER
:表示用户ID
参数who:
- 0:表示调用进程,进程组或者用户
- 当which设置为PRIO_USER并且who为0,使用调用进程的实际用户ID
- 如果which参数作用于多个进程,则返回所有作用进程中优先级最高的
#define NZERO 20 /* default priority [XSI] */
/* = ((PRIO_MAX - PRIO_MIN) / 2) + 1 */
/* range: 0 - 39 [(2 * NZERO) - 1] */
/* 0 is not actually used */
Note:nice函数中的incr如果太大,系统会直接把它降到最大合法值;如果incr太小,系统会把它提高到最小的合法值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <sys/syslimits.h> // NZERO
#elif defined(BSD)
#include <sys/param.h> // NZERO
#endif
int main(int argc, char* argv[]) {
int ret;
// int nice(int inc);
ret = nice(3);
printf("nice = %d\n", ret);
// int getpriority(int which, id_t who);
int which = PRIO_PROCESS; // PRIO_PGRP / PRIO_USER
pid_t pid = getpid();
ret = getpriority(which, pid);
printf("nice = %d\n", ret);
// int setpriority(int which, id_t who, int prio);
ret = setpriority(which, pid, 100);
if (ret == -1) {
printf("Failed to set priority\n");
} else if (ret == 0) {
printf("Success to set priority\n");
}
ret = getpriority(which, pid);
printf("after set priority = %d\n", ret);
return 0;
}