1 进程命令
1.1 命令详解
ps
当前终端进程
1
2
3
PID TTY TIME CMD
1977 pts/0 00:00:00 bash
11858 pts/0 00:00:00 ps
ps -a
所有进程,包含其他终端与其他用户运行的程序
ps -la
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 1000 1095 1093 0 80 0 - 342745 ep_pol tty1 00:00:03 Xorg
0 S 1000 1167 1093 0 80 0 - 138634 poll_s tty1 00:00:00 gnome-session-b
0 S 1000 1351 1167 0 80 0 - 1017041 poll_s tty1 00:00:48 gnome-shell
0 S 1000 1427 1351 0 80 0 - 89093 poll_s tty1 00:00:00 ibus-daemon
0 S 1000 1431 1427 0 80 0 - 68908 poll_s tty1 00:00:00 ibus-dconf
0 S 1000 1433 1 0 80 0 - 84823 poll_s tty1 00:00:00 ibus-x11
0 S 1000 1528 1167 0 80 0 - 217312 poll_s tty1 00:00:00 gsd-power
0 S 1000 1530 1167 0 80 0 - 86031 poll_s tty1 00:00:00 gsd-print-notif
0 S 1000 1534 1167 0 80 0 - 104535 poll_s tty1 00:00:00 gsd-rfkill
0 S 1000 1535 1167 0 80 0 - 67632 poll_s tty1 00:00:00 gsd-screensaver
0 S 1000 1541 1167 0 80 0 - 111945 poll_s tty1 00:00:03 gsd-sharing
0 S 1000 1545 1167 0 80 0 - 93251 poll_s tty1 00:00:00 gsd-smartcard
0 S 1000 1546 1167 0 80 0 - 82477 poll_s tty1 00:00:00 gsd-sound
0 S 1000 1552 1167 0 80 0 - 122481 poll_s tty1 00:00:00 gsd-xsettings
0 S 1000 1559 1167 0 80 0 - 124927 poll_s tty1 00:00:00 gsd-wacom
0 S 1000 1562 1167 0 80 0 - 84737 poll_s tty1 00:00:00 gsd-clipboard
0 S 1000 1563 1167 0 80 0 - 68271 poll_s tty1 00:00:00 gsd-a11y-settin
0 S 1000 1564 1167 0 80 0 - 116168 poll_s tty1 00:00:00 gsd-datetime
0 S 1000 1568 1167 0 80 0 - 165818 poll_s tty1 00:00:03 gsd-color
0 S 1000 1571 1167 0 80 0 - 125430 poll_s tty1 00:00:00 gsd-keyboard
0 S 1000 1574 1167 0 80 0 - 89846 poll_s tty1 00:00:01 gsd-housekeepin
0 S 1000 1578 1167 0 80 0 - 68273 poll_s tty1 00:00:00 gsd-mouse
0 S 1000 1584 1167 0 80 0 - 269241 poll_s tty1 00:00:00 gsd-media-keys
0 S 1000 1620 1167 0 80 0 - 92014 poll_s tty1 00:00:00 indicator-messa
0 S 1000 1624 1167 0 80 0 - 67984 poll_s tty1 00:00:00 gsd-disk-utilit
0 S 1000 1635 1167 0 80 0 - 205580 poll_s tty1 00:00:00 nautilus-deskto
0 S 1000 1671 1 0 80 0 - 125892 poll_s tty1 00:00:00 gsd-printer
0 S 1000 1730 1427 0 80 0 - 77375 poll_s tty1 00:00:00 ibus-engine-lib
0 S 1000 1991 1167 0 80 0 - 343747 poll_s tty1 00:00:03 gnome-software
0 S 1000 1993 1167 0 80 0 - 153307 poll_s tty1 00:00:00 update-notifier
0 R 1000 11872 1977 0 80 0 - 7669 - pts/0 00:00:00 ps
S 进程是否运行。
R
运行,S
停止。UID 用户id
PID 进程id
PPID 父进程id
PRI/NI 进程的优先级
SZ 进程大小,进程占内存大小
WCHAN 进程睡眠原因
ps -ax
系统进程
1.2 shell功能
运行程序 ls,date等。
管理输入输出
>
,<
输入输出重定向等。shell编程,脚本 略
1.3 shell 是如何运行程序的
用户键入
shell建立一个新的程序来运行这个程序
shell 将程序从磁盘载入
程序在它的进程中运行直到结束
所以,要建立一个自己的shell,需要下面三个步骤:
- 运行一个程序
- 建立一个进程
- 等待exit()
2.实现一个自己的进程
2.1 一个程序如何运行另外一个程序
函数原型: int execvp(const char *file, char *const argv[]);
Unix如何运行一个程序:
将指定的程序复制到调用它的进程。
将指定的字符串作为arfv[] 传给这个程序
运行这个程序
简单的来说,如下:
- 程序调用execvp
- 内核从磁盘将程序载入
- 内核将arglist赋值到进程
- 内核调用main(argc,argv)
一个ls的demo
1
2
3
4
5
6
7
8
9
10
11
int main(){
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-1";
arglist[2] = 0;
printf("*** about to exec ls -1\n");
execvp("ls",arglist);
printf("*** ls is done .bye\n");
}
问题
第二条消息为什么没有打印?
内核将新程序载入到当前进程,替换当前进程的数据与代码
execvp就像换脑
execvp 载入由file指定的程序到当前进程中,然后试图运行他。 execvp 将以NULL 结尾的字符串列表传递给程序。execvp在环境变量PATH所指定的路径中查找file文件。
如果执行成功,execvp没有返回值。当前程序从进程中清除,新的程序在当前进程中运行
2.2 shell(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#define MAXARGS 20
#define ARGLEN 100
int main(){
char *arglist[MAXARGS + 1];
int numargs;
char argbuf[ARGLEN];
char * makestring();
int execute();
numargs = 0;
while (numargs < MAXARGS){
printf("Arg[%d]?",numargs);
if (fgets(argbuf,ARGLEN,stdin) && argbuf !='\n')
arglist[numargs++] = makestring(argbuf);
else{
if (numargs > 0){
arglist[numargs] = NULL;
execute(arglist);
numargs = 0;
}
}
}
return 0;
}
int execute (char *arglist[]){
execvp(arglist[0],arglist);
perror("execvp failed");
exit(1);
}
char *makestring(char *buf){
char *cp;
buf[strlen(buf) - 1] = '\0';
cp = malloc(strlen(buf) + 1);
if (cp == NULL){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp,buf);
return cp;
}
运行完后,该shell退出,并没有像我们设想中的,可以继续接受命令
2.3 fork()
fork的基本流程:
- 分配新的内存块和内核数据结构
- 复制原来的进程到新的进程
- 向运行进程集添加新的进程
- 将控制返回给两个进程
fork demo1:
1
2
3
4
5
6
7
8
9
10
11
int main(){
int ret_from,mypid;
mypid = getpid();
printf("Before: mypid is %d\n",mypid);
ret_from = fork();
sleep(1);
printf("After : my pid is %d , fork() said %d\n",getpid(),ret_from);
}
fork demo2:
1
2
3
4
5
6
7
8
9
10
int main(){
printf("my pid is %d\n",getpid());
fork();
fork();
fork();
printf("my pid is %d\n",getpid());
}
fork 叉子函数 综合来讲,fork() 函数,子进程返回0,父进程返回子进程的id。
2.4 wait()
首先想明白一个问题,shell 创建两个进程,由子进程执行命令,那么问题来了,主进程如何等待子进程的退出?
函数原型: pid_t wait(int *wstatus);
wait 暂停调用它的进程直到子进程结束 。 子进程与父进程并行运行,父进程调用wait,内核将父进程挂起直到紫禁城结束。
- 子进程结束会返回
exit(n)
, 该值由status
接受。 - 返回值,on success, returns the process ID of the terminated child; on error, -1 is returned.
demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main(){
int newpid;
void child_code(),parent_code();
printf("before: mypid is %d\n",getpid());
if ((newpid = fork()) == -1){
perror("fork error");
} else if ( newpid == 0){
child_code();
} else{
parent_code(newpid);
}
}
void child_code(){
printf(" this is children code runing ..., pid is %d\n",getpid());
sleep(DELAY);
exit(17);
}
void parent_code(int childpid){
printf(" parent is runing\n");
int wait_rv;
int status = 1; // 这里的status 还是有点问题
wait_rv = wait(&status);
printf("wait_rv is : %d\n",wait_rv);
printf("status is %d\n",status);
}
父进程阻塞一直等到子进程调用
exit()
。 比如,父进程调用fork创建一个子进程来对一个文件进行排序,父进程必须等到子进程完成才能处理这个文件。 系统调用wait
与exit
来协调这些。wait() 返回的是结束进程的PID,wait返回的是调用exit的子进程的PID, 一个父进程可以创建多个子进程。wait的返回值告诉父进程,哪个任务结束了。
进程的三种结束方式(成功,失败,死亡)
成功 exit(n); 略。
失败 内存耗尽,等。Unix 在程序遇到问题退出时,需要调用exit()传递给他一个非零的值,程序员通过该值判断。通过status判断。
死亡(被kill 信号杀死,又没有相应的捕捉信号) 一个没有被忽略又没有被捕捉的信号会杀死进程。
一个wait status 的demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main(){
int newpid;
void child_code(),parnet_code();
printf("before:mypid is %d\n",getpid());
if ((newpid = fork()) == -1)
perror("fork");
else if ( newpid == 0)
child_code(DELAY);
else
parnet_code(newpid);
}
void child_code(int delay){
printf("child %d here ,will sleep for %d seconds\n",getpid(),delay);
sleep(delay);
printf("child done ,about to exit\n");
exit(17);
}
void parnet_code(int childpid){
int wait_rv;
int child_status;
int hight_8,low_7,bit_7;
wait_rv = wait(&child_status);
printf("done waiting for %d,Wait returned: %d\n",childpid,wait_rv);
hight_8 = child_status >> 8;
low_7 = child_status & 0x7F;
bit_7 = child_status & 0x80;
printf("status is : %d\n",hight_8);
printf("sig is :%d\n",low_7);
printf("core is : %d\n",bit_7);
}
总结一下wait
子进程的三种退出方式,成功,失败(内存不够),被其他进程的信号量杀死。
status 为16位二进制数,其中:前8个bit记录的是exit(n)中的n值,中间1bit记录产生错误的内核映像,最后7bit记录信号序号(进程被杀死的信号量)
- 成功 由前8bit记录
- 失败 由前8bit记录
- 被杀死 由后7bit记录
3.实现一个真正的shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#define MAXARGS 20
#define ARGLEN 100
void execute(char*[]);
int main(){
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
char *makestring();
numargs = 0;
while ( numargs < MAXARGS){
printf("Arg[%d]?",numargs);
if ( fgets(argbuf,ARGLEN,stdin) && *argbuf != '\n')
arglist[numargs++] = makestring(argbuf);
else{
if (numargs > 0){
arglist[numargs] = NULL;
execute(arglist);
numargs = 0;
}
}
}
return 0;
}
void execute(char *arglist[]){
int pid,exitstatus;
pid = fork();
switch(pid){
case -1:
perror("fork fail");
exit(1);
case 0:
execvp(arglist[0],arglist);
perror("execvp error");
exit(1);
default:
while (wait(&exitstatus) != pid);
printf("child exited with sattus %d,%d\n",exitstatus>>8,exitstatus&0377);
}
}
char *makestring(char *buf){
char *cp;
buf[strlen(buf) -1] = '\0';
cp = malloc(strlen(buf)+1);
if (cp == NULL){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp,buf);
return cp;
}
这个shell 程序还是有一个小bug,在父进程等待子进程运行的过程中,如果按下ctr + c
,父进程与子进程会一起结束。因为这些进程都是终端进程的子进程,并没有捕捉SIGINT
信号,会被一起杀死
4. 关于进程编程的一点总结
c程序的call 与 return
一个函数调用另外一个函数,然后return。
exec/exit
一个c程序可以fork/exec 另外一个程序,并传给它一些参数。被调用的程序通过exit(n)来返回返回值。调用它的进程通过wait(&status)来获得返回值。
p255 exit 与 exce 的细节,有空再看。