目录
一、简介
二、创建进程
1. fork
2. wait
3. exit
三、多进程高并发设计
四、孤儿进程
五、僵尸进程
六、守护进程
七、总结
一、简介
进程是什么?
答:可以简单理解为,一个 .exe 的应用程序,就是运行在进程中的!
当然,一个应用程序,可以由多个进程共同运行。
操作系统可以运行多个程序,那他是如何运行的?实际上,CPU的执行是很快的,而待运行的程序很多,那么为了让操作系统运行多个程序,CPU会把它的执行时间划分成很多段,比如每一段是0.1秒,那么就可以这样A程序运行0.1秒,然后B程序运行0.1,然后C程序运行0.2秒,因为这个切换很快,所以我们感觉程序是同时运行的。
操作系统上看上面提到的运行程序就是指一个进程,因为存在切换,所以进程管理了很多资源(如打开的文件、挂起的信号、进程状态、内存地址空间等等),也就是说进程参与了CPU的调度,和管理了所有资源,哦,这句话,不是很正确,实际上现代CPU的执行非常非常快,而且操作系统有多个CPU,使用一个进程参与调度时,频繁地从CPU的寄存器和进程堆栈的保存运行状态和对应的信息都很耗时,所以现代CPU将进程仅仅作为一个资源管理的东东,而引入了线程作为CPU调度的基本单位,多个线程可以共享同一进程的所有资源(后面会讲线程)。
注意,进程跟进程间一般不共享数据,当然也会有一些特殊情况;线程是在进程内部的,所以线程跟线程之间可以共享数据!
二、创建进程
1. fork
#include <unistd.h>
pid_t fork(void);
描述:创建一个进程
返回值:
成功:父进程返回子进程id,子进程返回0;
失败:父进程返回-1,并设置errno错误标志。
示例:
pid_t fpid = fork();
2. wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
描述:父进程等待子进程退出
status:
获取子进程使用exit函数退出时指定给父进程返回的数据;如果子进程exit(10),返回的是正数,则父进程的status接收到的的是10;如果子进程exit(-10),返回的是补码,即返回246。
返回值:
成功:返回终止子进程的id;
失败:返回-1.
示例:
int status;
wait(&status);
3. exit
#include <stdlib.h>
void exit(int status);
描述:退出进程
status:
返回给父进程的参数
示例:
exit(-10); // 正数正常返回,负数返回的是补码
例:
#include <unistd.h> // fork
#include <stdlib.h> // exit
#include <sys/types.h>
#include <sys/wait.h> // wait
#include <stdio.h>
int main(int argc, char **argv) {
// 创建进程id
pid_t fpid;
int count = 0;
int status = 0;
// 创建进程
fpid = fork();
if (fpid < 0) {
printf("error in fork!\n");
} else if (0 == fpid) {
printf("child process, My process id is %d\n", getpid());
count += 10;
// 销毁进程
//exit(-10); // 正数正常返回,负数返回的是补码
} else {
printf("parent process, My process id si %d\n", getpid());
count++;
}
printf("统计结果是:%d\n", count);
//wait(&status);
printf("parent - status:%d\n", WEXITSTATUS(status));
return 0;
}
关于子进程和父进程是否共享变量内存的问题:
创建出了子进程,按照以前旧的版本,子进程创建成功后,会立马拷贝父进程的所有变量等内存到子进程中,这样效率很低;
然后现在的版本,子进程创建成功后,还不会立马拷贝,还是和父进程共享,但当父进程如果修改了某个变量的值时,在修改之时,子进程就会拷贝一份到自己那里,这样效率就高很多了!
三、多进程高并发设计
由一个Master管理进程和多个相同的work工作进程组成;
在终端输入命令:cat /proc/cpuinfo 查看自己cpu的核数;
例如,processor : 1 ;我这里最后一个显示的是1,那么就是两核的;
所以在下面的代码中,可以创建两个工作进程去来处理工作了!
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name);
int main(int argc, char **argv) {
start_worker_processes(2);
// 管理子进程
wait(NULL);
}
void start_worker_processes(int n) {
int i = 0;
for (i = n - 1; i >= 0; i--) {
spawn_process(worker_process_cycle,(void *)(intptr_t) i, (char*)"worker process");
}
}
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name) {
pid_t pid;
pid = fork();
switch(pid) {
case -1:
fprintf(stderr, "fork() failed while spawning \"%s\"\n", name);
return -1;
case 0:
proc(data);
return 0;
default:
break;
}
printf("start %s %ld\n", name, (long int)pid);
return pid;
}
static void worker_process_init(int worker) {
cpu_set_t cpu_affinity;
// 多核高并发处理
CPU_ZERO(&cpu_affinity); // 清零
CPU_SET(worker%CPU_SETSIZE, &cpu_affinity);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpu_affinity) == -1) {
fprintf(stderr, "sched_setaffinity() failed\n");
}
}
void worker_process_cycle(void *data) {
int worker = (intptr_t)data;
// 初始化
worker_process_init(worker);
// 进程干的活
for (;;) {
sleep(10);
printf("pid %ld, doing...\n", (long int)getpid());
}
}
程序运行后, 使用命令: ps -ef | grep 可执行文件名 即可查看自己创建的进程了
其中,4658和4659是程序创建的两个子进程id,4657是父进程id
ctrl + c 可以退出程序,或者 killall -9 可执行文件名
四、孤儿进程
父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
init进程是所有进程的祖宗,它的进程id是1;
如何模仿实现孤儿进程呢?
运行以下代码,然后使用命令: kill -9 父进程,即可实现孤儿进程!
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name);
int main(int argc, char **argv) {
start_worker_processes(2);
// 管理子进程
// 程序一直卡在这里,一直没法执行下面的wait代码
while(1) { sleep(1); }
wait(NULL);
}
void start_worker_processes(int n) {
int i = 0;
for (i = n - 1; i >= 0; i--) {
spawn_process(worker_process_cycle,(void *)(intptr_t) i, (char*)"worker process");
}
}
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name) {
pid_t pid;
pid = fork();
switch(pid) {
case -1:
fprintf(stderr, "fork() failed while spawning \"%s\"\n", name);
return -1;
case 0:
proc(data);
return 0;
default:
break;
}
printf("start %s %ld\n", name, (long int)pid);
return pid;
}
static void worker_process_init(int worker) {
cpu_set_t cpu_affinity;
// 多核高并发处理
CPU_ZERO(&cpu_affinity); // 清零
CPU_SET(worker%CPU_SETSIZE, &cpu_affinity);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpu_affinity) == -1) {
fprintf(stderr, "sched_setaffinity() failed\n");
}
}
void worker_process_cycle(void *data) {
int worker = (intptr_t)data;
// 初始化
worker_process_init(worker);
// 干活
for (;;) {
sleep(10);
printf("pid %ld, doing...\n", (long int)getpid());
}
}
当父进程被杀死后,两个子进程的父进程id变为了1,也就是init进程接管了两个子进程!这就是孤儿进程的展示了!
五、僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
僵尸进程怎样产生的:
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用 exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
子进程变为僵尸进程后,都是在等待父进程为他收尸,如果父进程没有给他收尸,那么它就一直都是僵尸进程。
所以,为了不让进程成为僵尸进程,务必在父进程里加上wait即可!
当然,也可以把父进程杀掉,那么init进程就会接管那些子进程,也一样会收尸。
或者接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。
使用下方代码实现僵尸进程:
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef void (*spawn_proc_pt) (void *data);
static void worker_process_cycle(void *data);
static void start_worker_processes(int n);
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name);
int main(int argc, char **argv) {
start_worker_processes(2);
// 管理子进程
// 程序一直卡在这里,一直没法执行下面的wait代码
while(1) { sleep(1); }
wait(NULL);
}
void start_worker_processes(int n) {
int i = 0;
for (i = n - 1; i >= 0; i--) {
spawn_process(worker_process_cycle,(void *)(intptr_t) i, (char *)"worker process");
}
}
pid_t spawn_process(spawn_proc_pt proc, void *data, char *name) {
pid_t pid;
pid = fork();
switch(pid) {
case -1:
fprintf(stderr, "fork() failed while spawning \"%s\"\n", name);
return -1;
case 0:
proc(data);
return 0;
default:
break;
}
printf("start %s %ld\n", name, (long int)pid);
return pid;
}
static void worker_process_init(int worker) {
cpu_set_t cpu_affinity;
// 多核高并发处理
CPU_ZERO(&cpu_affinity); // 清零
CPU_SET(worker%CPU_SETSIZE, &cpu_affinity);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpu_affinity) == -1) {
fprintf(stderr, "sched_setaffinity() failed\n");
}
}
void worker_process_cycle(void *data) {
int worker = (intptr_t)data;
// 初始化
worker_process_init(worker);
// 干活
//for (;;) {
//sleep(10);
printf("pid %ld, doing...\n", (long int)getpid());
//}
// 子进程退出,变为僵尸进程,等待父进程“收尸”
exit(1);
}
查看僵尸进程:
ps -ef | grep 可执行文件名
使用命令 :kill -9 父进程id ,即可结束僵尸进程。(init接管了子进程,为他们收尸)
六、守护进程
不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务。守护进程脱离于终端,是为了避免进程在执行过程中的信息在任何终端上显示,并且进程也不会被任何终端所产生的终端信息所打断(比如关闭终端等)。
有空的可以试一下,使用上面的代码创建子进程后,然后关闭终端,运行程序的进程也会随之关闭。
那如何成为一个守护进程呢? 步骤如下:
- 1. 调用fork(),创建新进程,它会是将来的守护进程.
- 2. 在父进程中调用exit,保证子进程不是进程组长
- 3. 调用setsid()创建新的会话区
- 4. 将当前目录改成根目录(如果把当前目录作为守护进程的目录,当前目录不能被卸载他作为守护进程的工作目录)
- 5. 将标准输入,标准输出,标准错误重定向到/dev/null.
守护进程代码:
int daemon(int nochdir, int noclose) {
int fd;
switch (fork()) {
case -1:
return -1;
case 0:
break;
default:
_exit(0);
}
if (setsid() == -1) {
return -1;
}
if (!nochdir) {
(void)chdir("/");
}
if (!noclose && (fd == open("/dev/null", O_RDWR, 0)) != -1) {
(void)dup2(fd, STDIN_FILENO);
(void)dup2(fd, STDOUT_FILENO);
(void)dup2(fd, STDERR_FILENO);
if (fd > 2) {
(void)close(fd);
}
return 0;
}
}
在main函数的第一行直接调用这个函数,参数传两个0即可创建守护进程!
int main(int argc, char **argv) {
// 创建守护进程
daemon(0, 0);
start_worker_processes(2);
// 管理子进程
wait(NULL);
}
七、总结
进程的一些用法就是这些了,现在我也还不知道进程有哪些作用,日后学习了在写一篇博客记录下来!