c(4)文件锁+进程

1)文件锁

为了避免在读写同一个文件的同一个区域时发生冲突,进程之间应该遵循以下规则:如果一个进程正在写,那么其他进程既不能写也不能读;如果一个进程正在读,那么其他进程不能写但是可以读。


2)
文件元数据(文件属性信息)

//node.c

输出:

-rrSr-rr--

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
//得到文件类型、权限等信息
const char *mtos(mode_t m){
	static char s[11];
	if(S_ISDIR(m))//是否目录
	    strcpy(s,"d");
	else if(S_ISSOCK(m))//是否本地套接字
	    strcpy(s,"s");
	else if(S_ISCHR(m))//是否字符设备
	    strcpy(s,"c");
	else if(S_ISBLK(m))//是否块设备
	    strcpy(s,"b");
	else if(S_ISLNK(m))//是否符号链接
	    strcpy(s,"l");
	else if(S_ISFIFO(m))//是否有名管道
	    strcpy(s,"p");
	else
	    strcpy(s,"-");//是否普通文件
	//用户读、写、执行
	strcat(s,m&S_IRUSR?"r":"-");
	strcat(s,m&S_IWUSR?"r":"-");
	strcat(s,m&S_IXUSR?"r":"-");
	//组成员读、写、执行
	strcat(s,m&S_IRGRP?"r":"-");
	strcat(s,m&S_IWGRP?"r":"-");
	strcat(s,m&S_IXGRP?"r":"-");
	//其他人读、写、执行
	strcat(s,m&S_IROTH?"r":"-");
	strcat(s,m&S_IWOTH?"r":"-");
	strcat(s,m&S_IXOTH?"r":"-");
	if(m&S_ISUID)//用户ID
	    s[3]=(s[3]=='x'?'s':'S');
	if(m&S_ISGID)//组ID
	    s[3]=(s[3]=='x'?'s':'S');
	if(m&S_ISVTX)//粘滞位
	    s[3]=(s[3]=='x'?'s':'S');
	return s;
}
int main(){
	int fd=open("data.txt",O_RDWR,0666);
	if(fd==-1){
		perror("open");
		exit(EXIT_FAILURE);
	}
	struct stat statdata;
	fstat(fd,&statdata);//用函数fstat提取文件fd的元数据
	printf("%s\n",mtos(statdata.st_mode));
	if(close(fd)==-1){
		perror("close");
		exit(EXIT_FAILURE);
	}
}

硬链接:[link函数]

本质就是目录文件里一个文件名和i节点号的对应条目,通过该节点,就可以根据一个文件的文件名迅速地找到与之相对应的节点号,进而访问该文件的数据。
软链接(符号链接):[symlink函数]
本质就是一个保存着另一个文件或目录的路径的文件。所有针对符号链接文件的访问,最后都会被定向到该符号链接所引用的目标文件或目录。


3)

创建子进程

//02pid.c

输出:

执行fork前带有换行符
执行fork前没有换行符 这是子进程,pid=3472,父进程的pid=3471
,pid=3472,glob=7,var=89
[停顿10秒]执行fork前没有换行符 这是父进程,pid=3471
pid=3471,glob=6,var=88

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int glob=6;
int main(){
	int var=88;
	pid_t pid;
//调用fork函数前的代码只有父进程执行,fork函数成功返回后
//的代码子进程和父进程都会执行,受逻辑控制进入不同分支。
//printf函数带\n和不带\n是有区别的,因为不带\n时,并不是马上
//更新显示缓冲区,所以造成好像子和父进程都执行了此语句的假象
	printf("执行fork前带有换行符\n");
	printf("执行fork前没有换行符 ");
	if((pid=fork())<0){
		perror("fork");
	}//fork函数创建子进程,调用一次,在子进程和父进程中各
	//返回一次,在子进程中返回所创建子进程的PID,而在子进程
	//中返回0
	else if(pid==0){
		printf("这是子进程,pid=%d,",getpid());
		printf("父进程的pid=%d\n,",getppid());
		glob++;
		var++;
	}
	else{
		sleep(10);
		printf("这是父进程,pid=%d\n",getpid());
	}
	printf("pid=%d,glob=%d,var=%d\n",getpid(),glob,var);
	return 0;
}

创建轻量级子进程
vfork函数创建的子进程不复制父进程的物理内存,也不拥有自己独立的内存映射,而是与父进程共享全部地址空间。vfork函数会在创建子进程的同时挂起其父进程,直到子进程终止,或通过exec函数启动了另一个可执行程序。

//05vfork.c

输出:

