房产网站建设ppt,小米商城网站开发文档,长宁苏州网站建设公司,花都高端网站建设tags: Linux OS Syscall C
写在前面
总结一下Linux系统的进程创建/终止/等待等系统调用, 参考:
Linux/Unix系统编程手册.
下面主要给出例子, 关于函数原型可以参考书中或者man 2 syscall(例如man 2 fork). 测试环境: Ubuntu 20.04 x86_64 gcc-9 进程创建: fork()
用于创建…
tags: Linux OS Syscall C
写在前面
总结一下Linux系统的进程创建/终止/等待等系统调用, 参考:
Linux/Unix系统编程手册.
下面主要给出例子, 关于函数原型可以参考书中或者man 2 syscall(例如man 2 fork). 测试环境: Ubuntu 20.04 x86_64 gcc-9 进程创建: fork()
用于创建新的进程, 创建出来的新进程称为子进程, 拥有和父进程一样的代码段/数据段/栈段/堆段.
所以创建新进程的资源消耗较大, 后续采用多线程方式可以解决这个问题.
由于这个函数的设计比较奇怪, 有两个返回值, 在父进程中返回子进程的进程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);/* PID526436 (child) idata333 istack666 *//* PID526435 (parent) idata111 istack222 */return 0;
}文件共享
#include stdio.h
#include stdlib.h
#include sys/stat.h
#include fcntl.h
#include sys/wait.h
#include unistd.hint 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: // childif (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: // parentif (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() 最好不要用vfork #include stdio.h
#include stdlib.h
#include unistd.hint main(int argc, char *argv[]) {int istack 222;switch (vfork()) {case -1:fprintf(stderr, vfork);exit(1);case 0: // childsleep(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 */
/* istack666 */子进程会共享父进程的内存, 父进程会一直挂起直到子进程终止或者调用exec
fork竞态条件与同步
竞态条件
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/wait.hint 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: // childprintf(%d child\n, j);_exit(EXIT_SUCCESS);default: // parentprintf(%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 its 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 tocomplete 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 */进程终止
exit和_exit
#include unistd.hvoid _exit(int status); // syscallstatus参数就是传入的终止状态.
#include stdlib.hvoid exit(int status); // libc会依次执行下面三个步骤:
调用退出处理程序: 通过atexit()和on_exit()注册的函数刷新stdio流缓冲区使用由status提供的值执行_exit()系统调用 执行return n;相当于执行exit(n); 进程终止后的细节
关闭所有打开的文件描述符, 目录流, 信息目录描述符以及转换描述符释放进程持有的所有文件锁分离任何已连接的System V共享内存段...
注册退出处理程序
#include stdlib.hint atexit(void (*func)(void)); // 出错返回非零值, 不一定为-1其中传入的参数为一个参数列表和返回值均为void的函数指针.
atexit函数会将传入的func函数指针加入到一个函数列表中, 进程终止时会调用该函数列表的所有函数.
以及一个类似的库函数: (可以传入状态)
#includestdlib.hint on_exit(void (*func)(int, void *), void *arg);其中传入的参数为一个参数列表为int和指针, 返回值为void的函数指针.
例子
#include stdio.h
#include stdlib.h
#include unistd.hstatic 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: status0, arg20 */
/* atexit func2 called */
/* atexit func1 called */
/* on_exit func called: status0, arg10 */监控子进程
wait(): 等待子进程的基本调用
等待调用进程的任一子进程终止, 同时在status所指向的缓冲区中返回子进程的终止状态.
#includesys/wait.hpid_t wait(int *status);wait()执行如下动作:
如果调用进程(指父进程)的一个(先前未等待的)子进程已经终止, 调用将一直阻塞, 直至某个子进程终止. 如果调用时已有子进程终止, wait将立即返回status非空, 则存入状态信息.内核将为父进程下所有子进程的运行总量追加进程CPU时间以及资源使用数据.将终止子进程的ID作为结果返回. 出错情况: (返回值为-1) 调用进程没有(先前未等待的)子进程, 此时errno置为ECHILD. 所以可以用下面代码检测所有子进程是否退出. while (childPid wait(NULL) ! -1) continue;
if (errno ! ECHILD) exit(1); // error例子
#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 (numDead1) */
/* [08:09:57] wait() return child PID 825605 (numDead2) */
/* [08:10:00] wait() return child PID 825603 (numDead3) */
waitpid(): 升级版的wait
wait存在以下的一些限制:
如果父进程已经创建了多个子进程, 使用wait将无法等待某一个确定的 子进程完成, 只能按顺序等待下一个子进程终止.如果没有子进程退出, wait总是保持阻塞, 但是有时候会希望执行非阻塞等待.使用wait只能发现那些已终止的子进程, 对于子进程因为哪个信号终止或者已停止的子进程收到SIGCONT信号后恢复执行的情况不得而知.
#include sys/wait.hpid_t waitpid(pid_t pid, int *status, int options);其中, 返回值和status参数的描述与wait相同, 下面是参数pid的含义:
pid0pid0pid0: 等待进程的PID为pid的子进程.pid0pid0pid0: 等待与调用进程(父进程)同一个进程组的所有子进程.pid−1pid-1pid−1: 等待进程组标识符与pid绝对值相等的所有子进程.pid−1pid-1pid−1: 等待任意子进程. wait(status);等价于waitpid(-1, status, 0);.
参数options是一个位掩码, 可以包含(按位或操作)0个或多个如下标志:
WUNTRACED: 除了返回终止子进程的信息外, 还返回因信号而停止的子进程信息WCONTINUED: 返回因收到SIGCONT信号而恢复执行的已停止子进程的状态信息WNOHANG: pid指定的子进程状态未改变, 返回, 不阻塞(poll,轮询), waitpid返回0.调用进程没有与pid匹配的子进程, 报错, errnoECHILD.
程序的执行
execve: 将新程序加载到某一进程的内存空间 在这一过程中, 将丢弃旧有程序, 而进程的栈,数据以及堆内存都会被新程序的相应部件所替换. 在执行了各种C语言函数库的运行时启动代码以及程序的初始化代码之后, 新程序会从main()函数位置开始执行. #include unistd.hint execve(const char *pathname, char *const argv[], char *const envp[]);例子:
// envargs.c
#include stdio.hextern 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.hint main(int argc, char *argv[]) {char *argVec[10];char *envVec[] {ab, cd, NULL};argVec[0] strrchr(argv[1], /);if (argVec[0] ! NULL)argVec[0];elseargVec[0] argv[1];argVec[1] hello;argVec[2] goodbye;argVec[3] NULL;execve(argv[1], argVec, envVec);fprintf(stderr, execve\n);return 0;
}system: 执行系统命令
#include stdio.h
#include stdlib.h
#include unistd.hint main (int argc, char *argv[])
{system(ls | wc);/* 13 13 170 */return 0;
}