. overview
💡 本节介绍关于进程实际操作的API函数,关于如何创建和控制进程。
1. API
unistd.h 为Linux/Unix系统中内置头文件 ,包含了许多系统服务的函数原型,例如read函数、write函数和getpid函数等
1.1 fork()
fork函数用来创建一个子进程,在调用者这方返回子进程的PID,被调用方(子进程)会得到0。
arduino
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("hello world! %d\n", (int)getpid());
**int rc = fork();**
if (rc < 0)
{
printf("create the pro faliler!\n");
exit(1);
}
else if (**rc == 0**)
{
printf("this is the child-proc:%d\n", (int)getpid());
}
else
{
printf("this is the parent-proc:%d",
"my child is %d\n", (int)getpid(), rc);
}
/* code */
return 0;
}
输出:
kotlin
hello world! 829734
this is the parent-proc:829734, my child is 829735
this is the child-proc:829735
此时当前进程(父进程)的PID为829734,调用fork后,将创建子进程,得到rc为子进程的PID。→要#include <unistd.h>
此时:该函数创建子进程时,子进程将会拷贝父进程的整个上下文信息,此时看起来就像是该运行程序的两个单独拷贝并独立运行。
- 两个进程都从fork函数处返回,继续往下执行。所以此时子线程不会从main函数从头执行,而是从fork函数后开始执行。
- 从fork函数处返回后,父进程得到的是子进程的PID(这里保留在rc变量),创建的子进程将返回得到0(保留在rc变量)。
💡 此时会有两个进程同时运行,所以也就会出现**并发的导致的一些现象**。如可能子进程先输入,也可能父进程先输出
1.2 wait()
有时,事实证明,对于父进程来说,等待子进程完成它一直在做的事情是非常有用的。→调用wait函数,返回得到等待的pid
要包含#include <sys/wait.h>
此时就是一个同步问题了。
arduino
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
printf("hello world! %d\n", (int)getpid());
int rc = fork();
if (rc < 0)
{
printf("create the pro faliler!\n");
exit(1);
}
else if (rc == 0)
{
printf("this is the child-proc:%d\n", (int)getpid());
}
else
{
int wait_pid = wait(NULL);
printf("this is the parent-proc:%d, my child is %d\n", (int)getpid(), rc);
}
/* code */
return 0;
}
此时一定是子进程先print,父进程才会print。
1.3 exec()
在fork()后,将会拷贝与父进程一样的一份代码,并运行。但是有时候想让子进程运行不同的程序代码,此时execvp()
函数就起作用了。如下,将参数和执行代码文件名封装在args中。此时会运行一个函数名为word_count的函数,其参数是a.txt。
scss
else if (rc == 0)
{
printf("this is the child-proc:%d\n", (int)getpid());
char *args[3];
args[0] = strdup("word_count");
args[1] = strdup("a.txt");
args[1] = NULL;
execvp(args[0], args);
}
💡 通过使用fork和exec函数,可以在运行fork之后,exec之前初始化一些进程其他的参数和变量,从而能够调用exec实现不同的函数。
fork() 和 exec() 的组合非常简单且功能强大。
1.4 shell命令行运行程序的例子
一、命令行运行程序
就像命令行窗口,它向您显示提示,然后等待您在其中键入内容。
- 然后,您键入一个命令(即,可执行程序的名称,以及任何参数;
- 在大多数情况下,shell 然后找出可执行文件在文件系统中的位置,调用 fork() 创建一个新的子进程来运行命令。
- 调用 exec() 的某个函数变体来运行命令
- 然后通过调用 wait() 等待命令完成。当子进程完成时,shell 将从 wait() 返回并再次打印出提示符,为下一个命令做好准备。
二、重定向输出
$ wc p3.c > newfile.txt
在上面的示例中,程序wc的输出被重定向到输出文件newfile.txt(大于符号是表示重定向的方式)。shell 完成此任务的方式非常简单:创建子进程时,在调用 exec() 之前,shell 会关闭标准输出并打开文件 newfile.txt。通过这样做,即将运行的程序wc中的任何输出都将发送到文件而不是屏幕。
此重定向有效的原因是由于对操作系统如何管理文件描述符的假设。具体来说,UNIX 系统开始以零的位置寻找空闲的文件描述符。在这种情况下,STDOUT FILENO 将是第一个可用的 FILENO,因此在调用 open() 时被分配。
1.5 其他对进程的控制→signal
(除了root)用户只能发送signal信号去中断自己发出的程序进程。
在 C 语言中,可以使用 kill
函数向指定进程发送信号。要中断另一个进程,可以将该进程的进程 ID 作为 kill
函数的第一个参数,将中断信号 SIGINT
或其他信号作为第二个参数:
arduino
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid = 1234; // 要中断的进程的进程 ID
int ret = kill(pid, SIGINT); // 发送中断信号
if (ret == -1) {
// 发送信号失败,处理错误
}
return 0;
}
可通过ps , top查看进程信息