父进程开始执行。
子进程开始执行。
excel: No such file or directory
[停顿1秒]父进程执行结束。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
	printf("父进程开始执行。\n");
	pid_t pid=vfork();
	if(pid==-1){
		perror("vfock");
		exit(EXIT_FAILURE);
	}
	if(pid==0){
		printf("子进程开始执行。\n");
		if(execl("ls","ls","-l",NULL)==-1){
			perror("excel");
			_exit(EXIT_FAILURE);
		}
	}
	sleep(1);
	printf("父进程执行结束。\n");
	return 0;
}



父子进程共享文件表
fork函数成功返回后,系统内核维护的文件描述符也被复制到子进程的进程表项中,文件表项并不复制,所以子进程和父进程写入统一文件

//04fork.c

输出:

111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024
111111111->0,buf[0]=0,writed=1024

......


data.txt内容:

111111111111111111111111111111111111111111111111111111111111111111111    111111111111111111111111111111111111111111111111111111111111111111111    111111111111111111111111111111111111111111111111111111111111111111111    111111111111111111111111111111111111111111111111111111111111111111111    111111111111111111111111111111111111111111111111111111111111111111111    111111111111111111111111111111111111111111111111111111111111111111111    111111111111111111111111111111111111111111111111111111111111111111111

......

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#define BUFSIZE 1024*1024
bool lock(int fd){
	struct flock lock;
	lock.l_type=F_WRLCK;
	lock.l_whence=SEEK_SET;
	lock.l_start=0;
	lock.l_len=0;
	lock.l_pid=-1;
	if(fcntl(fd,F_SETLK,&lock)==-1){
		if(errno!=EAGAIN){
			perror("fcntl");
			exit(EXIT_FAILURE);
		}
		return false;
	}
	return true;
}
void unlock(int fd){
	struct flock lock;
	lock.l_type=F_UNLCK;
	lock.l_whence=SEEK_SET;
	lock.l_start=0;
	lock.l_len=0;
	lock.l_pid=-1;
	if(fcntl(fd,F_SETLKW,&lock)==-1){
		perror("fcntl");
		exit(EXIT_FAILURE);
	}
}
//函数writedata向文件fd写入数据
void writedata(int fd,char *buf,char c){
	while(!lock(fd));//设置写锁,防止子进程和父进程同时写入造成数据混乱
	for(int i=0;i<BUFSIZE-1;i++)
	    buf[i]=c;
	for(int i=0;i<1024;i++){
	    	int writed;
		if((writed=write(fd,buf+i,1024))==-1){
			perror("write");
			exit(EXIT_FAILURE);
		}
		printf("111111111->%c,buf[0]=%c,writed=%d\n",c,*(buf+i),writed);
	}
	unlock(fd);
}
int main(){
	int fd=open("data.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
	if(fd==-1){
		perror("open");
		exit(EXIT_FAILURE);
	}
	pid_t pid;
	if((pid=fork())<0){
		perror("fork");
		return 1;
	}
	if(pid==0){//子进程向文件中写入0
		char buf[BUFSIZE]={};
		writedata(fd,buf,'0');
	}
	else{//父进程向文件中写入1
		char buf[BUFSIZE]={};
		writedata(fd,buf,'1');
	}
	if(close(fd)==-1){
		perror("close");
		exit(EXIT_FAILURE);
	}
	return 0;
}


4)
孤儿与僵尸
孤儿:
子进程被暂停了10秒钟,所以当父进程退出时,子进程仍然未退出。这样子进程即成为孤儿进程。当暂停的10秒钟结束时,子进程的父进程变成了1,即init进程,又被称为孤儿院进程。

//01.c

输出:

y@y-Lenovo-ideapad-310S-14IKB:~/case8$ ./a.out
这是父进程,pid=3686[等待10秒]
y@y-Lenovo-ideapad-310S-14IKB:~/case8$ 这是子进程,pid=3687父进程的pid=1221[进程未退出]

^C

/孤儿进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void){
	pid_t pid;
	if((pid=fork())<0){
		perror("fork");
	}
	else if(pid==0){
		sleep(10);
		printf("这是子进程,pid=%d",getpid());
		printf("父进程的pid=%d\n",getppid());
	}
	else{
		printf("这是父进程,pid=%d\n",getpid());
	}
	return 0;
}
僵尸:
指定父进程休眠的秒数,在这10秒内,子进程已经退出,而父进程休眠,不可能对它进行收集,这样,我们就能获得10秒的僵尸状态。
//02.c

输出:

这是子进程,pid=3722父进程的pid=3721[等待10秒]

这是父进程,pid=3721

//僵尸进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void){
	pid_t pid;
	if((pid=fork())<0){
		perror("fork");
	}
	else if(pid==0){
		printf("这是子进程,pid=%d",getpid());
		printf("父进程的pid=%d\n",getppid());
	}
	else{
	    	sleep(10);
		printf("这是父进程,pid=%d\n",getpid());
	}
	return 0;
}

