在之前的linux学习和实践中,对于一个进程的产生一般都是以下几个函数:
- fork()
- vfork()
- exec()族函数以及轻量级进程(线程)clone()等的使用
参考链接:
http://www.man7.org/linux/man-pages/man2/fork.2.html
https://blog.csdn.net/kunkliu/article/details/86621131
最近在看公司源码时看到一个函数system(),当时不是很懂就搜了一下,发现也是产生新的进程的一种方法,
接下来对该函数进行了简单的使用和跟踪:
#include<stdlib.h>
int system(const char *command)
使用:
1 #include<stdio.h>
2 #include<stdlib.h>
3 int main()
4 {
5 int n = system("ls -al");
6 printf("sys_ret = %d\n",n);
7 return 0;
8 }
我们可以用strace进行跟踪一下:
实际上我们启动一个新进程必定会调用很多的系统调用:
包括open一些文件如动态库等
brk() mmap() munmap()等申请开辟内存,我在会在下面打印结果中删除一些与研究system函数关系不大的一些系统调用
txp@txp-TM1801:~/mydir/study_note/0717$ strace ./2
//
execve("./2", ["./2"], [/* 85 vars */]) = 0
brk(NULL) = 0x1ce6000
//删除open access stat等系统调用
mmap(NULL, 140814, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fca717ec000
close(3) = 0
//...
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
//...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fca717eb000
//...
munmap(0x7fca717ec000, 140814) = 0
//这里应该是对SIGINT SIGQUIT等信号函数做处理
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fca712554b0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], SA_RESTORER, 0x7fca712554b0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
//产生子进程,父进程中wait()
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0x7ffd790627ac) = 26385
wait4(26385, total 40
//开始执行程序中给定system的命令
drwxrwxr-x 2 txp txp 4096 7月 18 09:59 .
drwxrwxr-x 3 txp txp 4096 7月 17 16:58 ..
-rwxrwxr-x 1 txp txp 9312 7月 17 17:05 1
-rw-rw-r-- 1 txp txp 191 7月 17 17:09 1.c
-rwxrwxr-x 1 txp txp 8656 7月 18 09:59 2
-rw-rw-r-- 1 txp txp 119 7月 18 09:59 2.c
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 26385
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fca712554b0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, [], SA_RESTORER, 0x7fca712554b0}, NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26385, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0
brk(NULL) = 0x1ce6000
brk(0x1d07000) = 0x1d07000
//在标准输出上打印sys_ret = 0这样的字符串
write(1, "sys_ret = 0\n", 12sys_ret = 0
) = 12
exit_group(0) = ?
+++ exited with 0 +++
看到这里有点不明白为什么调用那么多关于信号的系统调用,然后查看帮助手册
在博客上也看到大家发出来关系这个函数的内核的实现,基本搞明白了.
先看帮助手册怎么说:man system
system()库函数使用fork(2)来创建执行shell的子进程,然后使用execl(3)在命令中指定的命令如下:
execl(“/ bin / sh”,“sh”,“ - c”,command,(char *)0);命令完成后,system()返回。
在该command执行期间,SIGCHLD是被阻塞的,告诉内核此时不要给我送SIGCHLD信号,等到命令执行完毕再说;
在该command执行期间,SIGINT和SIGQUIT是被忽略的,意思是进程收到这两个信号后没有任何动作。
如果command为NULL,则system()返回指示shell是否可用的状态
(实际上,这就是我们看到在执行命令前,将对这些信号做了其他的处理,
如rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fca712554b0}, NULL, 8) = 0)
最后,看一下大家网上粘出来的一部分源码,还是比较好理解的代码:
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL)
{
return (1); //如果cmdstring为空,返回非零值,一般为1
}
if((pid = fork())<0)
{
status = -1; //fork失败,返回-1
}
else if(pid == 0)//用/bin/sh替换产生的子进程,并且执行命令cmdstring
{
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
// exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在
}
else //父进程等待子进程的结束,否则产生僵尸进程
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1; //如果waitpid被信号中断,则返回-1
break;
}
}
}
return status; //如果waitpid成功,则返回子进程的返回状态
}
那也就是说system()实际上也是对fork和exec类函数进行了封装,再加上waitpid()的调用
不同的是很固定的用shell来替换掉子进程,然后给shell传递命令字符串形式执行命令.
然后对于函数返回值也是明了,具体如果用到可以查看帮助手册和说明文档.
万变不离其宗.