赞
踩
总结一下Linux系统的进程创建/终止/等待等系统调用, 参考:
下面主要给出例子, 关于函数原型可以参考书中或者man 2 syscall
(例如man 2 fork
).
测试环境: Ubuntu 20.04 x86_64
gcc-9
用于创建新的进程, 创建出来的新进程称为子进程, 拥有和父进程一样的代码段/数据段/栈段/堆段.
所以创建新进程的资源消耗较大, 后续采用多线程方式可以解决这个问题.
由于这个函数的设计比较奇怪, 有两个返回值, 在父进程中返回子进程的进程ID, 在子进程中返回0, 错误返回-1, 所以可以用下面的语句制定创建子进程之后的进一步操作:
pid_t childPid;
switch (childPid = fork()) {
case -1:
/* error handling */
case 0: // child process
/* actions to child */
default:
/* actions to parent */
}
下面主要讨论数据共享和文件(句柄)共享, 为探讨进程间通信做准备.
一个例子, 关于同时操作一份数据:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> // pid_t #include <unistd.h> // fork static int idata = 111; int main(int argc, char *argv[]) { pid_t childPId; int istack = 222; switch (childPId = fork()) { case -1: fprintf(stderr, "fork error\n"); case 0: idata *= 3; istack *= 3; break; default: sleep(3); break; } printf("PID=%ld %s idata=%d istack=%d\n", (long)getpid(), (childPId == 0) ? "(child) " : "(parent)", idata, istack); /* PID=526436 (child) idata=333 istack=666 */ /* PID=526435 (parent) idata=111 istack=222 */ return 0; }
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <unistd.h> int main(int argc, char* argv[]) { int fd, flags; char template[] = "/tmp/test-XXXXXX"; setbuf(stdout, NULL);// 无缓冲 fd = mkstemp(template); if (fd == -1) fprintf(stderr, "mkstemp"); printf("File offset before fork: %lld\n", (long long)lseek(fd, 0, SEEK_CUR)); flags = fcntl(fd, F_GETFL); if (flags == -1) fprintf(stderr, "fcntl - F_GETFL"); printf("O_APPEND flag before fork() is %s\n", (flags & O_APPEND) ? "on" : "off"); switch (fork()) { case -1: fprintf(stderr, "fork"); case 0: // child if (lseek(fd, 1000, SEEK_SET) == -1) fprintf(stderr, "lseek"); flags = fcntl(fd, F_GETFL); if (flags == -1) fprintf(stderr, "fcntl - F_GETFL"); flags |= O_APPEND; if (fcntl(fd, F_SETFL, flags) == -1) fprintf(stderr, "fcntl - F_SETFL"); _exit(EXIT_SUCCESS); default: // parent if (wait(NULL) == -1) fprintf(stderr, "wait"); printf("child has exited\n"); printf("file offset in parent is %lld\n", (long long)lseek(fd, 0, SEEK_CUR)); flags = fcntl(fd, F_GETFL); if (flags == -1) fprintf(stderr, "fcntl - F_GETFL"); printf("O_APPEND in parent is %s\n", (flags & O_APPEND) ? "on" : "off"); exit(EXIT_SUCCESS); } return 0; } /* :!cc fork-file-shared.c && ./a.out */ /* File offset before fork: 0 */ /* O_APPEND flag before fork() is off */ /* child has exited */ /* file offset in parent is 1000 */ /* O_APPEND in parent is on */
最好不要用vfork
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { int istack = 222; switch (vfork()) { case -1: fprintf(stderr, "vfork"); exit(1); case 0: // child sleep(3); write(STDOUT_FILENO, "child executing\n", 16); istack *= 3; _exit(EXIT_SUCCESS); default: write(STDOUT_FILENO, "Parent executing\n", 17); printf("istack=%d\n", istack); exit(EXIT_SUCCESS); } return 0; } // 子进程对变量的修改影响了父进程的对应变量 /* child executing */ /* Parent executing */ /* istack=666 */
子进程会共享父进程的内存, 父进程会一直挂起直到子进程终止或者调用exec
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char *argv[]) { int numChild, j; pid_t childPid; numChild = 100; setbuf(stdout, NULL); // 关闭缓存 for (j = 0; j < numChild; ++j) { switch (childPid = fork()) { case -1: fprintf(stderr, "fork\n"); case 0: // child printf("%d child\n", j); _exit(EXIT_SUCCESS); default: // parent printf("%d parent\n", j); wait(NULL); break; } } return 0; }
几乎全是父进程先输出结果, 然后是子进程. 这就说明在Linux中fork执行之后会继续执行父进程, 而不是子进程.
不过, 这也取决于内核的调度算法实现.
所以不要对fork之后父子进程的执行顺序做任何假设, 如果一定要确保某一特定的执行顺序, 一定要采用某种进程间通信技术(同步技术), 例如: 文件锁, 信号量, 消息传送(基于管道, pipe).
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <time.h> #include <sys/types.h> #define BUF_SIZE 1000 #define SYNC_SIG SIGUSR1 /* Synchronization signal */ static void /* Signal handler - does nothing but return */ handler(int sig) {} char *currTime(const char *format) { static char buf[BUF_SIZE]; /* Nonreentrant */ time_t t; size_t s; struct tm *tm; t = time(NULL); tm = localtime(&t); if (tm == NULL) return NULL; s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm); return (s == 0) ? NULL : buf; } int main(int argc, char *argv[]) { pid_t childPid; sigset_t blockMask, origMask, emptyMask; struct sigaction sa; setbuf(stdout, NULL); /* Disable buffering of stdout */ sigemptyset(&blockMask); sigaddset(&blockMask, SYNC_SIG); /* Block signal */ if (sigprocmask(SIG_BLOCK, &blockMask, &origMask) == -1) fprintf(stderr, "sigprocmask"); sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = handler; if (sigaction(SYNC_SIG, &sa, NULL) == -1) fprintf(stderr, "sigaction"); switch (childPid = fork()) { case -1: fprintf(stderr, "fork"); case 0: /* Child */ /* Child does some required action here... */ printf("[%s %ld] Child started - doing some work\n", currTime("%T"), (long)getpid()); sleep(2); /* Simulate time spent doing some work */ /* And then signals parent that it's done */ printf("[%s %ld] Child about to signal parent\n", currTime("%T"), (long)getpid()); if (kill(getppid(), SYNC_SIG) == -1) fprintf(stderr, "kill"); /* Now child can do other things... */ _exit(EXIT_SUCCESS); default: /* Parent */ /* Parent may do some work here, and then waits for child to complete the required action */ printf("[%s %ld] Parent about to wait for signal\n", currTime("%T"), (long)getpid()); sigemptyset(&emptyMask); if (sigsuspend(&emptyMask) == -1 && errno != EINTR) fprintf(stderr, "sigsuspend"); printf("[%s %ld] Parent got signal\n", currTime("%T"), (long)getpid()); /* If required, return signal mask to its original state */ if (sigprocmask(SIG_SETMASK, &origMask, NULL) == -1) fprintf(stderr, "sigprocmask"); exit(EXIT_SUCCESS); } } /* :!cc fork-sig-sync.c -Wall && ./a.out */ /* [20:01:11 742971] Parent about to wait for signal */ /* [20:01:11 742977] Child started - doing some work */ /* [20:01:13 742977] Child about to signal parent */ /* [20:01:13 742971] Parent got signal */
#include <unistd.h>
void _exit(int status); // syscall
status参数就是传入的终止状态.
#include <stdlib.h>
void exit(int status); // libc
会依次执行下面三个步骤:
atexit()
和on_exit()
注册的函数stdio
流缓冲区status
提供的值执行_exit()
系统调用执行
return n;
相当于执行exit(n);
...
#include <stdlib.h>
int atexit(void (*func)(void)); // 出错返回非零值, 不一定为-1
其中传入的参数为一个参数列表和返回值均为void
的函数指针.
atexit函数会将传入的func函数指针加入到一个函数列表中, 进程终止时会调用该函数列表的所有函数.
以及一个类似的库函数: (可以传入状态)
#include<stdlib.h>
int on_exit(void (*func)(int, void *), void *arg);
其中传入的参数为一个参数列表为int和指针, 返回值为void
的函数指针.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> static void atexitFunc1(void) { printf("atexit func1 called\n"); } static void atexitFunc2(void) { printf("atexit func2 called\n"); } static void onexitFunc(int exitStatus, void *arg) { printf("on_exit func called: status=%d, arg=%ld\n", exitStatus, (long)arg); } int main(int argc, char *argv[]) { if (on_exit(onexitFunc, (void *)10)) fprintf(stderr, "on_exit 1\n"); if (atexit(atexitFunc1)) fprintf(stderr, "atexit 1\n"); if (atexit(atexitFunc2)) fprintf(stderr, "atexit 2\n"); if (on_exit(onexitFunc, (void *)20)) fprintf(stderr, "on_exit 2\n"); return 0; } /* on_exit func called: status=0, arg=20 */ /* atexit func2 called */ /* atexit func1 called */ /* on_exit func called: status=0, arg=10 */
等待调用进程的任一子进程终止, 同时在status所指向的缓冲区中返回子进程的终止状态.
#include<sys/wait.h>
pid_t wait(int *status);
wait()执行如下动作:
出错情况: (返回值为-1)
调用进程没有(先前未等待的)子进程, 此时errno置为ECHILD. 所以可以用下面代码检测所有子进程是否退出.
while (childPid = wait(NULL) != -1) continue; if (errno != ECHILD) exit(1); // error
- 1
- 2
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <errno.h> #include <sys/wait.h> #define BUF_SIZE 1000 #define NUM 4 int times[NUM] = {0, 7, 1, 4}; char *currTime(const char *format) { static char buf[BUF_SIZE]; /* Nonreentrant */ time_t t; size_t s; struct tm *tm; t = time(NULL); tm = localtime(&t); if (tm == NULL) return NULL; s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm); return (s == 0) ? NULL : buf; } int main(int argc, char *argv[]) { int numDead; pid_t childPid; int j; setbuf(stdout, NULL); for (j = 1; j < NUM; ++j) { switch (fork()) { case -1: fprintf(stderr, "fork\n"); case 0: printf( "[%s] child %d started with PID %ld, sleeping %d " "seconds\n", currTime("%T"), j, (long)getpid(), times[j]); sleep(times[j]); _exit(0); default: break; } } numDead = 0; for (;;) { childPid = wait(NULL); if (childPid == -1) { if (errno == ECHILD) { printf("No more children -bye!\n"); exit(0); } else { fprintf(stderr, "wait\n"); } } numDead++; printf("[%s] wait() return child PID %ld (numDead=%d)\n", currTime("%T"), (long)childPid, numDead); } } /* [08:09:53] child 2 started with PID 825604, sleeping 1 seconds */ /* [08:09:53] child 1 started with PID 825603, sleeping 7 seconds */ /* [08:09:53] child 3 started with PID 825605, sleeping 4 seconds */ /* [08:09:54] wait() return child PID 825604 (numDead=1) */ /* [08:09:57] wait() return child PID 825605 (numDead=2) */ /* [08:10:00] wait() return child PID 825603 (numDead=3) */
wait存在以下的一些限制:
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
其中, 返回值和status参数的描述与wait相同, 下面是参数pid的含义:
wait(&status);
等价于waitpid(-1, &status, 0);
.参数options是一个位掩码, 可以包含(按位或操作)0个或多个如下标志:
在这一过程中, 将丢弃旧有程序, 而进程的栈,数据以及堆内存都会被新程序的相应部件所替换.
在执行了各种C语言函数库的运行时启动代码以及程序的初始化代码之后, 新程序会从main()函数位置开始执行.
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
例子:
// envargs.c #include <stdio.h> extern char **environ; int main(int argc, char *argv[]) { int j; char **ep; for (j = 0; j < argc; ++j) printf("argv[%d]=%s\n", j, argv[j]); for (ep = environ; *ep != NULL; ++ep) printf("environ: %s\n", *ep); return 0; } // execve-1.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { char *argVec[10]; char *envVec[] = {"a=b", "c=d", NULL}; argVec[0] = strrchr(argv[1], '/'); if (argVec[0] != NULL) argVec[0]++; else argVec[0] = argv[1]; argVec[1] = "hello"; argVec[2] = "goodbye"; argVec[3] = NULL; execve(argv[1], argVec, envVec); fprintf(stderr, "execve\n"); return 0; }
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main (int argc, char *argv[])
{
system("ls | wc");
/* 13 13 170 */
return 0;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。