5)
进程的正常终止
<stdlib.h>
void exit(int status);
int atexit(void (*function)(void));
int on_exit(void (*function)(int , void *), void *arg);

void exit(int status);
exit函数调用,立即令调用进程终止

int atexit(void (*function)(void));

函数atxit为注册退出处理函数,该函数有一个参数是退出处理函数指针,所指向的函数必须既无返回值亦无参数,在进程终止前被调用,为进程料理临终事宜。atexit函数本身并不调用退出处理函数指针,只是将doexit参数所表示的退出处理函数地址,保存(注册)在系统内核的某个地方(进程表项)。待到exit函数被调用或在main函数里执行return语句时,再由系统内核根据这些退出处理函数的地址来调用它们,此过程亦称回调。

//06atexit.c
输出:
主函数退出了。

注册的退出处理函数被调用了。

#include <stdio.h>
#include <stdlib.h>
void doexit(){
	printf("注册的退出处理函数被调用了。\n");
}
int main(){
	atexit(doexit);
	printf("主函数退出了。\n");
	return 0;
}


int on_exit(void (*function)(int , void *), void *arg);

使用函数on_exit注册退出处理函数,该函数有两个参数,第一个参数为退出处理函数指针,所指向的函数必须无返回值但有两个参数。其中一个参数来自传递给exit函数的statues参数或在main函数里执行return语句的返回值,而第二个参数则来自传递给on_exit函数的arg参数。该函数在进程终止前被调用,为进程料理临终事宜。第二个参数为泛型指针,将作为第二个参数传递给function所指向的退出处理函数。

//06on_exit.c
输出:
主函数退出了。

注册的退出处理函数被调用了。

#include <stdio.h>
#include <stdlib.h>
//定义退出处理函数,用作退出处理函数,处理函数退出前必做的一些工作
void doexit(int status,void *arg){
	printf("%s",(char *)arg);
}
int main(){
	on_exit(doexit,"注册的退出处理函数被调用了。\n");
	printf("主函数退出了。\n");
	return 0;
}


[_exit、_Exit和exit的区别:

_exit、_Exit函数和exit函数一样也是正常终止调用,但它们影响的完全是调用进程自己的资源,与其父进程没有任何关系。在用vfork函数创建的子进程存在太多与其父进程共享的资源。如果在子进程里调用了exit函数或在子进程的main函数里执行了return语句(这二者在本质上是等价的),不仅销毁了子进程的资源,同时也销毁了父进程的资源,而父进程恰恰要在子进程终止以后从挂起中恢复运行,其后果可想而知。
]


6)
<sys/types.h>
<sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

回收任意或所有子进程
pid_t wait(int *wstatus);
父进程在创建若干子进程以后调用wait函数,此时若所有子进程都在运行,则wait阻塞,直至有子进程终止;若有一个子进程已终止,则wait返回该子进程的PID并通过status参数(若非NULL)输出其终止状态;若没有需要等待的子进程,则wait返回-1,置errno为ECHILD。
//07.c
输出:
这是父进程,pid=5926
这是子进程,pid=5928,父进程的pid=5926
这是子进程,pid=5927,父进程的pid=5926

5927子进程终止5928子进程终止子进程都死光了

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
int main(){
	pid_t pid1;
	if((pid1=fork())<0)
	    perror("fork");
	if(pid1==0){
		printf("这是子进程,pid=%d,",getpid());
		printf("父进程的pid=%d\n",getppid());
		_exit(0);
	}
	pid_t pid2;
	if((pid2=fork())<0)
	    perror("fork");
	if(pid2==0){
		printf("这是子进程,pid=%d,",getpid());
		printf("父进程的pid=%d\n",getppid());
		_exit(0);
	}
	printf("这是父进程,pid=%d\n",getpid());
	//等待并回收所有子进程,忽略其终止状态。
	while(1){
		pid_t pid=wait(NULL);
		if(pid==-1){
			if(errno!=ECHILD){
				perror("wait");
				exit(EXIT_FAILURE);
			}
			printf("子进程都死光了\n");
			break;
		}
		printf("%d子进程终止",pid);
	}
	return 0;
}


非阻塞模式回收所有子进程

pid_t waitpid(pid_t pid, int *wstatus, int options);
wstatus为输出子进程的终止状态,可置NULL
options=0,阻塞模式
options=WNOHANG,非阻塞模式
使用函数waitpid以非阻塞方式回收任意子进程
//09.c
输出:
这是父进程,pid=6420
在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处�这是子进程,pid=6422,父进程的pid=6420
这是子进程,pid=6421,父进程的pid=6420
��在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理在这里进行空闲处理6421子进程终止
6422子进程终止

