OSTEP:5 Process API Homework

Process API Homework

CC=gcc
GCCFLAGS= -g -Wall
TARGET = $(word 1,$(MAKECMDGOALS))
SRC=$(word 1,$(MAKECMDGOALS)).c

$(TARGET):$(SRC)
$(CC) $(GCCFLAGS) $(SRC) -o $(TARGET)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
int main() {
int x = 1;
printf("%d\n", x);
x = 2;
pid_t rc = fork();
if (rc < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (rc == 0) {
printf("fork %d\n", x);
x = 3;
printf("fork %d\n", x);
}
else {
wait(NULL);
printf("%d\n", x);
}

}
grxer@grxer /m/h/S/O/o/cpu-api> make 1
gcc -g -Wall 1.c -o 1
1.c: In function ‘main’:
1.c:20:9: warning: implicit declaration of function ‘wait’ [-Wimplicit-function-declaration]
wait(NULL);
^
grxer@grxer /m/h/S/O/o/cpu-api> ./1
1
fork 2
fork 3
2

子进程和父进程变量没有任何关系

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #define NDEBUG
#include <assert.h>
int main() {
int fd = open("./flag",O_RDWR|O_APPEND);
assert(-1 != fd);
int pid = fork();
if (pid < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (0==pid){
char buf[100] = { 0 };
read(fd, buf, sizeof(char) * 10);
printf("child:%s\n", buf);
memcpy(buf, "child", strlen("child"));
for (size_t i = 0; i < 10; i++)
{
int err = write(fd, buf, strlen(buf) * sizeof(char));
assert(err);
}

}
else {
char buf[100] = { 0 };
read(fd, buf, sizeof(char) * 10);
printf("parent:%s\n", buf);
// usleep(100);
memcpy(buf, "parent", strlen("parent"));
for (size_t i = 0; i < 10; i++)
{
int err = write(fd, buf, strlen(buf) * sizeof(char));
assert(err);
}
wait(NULL);
}
}
grxer@grxer /m/h/S/O/o/cpu-api> cat flag
1111111111xxxxxxxxxxx
grxer@grxer /m/h/S/O/o/cpu-api> ./2
parent:1111111111
child:xxxxxxxxxx
grxer@grxer /m/h/S/O/o/cpu-api> cat flag
1111111111xxxxxxxxxxx
parent1111parent1111childxxxxxchildxxxxxparent1111parent1111childxxxxxchildxxxxxparent1111parent1111childxxxxxparent1111parent1111childxxxxxchildxxxxxparent1111parent1111childxxxxxchildxxxxxchildxxxxx⏎

可以看出父子是共享文件描述符的,父进程读后文件的读写位置往后移到了xxxxx,可以用 lseek(fd, 0, SEEK_SET);把他移到开头

并发的写入由于操作系统调度顺序问题,写入顺序是随机的,竞争关系的他们并不能同时使用fd,操作系统应该加了一些锁

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = 10;
int pid = vfork();
if (pid < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (0 == pid) {
x = 66;
printf("hello ");
exit(0);
}
else {
printf("world-----");
printf("%d",x);
}
}
grxer@grxer /m/h/S/O/o/cpu-api> make 3
gcc -g -Wall 3.c -o 3
grxer@grxer /m/h/S/O/o/cpu-api> ./3
hello world-----66⏎

可以调用vfork函数,和fork函数的区别就是他会阻塞父进程,直到子进程调用exec或exit才恢复调度可能,在此之前和父进程共享所以内存包括栈!可以看的x已经被修改

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char* argv[], char* envp[]) {
int pid = fork();
char* cmd = "/bin/ls";
char* name = "ls";
char* arg[] = { "ls", "-a", "-l", "-h", NULL };

if (pid < 0) {
perror("error");
exit(1);
}
else if (pid == 0) {

// execl(cmd, "ls", "-a", "-l", "-h", NULL);
execlp(name, "ls","-a", "-l", "-h",NULL);
// execve(cmd, arg, envp);
// execv(cmd, arg);
// execvp(name, arg);
// execle(cmd, "ls", NULL, envp);
}
else {
}
}

以exec为前缀

  • l(list)使用参数地址列表,以空指针结束标志execl("/bin/ls", "ls", "-a", "-l", "-h", NULL);。第二个参数ls没有太大意义,如果要给ls传参必须写上这个字符串,
  • v(vector) 以 NULL结尾字符串数组的指针作参数
  • p(path) PATH环境变量指定的目录搜索可执行文件,可以不用写路径
  • e(environment) 可以传存有环境变量envp字符串地址的指针数组的地址,无后缀e的话使用当前进程环境变量
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
int main() {
int pid = vfork();
if (pid < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (0 == pid) {
puts("gr");
int rs = wait(NULL);
printf("%d\n", rs);
exit(0);
}
else {
int rs = wait(NULL);
assert(pid == rs);
}
}
rxer@grxer /m/h/S/O/o/cpu-api> make 5
gcc -g -Wall 5.c -o 5
grxer@grxer /m/h/S/O/o/cpu-api> ./5
gr
-1

父进程wait成功等待了返回子进程id,否则-1,子进程返回-1

其他更精细的事情,比如非阻塞等待:希望子进程退出能够被我父进程检测到,同时我又不希望我父进程处于阻塞等待waitpid(pid,NULL,WNOHANG)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
int main() {
int pid = fork();
if (pid < 0) {
fprintf(stderr, "fork failed\n");
exit(1);
}
else if (0 == pid) {
printf("child hello");
close(STDOUT_FILENO);
printf("grxer");
exit(0);
}
else {
printf("parent : hello");
wait(NULL);
printf(" world\n");
}
}
grxer@grxer /m/h/S/O/o/cpu-api> make 7
gcc -g -Wall 7.c -o 7
grxer@grxer /m/h/S/O/o/cpu-api> ./7
parent : hello world

没有输出的同时可以看出子进程继承了文件描述符,但是继承过后就是私有的了,不会影响父进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <sys/wait.h>
int main() {
pid_t pid[2];
int pidfd[2];
int res = pipe(pidfd);
assert(0 == res);
pid[0] = fork();
if (pid[0] < 0) {
perror("fork1 fail");
exit(-1);
}
else if (0 == pid[0]) {
close(pidfd[0]);//read
assert(dup2(pidfd[1], STDOUT_FILENO) == STDOUT_FILENO);
printf("i am from pid[0]");
close(pidfd[1]);
}
else {
pid[1] = fork();
if (pid[1] < 0) {
perror("fork2 fail");
exit(-2);
}
else if (0 == pid[1]) {
char buf[30] = {0};
close(pidfd[1]);//write
assert(dup2(pidfd[0], STDIN_FILENO) == STDIN_FILENO);
read(STDIN_FILENO,buf,30);
printf("pid[1]:where are you from??\n %s",buf);
close(pidfd[0]);
}
else {
waitpid(pid[0], NULL, 0);
waitpid(pid[1], NULL, 0);
}
}

}
grxer@grxer /m/h/S/O/o/cpu-api> make 8
gcc -g -Wall 8.c -o 8
grxer@grxer /m/h/S/O/o/cpu-api> ./8
pid[1]:where are you from??
i am from pid[0]⏎
#include <unistd.h>
int pipe(int pipefd[2]);
成功返回0 失败返回-1

管道:半双工通道,只允许数据在一个方向上传输的通道。发送方和接收方不能同时发送数据,只能轮流进行发送和接收。用于进程间通信

pipe函数创建一个管道,pipefd返回管道两端的文件描述符,pipefd[0]是读端,pipefd[1]是写端

管道可以理解为进程共用的内核空间里的一块内存来充当虚拟文件

img

#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
On success, these system calls return the new descriptor. On error, -1
is returned, and errno is set appropriately.

dup函数创建一个oldfd文件描述符的副本(而且是动态的副本,本质上是一个,只不过起了不同名字),不过不共享文件描述符标志,返回新文件描述符是取系统当前可用的最小整数值

dup2 和dup功能一样,不过它可以用newfd指定返回的新文件描述符号,如果newfd是一个已经打开的描述符,会把他先关闭