> 文章列表 > C/C++ Linux进程操作

C/C++ Linux进程操作

C/C++ Linux进程操作

目录

一、简介

二、创建进程

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) {// 创建进程idpid_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. 1. 调用fork(),创建新进程,它会是将来的守护进程.
  2. 2. 在父进程中调用exit,保证子进程不是进程组长
  3. 3. 调用setsid()创建新的会话区
  4. 4. 将当前目录改成目录(如果把当前目录作为守护进程的目录,当前目录不能被卸载他作为守护进程的工作目录)
  5. 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);
}

七、总结

进程的一些用法就是这些了,现在我也还不知道进程有哪些作用,日后学习了在写一篇博客记录下来!