子进程都死光了

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
int main(){
	pid_t pid1;
	if((pid1=fork())<0)
	    perror("fork");
	if(pid1==0){
		printf("这是子进程,pid=%d,",getpid());
		printf("父进程的pid=%d\n",getppid());
		_exit(0);
	}
	pid_t pid2;
	if((pid2=fork())<0)
	    perror("fork");
	if(pid2==0){
		printf("这是子进程,pid=%d,",getpid());
		printf("父进程的pid=%d\n",getppid());
		_exit(0);
	}
	printf("这是父进程,pid=%d\n",getpid());
//非阻塞模式等待并回收所有子进程,等待的同时做空闲处理。
	while(1){
		pid_t pid=waitpid(-1,NULL,WNOHANG);
		if(pid==-1){
//errno==ECHILD表示已没有可回收的子进程了
			if(errno!=ECHILD){
				perror("wait");
				exit(EXIT_FAILURE);
			}
			printf("子进程都死光了\n");
			break;
		}
		if(pid)
//成功回收该子进程
		    printf("%d子进程终止\n",pid);
		else 
//所等子进程仍在运行,此时父进程出现空闲时间,可在这里进行空闲处理
		    printf("在这里进行空闲处理");
	}
	return 0;
}


回收特定子进程

无论一个进程是正常终止还是异常终止,都会通过系统内核向其父进程发送SIGCHILD(17)信号,父进程可以忽略该信号,也可以提供一个针对该信号的信号处理函数,在信号处理函数中以异步的方式回收该子进程。这样不仅流程简单,而且僵尸的存活时间短,回收效率高。
//08.c
输出:
这是父进程,pid=5979
这是子进程,pid=5980,父进程的pid=5979

5980子进程终止5979父进程终止

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
int main(){
	pid_t pid;
	if((pid=fork())<0)
	    perror("fork");
	if(pid==0){
		printf("这是子进程,pid=%d,",getpid());
		printf("父进程的pid=%d\n",getppid());
		_exit(0);
	}
	else
		printf("这是父进程,pid=%d\n",getpid());
	//等待并回收所有子进程,忽略其终止状态。
		pid=waitpid(pid,NULL,0);
		if(pid==-1){
			if(errno!=ECHILD){
				perror("wait");
				exit(EXIT_FAILURE);
			}
		}
		else
			printf("%d子进程终止",pid);
		printf("%d父进程终止\n",getpid());
	return 0;
}

7)创建新进程的exec函数族
与fork或vfork函数不同,exec函数不是创建调用进程的子进程,而是创建一个新的进程取代调用进程自身。新进程会用自己的全部地址空间,覆盖调用进程的地址空间,但进程的PID保持不变。
<unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...
                       /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
                       char *const envp[]);
int execve(const char *filename, char *const argv[],

                  char *const envp[]);


//10execl.c

输出:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
	if(execl("/bin/pwd","pwd",NULL)==-1){
		perror("execl");
		exit(EXIT_FAILURE);
	}
	printf("本函数执行完毕");
	return 0;
}


//10execle.c

输出:

/home/y/case8

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
	char *envp[]={"HOME=/home/y",NULL};
	if(execle("/bin/pwd","pwd",NULL,envp)==-1){
		perror("execl");
		exit(EXIT_FAILURE);
	}
	printf("本函数执行完毕");
	return 0;
}

//10execlp.c

输出:

/home/y/case8

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
	if(execlp("pwd","pwd",NULL)==-1){
		perror("execl");
		exit(EXIT_FAILURE);
	}
	printf("本函数执行完毕");
	return 0;
}

//10vexec.c

输出:

本函数执行完毕
/home/y/case8
3086子进程终止

3085父进程终止

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
	pid_t pid=vfork();
	if(pid==-1){
		perror("vfork");
		exit(EXIT_FAILURE);
	}
	if(pid==0){
		if(execl("/bin/pwd","pwd",NULL)==-1){
			perror("execl");
			exit(EXIT_FAILURE);
		}
		_exit(0);
	}
	else
	    printf("本函数执行完毕\n");
	pid=wait(NULL);
	    if(pid==-1 && errno!=ECHILD){
	    	perror("wait");
		exit(EXIT_FAILURE);
	    }
	printf("%d子进程终止\n",pid);
	printf("%d父进程终止\n",getpid());
	return 0;
}

使用system函数可以执行shell命令

//11system.c

输出:

Shell不可用

#include <stdio.h>
#include <stdlib.h>
int main(){
	int status;
	if(status=system(NULL)==-1){
		perror("system");
		exit(EXIT_FAILURE);
	}
	if(!status)
	    printf("Shell不可用\n");
	else{
		if((status=system("ls -l"))==-1){
			perror("system");
			exit(EXIT_FAILURE);
		}
		printf("%d\n",WEXITSTATUS(status));
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值